mirror of
https://github.com/appwrite/appwrite
synced 2026-05-23 08:58:35 +00:00
Merge remote-tracking branch 'origin/feat-custom-cf-hostnames' into feat-migration
# Conflicts: # composer.json # composer.lock
This commit is contained in:
commit
21bff77c50
44 changed files with 4173 additions and 3218 deletions
|
|
@ -4945,7 +4945,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "listExecutions",
|
||||
"weight": 305,
|
||||
"weight": 306,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -5033,7 +5033,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "createExecution",
|
||||
"weight": 304,
|
||||
"weight": 305,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -5150,7 +5150,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "getExecution",
|
||||
"weight": 306,
|
||||
"weight": 307,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -5226,7 +5226,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "query",
|
||||
"weight": 330,
|
||||
"weight": 331,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"deprecated": false,
|
||||
|
|
@ -5280,7 +5280,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "mutation",
|
||||
"weight": 329,
|
||||
"weight": 330,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"deprecated": false,
|
||||
|
|
@ -5766,7 +5766,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"weight": 381,
|
||||
"weight": 382,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -5851,7 +5851,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"weight": 385,
|
||||
"weight": 386,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -5928,7 +5928,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "listFiles",
|
||||
"weight": 207,
|
||||
"weight": 208,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -6016,7 +6016,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "createFile",
|
||||
"weight": 206,
|
||||
"weight": 207,
|
||||
"cookies": false,
|
||||
"type": "upload",
|
||||
"deprecated": false,
|
||||
|
|
@ -6116,7 +6116,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFile",
|
||||
"weight": 208,
|
||||
"weight": 209,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -6190,7 +6190,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateFile",
|
||||
"weight": 213,
|
||||
"weight": 214,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -6281,7 +6281,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteFile",
|
||||
"weight": 214,
|
||||
"weight": 215,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -6350,7 +6350,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFileDownload",
|
||||
"weight": 210,
|
||||
"weight": 211,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
|
|
@ -6419,7 +6419,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFilePreview",
|
||||
"weight": 209,
|
||||
"weight": 210,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
|
|
@ -6637,7 +6637,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFileView",
|
||||
"weight": 211,
|
||||
"weight": 212,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
|
|
@ -6713,7 +6713,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "list",
|
||||
"weight": 218,
|
||||
"weight": 219,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -6791,7 +6791,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "create",
|
||||
"weight": 217,
|
||||
"weight": 218,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -6878,7 +6878,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"weight": 219,
|
||||
"weight": 220,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -6942,7 +6942,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateName",
|
||||
"weight": 221,
|
||||
"weight": 222,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7018,7 +7018,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "delete",
|
||||
"weight": 223,
|
||||
"weight": 224,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7069,7 +7069,7 @@
|
|||
"tags": [
|
||||
"teams"
|
||||
],
|
||||
"description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.",
|
||||
"description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Memberships List",
|
||||
|
|
@ -7084,7 +7084,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "listMemberships",
|
||||
"weight": 225,
|
||||
"weight": 226,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7172,7 +7172,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMembership",
|
||||
"weight": 224,
|
||||
"weight": 225,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7270,7 +7270,7 @@
|
|||
"tags": [
|
||||
"teams"
|
||||
],
|
||||
"description": "Get a team member by the membership unique id. All team members have read access for this resource.",
|
||||
"description": "Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Membership",
|
||||
|
|
@ -7285,7 +7285,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "getMembership",
|
||||
"weight": 226,
|
||||
"weight": 227,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7359,7 +7359,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMembership",
|
||||
"weight": 227,
|
||||
"weight": 228,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7448,7 +7448,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteMembership",
|
||||
"weight": 229,
|
||||
"weight": 230,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7524,7 +7524,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMembershipStatus",
|
||||
"weight": 228,
|
||||
"weight": 229,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7624,7 +7624,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "getPrefs",
|
||||
"weight": 220,
|
||||
"weight": 221,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7687,7 +7687,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePrefs",
|
||||
"weight": 222,
|
||||
"weight": 223,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -9266,12 +9266,12 @@
|
|||
},
|
||||
"userName": {
|
||||
"type": "string",
|
||||
"description": "User name.",
|
||||
"description": "User name. Hide this attribute by toggling membership privacy in the Console.",
|
||||
"x-example": "John Doe"
|
||||
},
|
||||
"userEmail": {
|
||||
"type": "string",
|
||||
"description": "User email address.",
|
||||
"description": "User email address. Hide this attribute by toggling membership privacy in the Console.",
|
||||
"x-example": "john@appwrite.io"
|
||||
},
|
||||
"teamId": {
|
||||
|
|
@ -9301,7 +9301,7 @@
|
|||
},
|
||||
"mfa": {
|
||||
"type": "boolean",
|
||||
"description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise.",
|
||||
"description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console.",
|
||||
"x-example": false
|
||||
},
|
||||
"roles": {
|
||||
|
|
@ -9826,7 +9826,7 @@
|
|||
"name": {
|
||||
"type": "string",
|
||||
"description": "Target Name.",
|
||||
"x-example": "Aegon apple token"
|
||||
"x-example": "Apple iPhone 12"
|
||||
},
|
||||
"userId": {
|
||||
"type": "string",
|
||||
|
|
@ -9848,6 +9848,11 @@
|
|||
"type": "string",
|
||||
"description": "The target identifier.",
|
||||
"x-example": "token"
|
||||
},
|
||||
"expired": {
|
||||
"type": "boolean",
|
||||
"description": "Is the target expired.",
|
||||
"x-example": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -9857,7 +9862,8 @@
|
|||
"name",
|
||||
"userId",
|
||||
"providerType",
|
||||
"identifier"
|
||||
"identifier",
|
||||
"expired"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -5101,7 +5101,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "listExecutions",
|
||||
"weight": 305,
|
||||
"weight": 306,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -5186,7 +5186,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "createExecution",
|
||||
"weight": 304,
|
||||
"weight": 305,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -5307,7 +5307,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "getExecution",
|
||||
"weight": 306,
|
||||
"weight": 307,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -5381,7 +5381,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "query",
|
||||
"weight": 330,
|
||||
"weight": 331,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"deprecated": false,
|
||||
|
|
@ -5457,7 +5457,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "mutation",
|
||||
"weight": 329,
|
||||
"weight": 330,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"deprecated": false,
|
||||
|
|
@ -5981,7 +5981,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"weight": 381,
|
||||
"weight": 382,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -6070,7 +6070,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"weight": 385,
|
||||
"weight": 386,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -6145,7 +6145,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "listFiles",
|
||||
"weight": 207,
|
||||
"weight": 208,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -6230,7 +6230,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "createFile",
|
||||
"weight": 206,
|
||||
"weight": 207,
|
||||
"cookies": false,
|
||||
"type": "upload",
|
||||
"deprecated": false,
|
||||
|
|
@ -6324,7 +6324,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFile",
|
||||
"weight": 208,
|
||||
"weight": 209,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -6396,7 +6396,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateFile",
|
||||
"weight": 213,
|
||||
"weight": 214,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -6487,7 +6487,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteFile",
|
||||
"weight": 214,
|
||||
"weight": 215,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -6561,7 +6561,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFileDownload",
|
||||
"weight": 210,
|
||||
"weight": 211,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
|
|
@ -6635,7 +6635,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFilePreview",
|
||||
"weight": 209,
|
||||
"weight": 210,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
|
|
@ -6836,7 +6836,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "getFileView",
|
||||
"weight": 211,
|
||||
"weight": 212,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"deprecated": false,
|
||||
|
|
@ -6910,7 +6910,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "list",
|
||||
"weight": 218,
|
||||
"weight": 219,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -6987,7 +6987,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "create",
|
||||
"weight": 217,
|
||||
"weight": 218,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7081,7 +7081,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"weight": 219,
|
||||
"weight": 220,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7145,7 +7145,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateName",
|
||||
"weight": 221,
|
||||
"weight": 222,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7222,7 +7222,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "delete",
|
||||
"weight": 223,
|
||||
"weight": 224,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7277,7 +7277,7 @@
|
|||
"tags": [
|
||||
"teams"
|
||||
],
|
||||
"description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.",
|
||||
"description": "Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Memberships List",
|
||||
|
|
@ -7288,7 +7288,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "listMemberships",
|
||||
"weight": 225,
|
||||
"weight": 226,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7373,7 +7373,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "createMembership",
|
||||
"weight": 224,
|
||||
"weight": 225,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7479,7 +7479,7 @@
|
|||
"tags": [
|
||||
"teams"
|
||||
],
|
||||
"description": "Get a team member by the membership unique id. All team members have read access for this resource.",
|
||||
"description": "Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Membership",
|
||||
|
|
@ -7490,7 +7490,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "getMembership",
|
||||
"weight": 226,
|
||||
"weight": 227,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7562,7 +7562,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMembership",
|
||||
"weight": 227,
|
||||
"weight": 228,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7650,7 +7650,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "deleteMembership",
|
||||
"weight": 229,
|
||||
"weight": 230,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7724,7 +7724,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "updateMembershipStatus",
|
||||
"weight": 228,
|
||||
"weight": 229,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7822,7 +7822,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "getPrefs",
|
||||
"weight": 220,
|
||||
"weight": 221,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -7885,7 +7885,7 @@
|
|||
},
|
||||
"x-appwrite": {
|
||||
"method": "updatePrefs",
|
||||
"weight": 222,
|
||||
"weight": 223,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"deprecated": false,
|
||||
|
|
@ -9445,12 +9445,12 @@
|
|||
},
|
||||
"userName": {
|
||||
"type": "string",
|
||||
"description": "User name.",
|
||||
"description": "User name. Hide this attribute by toggling membership privacy in the Console.",
|
||||
"x-example": "John Doe"
|
||||
},
|
||||
"userEmail": {
|
||||
"type": "string",
|
||||
"description": "User email address.",
|
||||
"description": "User email address. Hide this attribute by toggling membership privacy in the Console.",
|
||||
"x-example": "john@appwrite.io"
|
||||
},
|
||||
"teamId": {
|
||||
|
|
@ -9480,7 +9480,7 @@
|
|||
},
|
||||
"mfa": {
|
||||
"type": "boolean",
|
||||
"description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise.",
|
||||
"description": "Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console.",
|
||||
"x-example": false
|
||||
},
|
||||
"roles": {
|
||||
|
|
@ -10008,7 +10008,7 @@
|
|||
"name": {
|
||||
"type": "string",
|
||||
"description": "Target Name.",
|
||||
"x-example": "Aegon apple token"
|
||||
"x-example": "Apple iPhone 12"
|
||||
},
|
||||
"userId": {
|
||||
"type": "string",
|
||||
|
|
@ -10030,6 +10030,11 @@
|
|||
"type": "string",
|
||||
"description": "The target identifier.",
|
||||
"x-example": "token"
|
||||
},
|
||||
"expired": {
|
||||
"type": "boolean",
|
||||
"description": "Is the target expired.",
|
||||
"x-example": false
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -10039,7 +10044,8 @@
|
|||
"name",
|
||||
"userId",
|
||||
"providerType",
|
||||
"identifier"
|
||||
"identifier",
|
||||
"expired"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -274,7 +274,6 @@ $createSession = function (string $userId, string $secret, Request $request, Res
|
|||
App::post('/v1/account')
|
||||
->desc('Create account')
|
||||
->groups(['api', 'account', 'auth'])
|
||||
->label('event', 'users.[userId].create')
|
||||
->label('scope', 'sessions.write')
|
||||
->label('auth.type', 'emailPassword')
|
||||
->label('audits.event', 'user.create')
|
||||
|
|
@ -297,9 +296,8 @@ App::post('/v1/account')
|
|||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
|
||||
->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
|
||||
$email = \strtolower($email);
|
||||
if ('console' === $project->getId()) {
|
||||
|
|
@ -409,8 +407,6 @@ App::post('/v1/account')
|
|||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
Authorization::setRole(Role::users()->toString());
|
||||
|
||||
$queueForEvents->setParam('userId', $user->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($user, Response::MODEL_ACCOUNT);
|
||||
|
|
@ -442,7 +438,6 @@ App::get('/v1/account')
|
|||
App::delete('/v1/account')
|
||||
->desc('Delete account')
|
||||
->groups(['api', 'account'])
|
||||
->label('event', 'users.[userId].delete')
|
||||
->label('scope', 'account')
|
||||
->label('audits.event', 'user.delete')
|
||||
->label('audits.resource', 'user/{response.$id}')
|
||||
|
|
@ -1499,6 +1494,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
|||
'providerType' => MESSAGE_TYPE_EMAIL,
|
||||
'identifier' => $email,
|
||||
]));
|
||||
|
||||
} catch (Duplicate) {
|
||||
$failureRedirect(Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ use Utopia\Database\Exception\Authorization as AuthorizationException;
|
|||
use Utopia\Database\Exception\Conflict as ConflictException;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Exception\Limit as LimitException;
|
||||
use Utopia\Database\Exception\NotFound as NotFoundException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Exception\Restricted as RestrictedException;
|
||||
use Utopia\Database\Exception\Structure as StructureException;
|
||||
|
|
@ -351,13 +352,16 @@ function updateAttribute(
|
|||
if ($type === Database::VAR_RELATIONSHIP) {
|
||||
$primaryDocumentOptions = \array_merge($attribute->getAttribute('options', []), $options);
|
||||
$attribute->setAttribute('options', $primaryDocumentOptions);
|
||||
|
||||
$dbForProject->updateRelationship(
|
||||
collection: $collectionId,
|
||||
id: $key,
|
||||
newKey: $newKey,
|
||||
onDelete: $primaryDocumentOptions['onDelete'],
|
||||
);
|
||||
try {
|
||||
$dbForProject->updateRelationship(
|
||||
collection: $collectionId,
|
||||
id: $key,
|
||||
newKey: $newKey,
|
||||
onDelete: $primaryDocumentOptions['onDelete'],
|
||||
);
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($primaryDocumentOptions['twoWay']) {
|
||||
$relatedCollection = $dbForProject->getDocument('database_' . $db->getInternalId(), $primaryDocumentOptions['relatedCollection']);
|
||||
|
|
@ -387,6 +391,8 @@ function updateAttribute(
|
|||
);
|
||||
} catch (TruncateException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_INVALID_RESIZE);
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::ATTRIBUTE_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -529,12 +535,7 @@ App::get('/v1/databases')
|
|||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (array $queries, string $search, Response $response, Database $dbForProject) {
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
$queries = Query::parseQueries($queries);
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
|
|
@ -815,22 +816,21 @@ App::post('/v1/databases/:databaseId/collections')
|
|||
$collectionId = $collectionId == 'unique()' ? ID::unique() : $collectionId;
|
||||
|
||||
// Map aggregate permissions into the multiple permissions they represent.
|
||||
$permissions = Permission::aggregate($permissions);
|
||||
$permissions = Permission::aggregate($permissions) ?? [];
|
||||
|
||||
try {
|
||||
$dbForProject->createDocument('database_' . $database->getInternalId(), new Document([
|
||||
$collection = $dbForProject->createDocument('database_' . $database->getInternalId(), new Document([
|
||||
'$id' => $collectionId,
|
||||
'databaseInternalId' => $database->getInternalId(),
|
||||
'databaseId' => $databaseId,
|
||||
'$permissions' => $permissions ?? [],
|
||||
'$permissions' => $permissions,
|
||||
'documentSecurity' => $documentSecurity,
|
||||
'enabled' => $enabled,
|
||||
'name' => $name,
|
||||
'search' => implode(' ', [$collectionId, $name]),
|
||||
]));
|
||||
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
|
||||
|
||||
$dbForProject->createCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), permissions: $permissions ?? [], documentSecurity: $documentSecurity);
|
||||
$dbForProject->createCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), permissions: $permissions, documentSecurity: $documentSecurity);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::COLLECTION_ALREADY_EXISTS);
|
||||
} catch (LimitException) {
|
||||
|
|
@ -848,7 +848,7 @@ App::post('/v1/databases/:databaseId/collections')
|
|||
});
|
||||
|
||||
App::get('/v1/databases/:databaseId/collections')
|
||||
->alias('/v1/database/collections', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections')
|
||||
->desc('List collections')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'collections.read')
|
||||
|
|
@ -874,11 +874,7 @@ App::get('/v1/databases/:databaseId/collections')
|
|||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
$queries = Query::parseQueries($queries);
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
|
|
@ -918,7 +914,7 @@ App::get('/v1/databases/:databaseId/collections')
|
|||
});
|
||||
|
||||
App::get('/v1/databases/:databaseId/collections/:collectionId')
|
||||
->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId')
|
||||
->desc('Get collection')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'collections.read')
|
||||
|
|
@ -953,7 +949,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId')
|
|||
});
|
||||
|
||||
App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
|
||||
->alias('/v1/database/collections/:collectionId/logs', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId/logs')
|
||||
->desc('List collection logs')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'collections.read')
|
||||
|
|
@ -987,12 +983,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
|
|||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
$grouped = Query::groupByType($queries);
|
||||
$limit = $grouped['limit'] ?? APP_LIMIT_COUNT;
|
||||
$offset = $grouped['offset'] ?? 0;
|
||||
|
|
@ -1054,7 +1045,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/logs')
|
|||
|
||||
|
||||
App::put('/v1/databases/:databaseId/collections/:collectionId')
|
||||
->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId')
|
||||
->desc('Update collection')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
|
|
@ -1100,12 +1091,16 @@ App::put('/v1/databases/:databaseId/collections/:collectionId')
|
|||
|
||||
$enabled ??= $collection->getAttribute('enabled', true);
|
||||
|
||||
$collection = $dbForProject->updateDocument('database_' . $database->getInternalId(), $collectionId, $collection
|
||||
$collection = $dbForProject->updateDocument(
|
||||
'database_' . $database->getInternalId(),
|
||||
$collectionId,
|
||||
$collection
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('$permissions', $permissions)
|
||||
->setAttribute('documentSecurity', $documentSecurity)
|
||||
->setAttribute('enabled', $enabled)
|
||||
->setAttribute('search', implode(' ', [$collectionId, $name])));
|
||||
->setAttribute('search', \implode(' ', [$collectionId, $name]))
|
||||
);
|
||||
|
||||
$dbForProject->updateCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $permissions, $documentSecurity);
|
||||
|
||||
|
|
@ -1118,7 +1113,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId')
|
|||
});
|
||||
|
||||
App::delete('/v1/databases/:databaseId/collections/:collectionId')
|
||||
->alias('/v1/database/collections/:collectionId', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId')
|
||||
->desc('Delete collection')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
|
|
@ -1174,7 +1169,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId')
|
|||
});
|
||||
|
||||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/string', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId/attributes/string')
|
||||
->desc('Create string attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
|
|
@ -1225,14 +1220,13 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/string
|
|||
'filters' => $filters,
|
||||
]), $response, $dbForProject, $queueForDatabase, $queueForEvents);
|
||||
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
|
||||
->dynamic($attribute, Response::MODEL_ATTRIBUTE_STRING);
|
||||
});
|
||||
|
||||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/email', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId/attributes/email')
|
||||
->desc('Create email attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
|
|
@ -1275,7 +1269,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/email'
|
|||
});
|
||||
|
||||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/enum', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId/attributes/enum')
|
||||
->desc('Create enum attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
|
|
@ -1323,7 +1317,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/enum')
|
|||
});
|
||||
|
||||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/ip', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId/attributes/ip')
|
||||
->desc('Create IP address attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
|
|
@ -1366,7 +1360,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/ip')
|
|||
});
|
||||
|
||||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/url', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId/attributes/url')
|
||||
->desc('Create URL attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
|
|
@ -1409,7 +1403,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/url')
|
|||
});
|
||||
|
||||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/integer')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/integer', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId/attributes/integer')
|
||||
->desc('Create integer attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
|
|
@ -1439,8 +1433,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege
|
|||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?int $min, ?int $max, ?int $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
|
||||
|
||||
// Ensure attribute default is within range
|
||||
$min = (is_null($min)) ? PHP_INT_MIN : \intval($min);
|
||||
$max = (is_null($max)) ? PHP_INT_MAX : \intval($max);
|
||||
$min = \is_null($min) ? PHP_INT_MIN : $min;
|
||||
$max = \is_null($max) ? PHP_INT_MAX : $max;
|
||||
|
||||
if ($min > $max) {
|
||||
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value');
|
||||
|
|
@ -1481,7 +1475,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/intege
|
|||
});
|
||||
|
||||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/float', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId/attributes/float')
|
||||
->desc('Create float attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
|
|
@ -1511,21 +1505,16 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float'
|
|||
->action(function (string $databaseId, string $collectionId, string $key, ?bool $required, ?float $min, ?float $max, ?float $default, bool $array, Response $response, Database $dbForProject, EventDatabase $queueForDatabase, Event $queueForEvents) {
|
||||
|
||||
// Ensure attribute default is within range
|
||||
$min = (is_null($min)) ? -PHP_FLOAT_MAX : \floatval($min);
|
||||
$max = (is_null($max)) ? PHP_FLOAT_MAX : \floatval($max);
|
||||
$min = \is_null($min) ? -PHP_FLOAT_MAX : $min;
|
||||
$max = \is_null($max) ? PHP_FLOAT_MAX : $max;
|
||||
|
||||
if ($min > $max) {
|
||||
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, 'Minimum value must be lesser than maximum value');
|
||||
}
|
||||
|
||||
// Ensure default value is a float
|
||||
if (!is_null($default)) {
|
||||
$default = \floatval($default);
|
||||
}
|
||||
|
||||
$validator = new Range($min, $max, Database::VAR_FLOAT);
|
||||
|
||||
if (!is_null($default) && !$validator->isValid($default)) {
|
||||
if (!\is_null($default) && !$validator->isValid($default)) {
|
||||
throw new Exception(Exception::ATTRIBUTE_VALUE_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
|
|
@ -1556,7 +1545,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/float'
|
|||
});
|
||||
|
||||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolean')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/boolean', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId/attributes/boolean')
|
||||
->desc('Create boolean attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
|
|
@ -1598,7 +1587,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/boolea
|
|||
});
|
||||
|
||||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/datetime')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/datetime', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId/attributes/datetime')
|
||||
->desc('Create datetime attribute')
|
||||
->groups(['api', 'database'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
|
|
@ -1617,7 +1606,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti
|
|||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new DatetimeValidator(), 'Default value for the attribute in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Cannot be set when attribute is required.', true)
|
||||
->param('default', null, fn (Database $dbForProject) => new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMaxDateTime()), 'Default value for the attribute in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Cannot be set when attribute is required.', true, ['dbForProject'])
|
||||
->param('array', false, new Boolean(), 'Is attribute an array?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
|
|
@ -1643,7 +1632,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/dateti
|
|||
});
|
||||
|
||||
App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relationship')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/relationship', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId/attributes/relationship')
|
||||
->desc('Create relationship attribute')
|
||||
->groups(['api', 'database'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].attributes.[attributeId].create')
|
||||
|
|
@ -1772,7 +1761,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati
|
|||
});
|
||||
|
||||
App::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
|
||||
->alias('/v1/database/collections/:collectionId/attributes', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId/attributes')
|
||||
->desc('List attributes')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'collections.read')
|
||||
|
|
@ -1803,16 +1792,12 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
|
|||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
$queries = Query::parseQueries($queries);
|
||||
|
||||
\array_push(
|
||||
$queries,
|
||||
Query::equal('databaseInternalId', [$database->getInternalId()]),
|
||||
Query::equal('collectionInternalId', [$collection->getInternalId()]),
|
||||
Query::equal('databaseInternalId', [$database->getInternalId()])
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -1821,6 +1806,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
|
|||
$cursor = \array_filter($queries, function ($query) {
|
||||
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
|
||||
});
|
||||
|
||||
$cursor = \reset($cursor);
|
||||
|
||||
if ($cursor) {
|
||||
|
|
@ -1831,8 +1817,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
|
|||
|
||||
$attributeId = $cursor->getValue();
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->find('attributes', [
|
||||
Query::equal('collectionInternalId', [$collection->getInternalId()]),
|
||||
Query::equal('databaseInternalId', [$database->getInternalId()]),
|
||||
Query::equal('collectionInternalId', [$collection->getInternalId()]),
|
||||
Query::equal('key', [$attributeId]),
|
||||
Query::limit(1),
|
||||
]));
|
||||
|
|
@ -1856,7 +1842,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/attributes')
|
|||
});
|
||||
|
||||
App::get('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/:key', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId/attributes/:key')
|
||||
->desc('Get attribute')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'collections.read')
|
||||
|
|
@ -1950,7 +1936,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/strin
|
|||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new Text(0, 0)), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->param('size', null, new Integer(), 'Maximum size of the string attribute.', true)
|
||||
->param('size', null, new Range(1, APP_DATABASE_ATTRIBUTE_STRING_MAX_LENGTH, Range::TYPE_INTEGER), 'Maximum size of the string attribute.', true)
|
||||
->param('newKey', null, new Key(), 'New attribute key.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
|
|
@ -2308,7 +2294,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/datet
|
|||
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
|
||||
->param('key', '', new Key(), 'Attribute Key.')
|
||||
->param('required', null, new Boolean(), 'Is attribute required?')
|
||||
->param('default', null, new Nullable(new DatetimeValidator()), 'Default value for attribute when not provided. Cannot be set when attribute is required.')
|
||||
->param('default', null, fn (Database $dbForProject) => new Nullable(new DatetimeValidator($dbForProject->getAdapter()->getMinDateTime(), $dbForProject->getAdapter()->getMaxDateTime())), 'Default value for attribute when not provided. Cannot be set when attribute is required.', injections: ['dbForProject'])
|
||||
->param('newKey', null, new Key(), 'New attribute key.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
|
|
@ -2389,7 +2375,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/attributes/:key/
|
|||
});
|
||||
|
||||
App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key')
|
||||
->alias('/v1/database/collections/:collectionId/attributes/:key', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId/attributes/:key')
|
||||
->desc('Delete attribute')
|
||||
->groups(['api', 'database', 'schema'])
|
||||
->label('scope', 'collections.write')
|
||||
|
|
@ -2503,7 +2489,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/attributes/:key
|
|||
});
|
||||
|
||||
App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
||||
->alias('/v1/database/collections/:collectionId/indexes', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId/indexes')
|
||||
->desc('Create index')
|
||||
->groups(['api', 'database'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].indexes.[indexId].create')
|
||||
|
|
@ -2674,7 +2660,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
|||
});
|
||||
|
||||
App::get('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
||||
->alias('/v1/database/collections/:collectionId/indexes', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId/indexes')
|
||||
->desc('List indexes')
|
||||
->groups(['api', 'database'])
|
||||
->label('scope', 'collections.read')
|
||||
|
|
@ -2705,13 +2691,13 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/indexes')
|
|||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
$queries = Query::parseQueries($queries);
|
||||
|
||||
\array_push($queries, Query::equal('collectionId', [$collectionId]), Query::equal('databaseId', [$databaseId]));
|
||||
\array_push(
|
||||
$queries,
|
||||
Query::equal('databaseId', [$databaseId]),
|
||||
Query::equal('collectionId', [$collectionId]),
|
||||
);
|
||||
|
||||
/**
|
||||
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
|
||||
|
|
@ -3041,10 +3027,12 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
|
|||
|
||||
try {
|
||||
$document = $dbForProject->createDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $document);
|
||||
} catch (StructureException $exception) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage());
|
||||
} catch (DuplicateException $exception) {
|
||||
} catch (StructureException $e) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
|
||||
} catch (DuplicateException $e) {
|
||||
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
|
||||
} catch (NotFoundException $e) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Add $collectionId and $databaseId for all documents
|
||||
|
|
@ -3175,14 +3163,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
|
|||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
try {
|
||||
$documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries);
|
||||
$total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT);
|
||||
} catch (AuthorizationException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
$documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries);
|
||||
$total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT);
|
||||
|
||||
// Add $collectionId and $databaseId for all documents
|
||||
$processDocument = (function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database): bool {
|
||||
|
|
@ -3468,7 +3450,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
|
|||
});
|
||||
|
||||
App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId')
|
||||
->alias('/v1/database/collections/:collectionId/documents/:documentId', ['databaseId' => 'default'])
|
||||
->alias('/v1/database/collections/:collectionId/documents/:documentId')
|
||||
->desc('Update document')
|
||||
->groups(['api', 'database'])
|
||||
->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].update')
|
||||
|
|
@ -3646,8 +3628,10 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
|
|||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
|
||||
} catch (StructureException $exception) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $exception->getMessage());
|
||||
} catch (StructureException $e) {
|
||||
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
|
||||
} catch (NotFoundException $e) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Add $collectionId and $databaseId for all documents
|
||||
|
|
@ -3756,12 +3740,16 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
|
|||
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $documentId) {
|
||||
$dbForProject->deleteDocument(
|
||||
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
|
||||
$documentId
|
||||
);
|
||||
});
|
||||
try {
|
||||
$dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $documentId) {
|
||||
$dbForProject->deleteDocument(
|
||||
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
|
||||
$documentId
|
||||
);
|
||||
});
|
||||
} catch (NotFoundException $e) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Add $collectionId and $databaseId for all documents
|
||||
$processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) {
|
||||
|
|
|
|||
|
|
@ -1753,7 +1753,7 @@ App::post('/v1/functions/:functionId/executions')
|
|||
->param('path', '/', new Text(2048), 'HTTP path of execution. Path can include query params. Default value is /', true)
|
||||
->param('method', 'POST', new Whitelist(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], true), 'HTTP method of execution. Default value is GET.', true)
|
||||
->param('headers', [], new AnyOf([new Assoc(), new Text(65535)], AnyOf::TYPE_MIXED), 'HTTP headers of execution. Defaults to empty.', true)
|
||||
->param('scheduledAt', null, new DatetimeValidator(true, DateTimeValidator::PRECISION_MINUTES, 60), 'Scheduled execution time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.', true)
|
||||
->param('scheduledAt', null, new DatetimeValidator(requireDateInFuture: true, precision: DateTimeValidator::PRECISION_MINUTES, offset: 60), 'Scheduled execution time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.', true)
|
||||
->inject('response')
|
||||
->inject('request')
|
||||
->inject('project')
|
||||
|
|
@ -2642,9 +2642,11 @@ App::get('/v1/functions/templates/:templateId')
|
|||
->action(function (string $templateId, Response $response) {
|
||||
$templates = Config::getParam('function-templates', []);
|
||||
|
||||
$template = array_shift(\array_filter($templates, function ($template) use ($templateId) {
|
||||
$filtered = \array_filter($templates, function ($template) use ($templateId) {
|
||||
return $template['id'] === $templateId;
|
||||
}));
|
||||
});
|
||||
|
||||
$template = array_shift($filtered);
|
||||
|
||||
if (empty($template)) {
|
||||
throw new Exception(Exception::FUNCTION_TEMPLATE_NOT_FOUND);
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ App::get('/v1/health/cache')
|
|||
foreach ($configs as $key => $config) {
|
||||
foreach ($config as $database) {
|
||||
try {
|
||||
/** @var \Utopia\Cache\Adapter $adapter */
|
||||
$adapter = $pools->get($database)->pop()->getResource();
|
||||
|
||||
$checkStart = \microtime(true);
|
||||
|
|
@ -191,11 +192,11 @@ App::get('/v1/health/queue')
|
|||
|
||||
foreach ($configs as $key => $config) {
|
||||
foreach ($config as $database) {
|
||||
$checkStart = \microtime(true);
|
||||
try {
|
||||
/** @var Connection $adapter */
|
||||
$adapter = $pools->get($database)->pop()->getResource();
|
||||
|
||||
$checkStart = \microtime(true);
|
||||
|
||||
if ($adapter->ping()) {
|
||||
$output[] = new Document([
|
||||
'name' => $key . " ($database)",
|
||||
|
|
@ -249,6 +250,7 @@ App::get('/v1/health/pubsub')
|
|||
foreach ($configs as $key => $config) {
|
||||
foreach ($config as $database) {
|
||||
try {
|
||||
/** @var \Appwrite\PubSub\Adapter $adapter */
|
||||
$adapter = $pools->get($database)->pop()->getResource();
|
||||
|
||||
$checkStart = \microtime(true);
|
||||
|
|
|
|||
|
|
@ -111,6 +111,9 @@ App::post('/v1/projects')
|
|||
'personalDataCheck' => false,
|
||||
'mockNumbers' => [],
|
||||
'sessionAlerts' => false,
|
||||
'membershipsUserName' => false,
|
||||
'membershipsUserEmail' => false,
|
||||
'membershipsMfa' => false,
|
||||
];
|
||||
|
||||
foreach ($auth as $method) {
|
||||
|
|
@ -693,6 +696,41 @@ App::patch('/v1/projects/:projectId/auth/session-alerts')
|
|||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/memberships-privacy')
|
||||
->desc('Update project memberships privacy attributes')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
|
||||
->label('sdk.namespace', 'projects')
|
||||
->label('sdk.method', 'updateMembershipsPrivacy')
|
||||
->label('sdk.response.code', Response::STATUS_CODE_OK)
|
||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||
->label('sdk.response.model', Response::MODEL_PROJECT)
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('userName', true, new Boolean(true), 'Set to true to show userName to members of a team.')
|
||||
->param('userEmail', true, new Boolean(true), 'Set to true to show email to members of a team.')
|
||||
->param('mfa', true, new Boolean(true), 'Set to true to show mfa to members of a team.')
|
||||
->inject('response')
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $projectId, bool $userName, bool $userEmail, bool $mfa, Response $response, Database $dbForConsole) {
|
||||
$project = $dbForConsole->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$auths = $project->getAttribute('auths', []);
|
||||
|
||||
$auths['membershipsUserName'] = $userName;
|
||||
$auths['membershipsUserEmail'] = $userEmail;
|
||||
$auths['membershipsMfa'] = $mfa;
|
||||
|
||||
$dbForConsole->updateDocument('projects', $project->getId(), $project
|
||||
->setAttribute('auths', $auths));
|
||||
|
||||
$response->dynamic($project, Response::MODEL_PROJECT);
|
||||
});
|
||||
|
||||
App::patch('/v1/projects/:projectId/auth/limit')
|
||||
->desc('Update project users limit')
|
||||
->groups(['api', 'projects'])
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ use Utopia\App;
|
|||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Exception\NotFound as NotFoundException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
|
|
@ -67,19 +68,19 @@ App::post('/v1/storage/buckets')
|
|||
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
|
||||
->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
|
||||
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
|
||||
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
|
||||
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD], true), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
|
||||
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
|
||||
->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, ?string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
|
||||
$bucketId = $bucketId === 'unique()' ? ID::unique() : $bucketId;
|
||||
|
||||
// Map aggregate permissions into the multiple permissions they represent.
|
||||
$permissions = Permission::aggregate($permissions);
|
||||
|
||||
$compression ??= Compression::NONE;
|
||||
try {
|
||||
$files = (Config::getParam('collections', [])['buckets'] ?? [])['files'] ?? [];
|
||||
if (empty($files)) {
|
||||
|
|
@ -131,7 +132,7 @@ App::post('/v1/storage/buckets')
|
|||
$bucket = $dbForProject->getDocument('buckets', $bucketId);
|
||||
|
||||
$dbForProject->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes, permissions: $permissions ?? [], documentSecurity: $fileSecurity);
|
||||
} catch (Duplicate) {
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
|
|
@ -253,13 +254,13 @@ App::put('/v1/storage/buckets/:bucketId')
|
|||
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
|
||||
->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
|
||||
->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true)
|
||||
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
|
||||
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD], true), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
|
||||
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
|
||||
->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, ?string $compression, bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
$bucket = $dbForProject->getDocument('buckets', $bucketId);
|
||||
|
||||
if ($bucket->isEmpty()) {
|
||||
|
|
@ -272,11 +273,8 @@ App::put('/v1/storage/buckets/:bucketId')
|
|||
$enabled ??= $bucket->getAttribute('enabled', true);
|
||||
$encryption ??= $bucket->getAttribute('encryption', true);
|
||||
$antivirus ??= $bucket->getAttribute('antivirus', true);
|
||||
$compression ??= $bucket->getAttribute('compression', Compression::NONE);
|
||||
|
||||
/**
|
||||
* Map aggregate permissions into the multiple permissions they represent,
|
||||
* accounting for the resource type given that some types not allowed specific permissions.
|
||||
*/
|
||||
// Map aggregate permissions into the multiple permissions they represent.
|
||||
$permissions = Permission::aggregate($permissions);
|
||||
|
||||
|
|
@ -290,11 +288,11 @@ App::put('/v1/storage/buckets/:bucketId')
|
|||
->setAttribute('encryption', $encryption)
|
||||
->setAttribute('compression', $compression)
|
||||
->setAttribute('antivirus', $antivirus));
|
||||
|
||||
$dbForProject->updateCollection('bucket_' . $bucket->getInternalId(), $permissions, $fileSecurity);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('bucketId', $bucket->getId())
|
||||
;
|
||||
->setParam('bucketId', $bucket->getId());
|
||||
|
||||
$response->dynamic($bucket, Response::MODEL_BUCKET);
|
||||
});
|
||||
|
|
@ -342,7 +340,7 @@ App::delete('/v1/storage/buckets/:bucketId')
|
|||
});
|
||||
|
||||
App::post('/v1/storage/buckets/:bucketId/files')
|
||||
->alias('/v1/storage/files', ['bucketId' => 'default'])
|
||||
->alias('/v1/storage/files')
|
||||
->desc('Create file')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.write')
|
||||
|
|
@ -670,7 +668,11 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
'metadata' => $metadata,
|
||||
]);
|
||||
|
||||
$file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc);
|
||||
try {
|
||||
$file = $dbForProject->createDocument('bucket_' . $bucket->getInternalId(), $doc);
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
} else {
|
||||
$file = $file
|
||||
->setAttribute('chunksUploaded', $chunksUploaded)
|
||||
|
|
@ -686,15 +688,19 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
if (!$validator->isValid($bucket->getCreate())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
|
||||
try {
|
||||
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setParam('bucketId', $bucket->getId())
|
||||
->setParam('fileId', $file->getId())
|
||||
->setContext('bucket', $bucket)
|
||||
;
|
||||
->setContext('bucket', $bucket);
|
||||
|
||||
$metadata = null; // was causing leaks as it was passed by reference
|
||||
|
||||
|
|
@ -704,7 +710,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
|||
});
|
||||
|
||||
App::get('/v1/storage/buckets/:bucketId/files')
|
||||
->alias('/v1/storage/files', ['bucketId' => 'default'])
|
||||
->alias('/v1/storage/files')
|
||||
->desc('List files')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
|
|
@ -739,11 +745,7 @@ App::get('/v1/storage/buckets/:bucketId/files')
|
|||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
$queries = Query::parseQueries($queries);
|
||||
|
||||
if (!empty($search)) {
|
||||
$queries[] = Query::search('search', $search);
|
||||
|
|
@ -781,12 +783,16 @@ App::get('/v1/storage/buckets/:bucketId/files')
|
|||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
if ($fileSecurity && !$valid) {
|
||||
$files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries);
|
||||
$total = $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT);
|
||||
} else {
|
||||
$files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries));
|
||||
$total = Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT));
|
||||
try {
|
||||
if ($fileSecurity && !$valid) {
|
||||
$files = $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries);
|
||||
$total = $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT);
|
||||
} else {
|
||||
$files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getInternalId(), $queries));
|
||||
$total = Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getInternalId(), $filterQueries, APP_LIMIT_COUNT));
|
||||
}
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
|
|
@ -796,7 +802,7 @@ App::get('/v1/storage/buckets/:bucketId/files')
|
|||
});
|
||||
|
||||
App::get('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
->alias('/v1/storage/files/:fileId', ['bucketId' => 'default'])
|
||||
->alias('/v1/storage/files/:fileId')
|
||||
->desc('Get file')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
|
|
@ -844,7 +850,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
|
|||
});
|
||||
|
||||
App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||
->alias('/v1/storage/files/:fileId/preview', ['bucketId' => 'default'])
|
||||
->alias('/v1/storage/files/:fileId/preview')
|
||||
->desc('Get file preview')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
|
|
@ -1017,7 +1023,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
|||
});
|
||||
|
||||
App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||
->alias('/v1/storage/files/:fileId/download', ['bucketId' => 'default'])
|
||||
->alias('/v1/storage/files/:fileId/download')
|
||||
->desc('Get file for download')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
|
|
@ -1158,7 +1164,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
|||
});
|
||||
|
||||
App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||
->alias('/v1/storage/files/:fileId/view', ['bucketId' => 'default'])
|
||||
->alias('/v1/storage/files/:fileId/view')
|
||||
->desc('Get file for view')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.read')
|
||||
|
|
@ -1465,7 +1471,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
|
|||
});
|
||||
|
||||
App::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
->alias('/v1/storage/files/:fileId', ['bucketId' => 'default'])
|
||||
->alias('/v1/storage/files/:fileId')
|
||||
->desc('Update file')
|
||||
->groups(['api', 'storage'])
|
||||
->label('scope', 'files.write')
|
||||
|
|
@ -1555,10 +1561,14 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
|||
$file->setAttribute('name', $name);
|
||||
}
|
||||
|
||||
if ($fileSecurity && !$valid) {
|
||||
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
|
||||
} else {
|
||||
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
try {
|
||||
if ($fileSecurity && !$valid) {
|
||||
$file = $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file);
|
||||
} else {
|
||||
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getInternalId(), $fileId, $file));
|
||||
}
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
|
|
@ -1641,10 +1651,14 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
|||
->setResource('file/' . $fileId)
|
||||
;
|
||||
|
||||
if ($fileSecurity && !$valid) {
|
||||
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} else {
|
||||
$deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
try {
|
||||
if ($fileSecurity && !$valid) {
|
||||
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId);
|
||||
} else {
|
||||
$deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getInternalId(), $fileId));
|
||||
}
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$deleted) {
|
||||
|
|
|
|||
|
|
@ -727,9 +727,9 @@ App::get('/v1/teams/:teamId/memberships')
|
|||
->param('queries', [], new Memberships(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Memberships::ALLOWED_ATTRIBUTES), true)
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $teamId, array $queries, string $search, Response $response, Database $dbForProject) {
|
||||
|
||||
->action(function (string $teamId, array $queries, string $search, Response $response, Document $project, Database $dbForProject) {
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
|
|
@ -790,27 +790,48 @@ App::get('/v1/teams/:teamId/memberships')
|
|||
|
||||
$memberships = array_filter($memberships, fn (Document $membership) => !empty($membership->getAttribute('userId')));
|
||||
|
||||
$memberships = array_map(function ($membership) use ($dbForProject, $team) {
|
||||
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
|
||||
$membershipsPrivacy = [
|
||||
'userName' => $project->getAttribute('auths', [])['membershipsUserName'] ?? true,
|
||||
'userEmail' => $project->getAttribute('auths', [])['membershipsUserEmail'] ?? true,
|
||||
'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true,
|
||||
];
|
||||
|
||||
$mfa = $user->getAttribute('mfa', false);
|
||||
if ($mfa) {
|
||||
$totp = TOTP::getAuthenticatorFromUser($user);
|
||||
$totpEnabled = $totp && $totp->getAttribute('verified', false);
|
||||
$emailEnabled = $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false);
|
||||
$phoneEnabled = $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false);
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
if (!$totpEnabled && !$emailEnabled && !$phoneEnabled) {
|
||||
$mfa = false;
|
||||
$membershipsPrivacy = array_map(function ($privacy) use ($isPrivilegedUser, $isAppUser) {
|
||||
return $privacy || $isPrivilegedUser || $isAppUser;
|
||||
}, $membershipsPrivacy);
|
||||
|
||||
$memberships = array_map(function ($membership) use ($dbForProject, $team, $membershipsPrivacy) {
|
||||
if ($membershipsPrivacy['mfa']) {
|
||||
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
|
||||
$mfa = $user->getAttribute('mfa', false);
|
||||
|
||||
if ($mfa) {
|
||||
$totp = TOTP::getAuthenticatorFromUser($user);
|
||||
$totpEnabled = $totp && $totp->getAttribute('verified', false);
|
||||
$emailEnabled = $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false);
|
||||
$phoneEnabled = $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false);
|
||||
|
||||
if (!$totpEnabled && !$emailEnabled && !$phoneEnabled) {
|
||||
$mfa = false;
|
||||
}
|
||||
}
|
||||
|
||||
$membership->setAttribute('mfa', $mfa);
|
||||
}
|
||||
|
||||
$membership
|
||||
->setAttribute('mfa', $mfa)
|
||||
->setAttribute('teamName', $team->getAttribute('name'))
|
||||
->setAttribute('userName', $user->getAttribute('name'))
|
||||
->setAttribute('userEmail', $user->getAttribute('email'))
|
||||
;
|
||||
if ($membershipsPrivacy['userName']) {
|
||||
$membership->setAttribute('userName', $user->getAttribute('name'));
|
||||
}
|
||||
|
||||
if ($membershipsPrivacy['userEmail']) {
|
||||
$membership->setAttribute('userEmail', $user->getAttribute('email'));
|
||||
}
|
||||
|
||||
$membership->setAttribute('teamName', $team->getAttribute('name'));
|
||||
|
||||
return $membership;
|
||||
}, $memberships);
|
||||
|
|
@ -837,8 +858,9 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
|
|||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('membershipId', '', new UID(), 'Membership ID.')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $teamId, string $membershipId, Response $response, Database $dbForProject) {
|
||||
->action(function (string $teamId, string $membershipId, Response $response, Document $project, Database $dbForProject) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
|
|
@ -852,27 +874,48 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
|
|||
throw new Exception(Exception::MEMBERSHIP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
|
||||
$membershipsPrivacy = [
|
||||
'userName' => $project->getAttribute('auths', [])['membershipsUserName'] ?? true,
|
||||
'userEmail' => $project->getAttribute('auths', [])['membershipsUserEmail'] ?? true,
|
||||
'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true,
|
||||
];
|
||||
|
||||
$mfa = $user->getAttribute('mfa', false);
|
||||
$roles = Authorization::getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
if ($mfa) {
|
||||
$totp = TOTP::getAuthenticatorFromUser($user);
|
||||
$totpEnabled = $totp && $totp->getAttribute('verified', false);
|
||||
$emailEnabled = $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false);
|
||||
$phoneEnabled = $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false);
|
||||
$membershipsPrivacy = array_map(function ($privacy) use ($isPrivilegedUser, $isAppUser) {
|
||||
return $privacy || $isPrivilegedUser || $isAppUser;
|
||||
}, $membershipsPrivacy);
|
||||
|
||||
if (!$totpEnabled && !$emailEnabled && !$phoneEnabled) {
|
||||
$mfa = false;
|
||||
if ($membershipsPrivacy['mfa']) {
|
||||
$user = $dbForProject->getDocument('users', $membership->getAttribute('userId'));
|
||||
|
||||
$mfa = $user->getAttribute('mfa', false);
|
||||
|
||||
if ($mfa) {
|
||||
$totp = TOTP::getAuthenticatorFromUser($user);
|
||||
$totpEnabled = $totp && $totp->getAttribute('verified', false);
|
||||
$emailEnabled = $user->getAttribute('email', false) && $user->getAttribute('emailVerification', false);
|
||||
$phoneEnabled = $user->getAttribute('phone', false) && $user->getAttribute('phoneVerification', false);
|
||||
|
||||
if (!$totpEnabled && !$emailEnabled && !$phoneEnabled) {
|
||||
$mfa = false;
|
||||
}
|
||||
}
|
||||
|
||||
$membership->setAttribute('mfa', $mfa);
|
||||
}
|
||||
|
||||
$membership
|
||||
->setAttribute('mfa', $mfa)
|
||||
->setAttribute('teamName', $team->getAttribute('name'))
|
||||
->setAttribute('userName', $user->getAttribute('name'))
|
||||
->setAttribute('userEmail', $user->getAttribute('email'))
|
||||
;
|
||||
if ($membershipsPrivacy['userName']) {
|
||||
$membership->setAttribute('userName', $user->getAttribute('name'));
|
||||
}
|
||||
|
||||
if ($membershipsPrivacy['userEmail']) {
|
||||
$membership->setAttribute('userEmail', $user->getAttribute('email'));
|
||||
}
|
||||
|
||||
$membership->setAttribute('teamName', $team->getAttribute('name'));
|
||||
|
||||
$response->dynamic($membership, Response::MODEL_MEMBERSHIP);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ use Utopia\Validator\Text;
|
|||
use Utopia\Validator\WhiteList;
|
||||
|
||||
/** TODO: Remove function when we move to using utopia/platform */
|
||||
function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks): Document
|
||||
function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Hooks $hooks): Document
|
||||
{
|
||||
$plaintextPassword = $password;
|
||||
$hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array
|
||||
|
|
@ -176,15 +176,12 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
|||
throw new Exception(Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$queueForEvents->setParam('userId', $user->getId());
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
App::post('/v1/users')
|
||||
->desc('Create user')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create')
|
||||
->label('scope', 'users.write')
|
||||
->label('audits.event', 'user.create')
|
||||
->label('audits.resource', 'user/{response.$id}')
|
||||
|
|
@ -203,10 +200,9 @@ App::post('/v1/users')
|
|||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
|
||||
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $queueForEvents, $hooks);
|
||||
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks);
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($user, Response::MODEL_USER);
|
||||
|
|
@ -215,7 +211,6 @@ App::post('/v1/users')
|
|||
App::post('/v1/users/bcrypt')
|
||||
->desc('Create user with bcrypt password')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create')
|
||||
->label('scope', 'users.write')
|
||||
->label('audits.event', 'user.create')
|
||||
->label('audits.resource', 'user/{response.$id}')
|
||||
|
|
@ -233,10 +228,9 @@ App::post('/v1/users/bcrypt')
|
|||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
|
||||
$user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks);
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
$user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
|
|
@ -246,7 +240,6 @@ App::post('/v1/users/bcrypt')
|
|||
App::post('/v1/users/md5')
|
||||
->desc('Create user with MD5 password')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create')
|
||||
->label('scope', 'users.write')
|
||||
->label('audits.event', 'user.create')
|
||||
->label('audits.resource', 'user/{response.$id}')
|
||||
|
|
@ -264,10 +257,9 @@ App::post('/v1/users/md5')
|
|||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
|
||||
$user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks);
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
$user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
|
|
@ -277,7 +269,6 @@ App::post('/v1/users/md5')
|
|||
App::post('/v1/users/argon2')
|
||||
->desc('Create user with Argon2 password')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create')
|
||||
->label('scope', 'users.write')
|
||||
->label('audits.event', 'user.create')
|
||||
->label('audits.resource', 'user/{response.$id}')
|
||||
|
|
@ -295,10 +286,9 @@ App::post('/v1/users/argon2')
|
|||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
|
||||
$user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks);
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
$user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
|
|
@ -308,7 +298,6 @@ App::post('/v1/users/argon2')
|
|||
App::post('/v1/users/sha')
|
||||
->desc('Create user with SHA password')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create')
|
||||
->label('scope', 'users.write')
|
||||
->label('audits.event', 'user.create')
|
||||
->label('audits.resource', 'user/{response.$id}')
|
||||
|
|
@ -327,16 +316,15 @@ App::post('/v1/users/sha')
|
|||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
|
||||
->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
$options = '{}';
|
||||
|
||||
if (!empty($passwordVersion)) {
|
||||
$options = '{"version":"' . $passwordVersion . '"}';
|
||||
}
|
||||
|
||||
$user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks);
|
||||
$user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
|
|
@ -346,7 +334,6 @@ App::post('/v1/users/sha')
|
|||
App::post('/v1/users/phpass')
|
||||
->desc('Create user with PHPass password')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create')
|
||||
->label('scope', 'users.write')
|
||||
->label('audits.event', 'user.create')
|
||||
->label('audits.resource', 'user/{response.$id}')
|
||||
|
|
@ -364,10 +351,9 @@ App::post('/v1/users/phpass')
|
|||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
|
||||
$user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks);
|
||||
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
$user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
|
|
@ -377,7 +363,6 @@ App::post('/v1/users/phpass')
|
|||
App::post('/v1/users/scrypt')
|
||||
->desc('Create user with Scrypt password')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create')
|
||||
->label('scope', 'users.write')
|
||||
->label('audits.event', 'user.create')
|
||||
->label('audits.resource', 'user/{response.$id}')
|
||||
|
|
@ -400,9 +385,8 @@ App::post('/v1/users/scrypt')
|
|||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
|
||||
->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
$options = [
|
||||
'salt' => $passwordSalt,
|
||||
'costCpu' => $passwordCpu,
|
||||
|
|
@ -411,7 +395,7 @@ App::post('/v1/users/scrypt')
|
|||
'length' => $passwordLength
|
||||
];
|
||||
|
||||
$user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks);
|
||||
$user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
|
|
@ -421,7 +405,6 @@ App::post('/v1/users/scrypt')
|
|||
App::post('/v1/users/scrypt-modified')
|
||||
->desc('Create user with Scrypt modified password')
|
||||
->groups(['api', 'users'])
|
||||
->label('event', 'users.[userId].create')
|
||||
->label('scope', 'users.write')
|
||||
->label('audits.event', 'user.create')
|
||||
->label('audits.resource', 'user/{response.$id}')
|
||||
|
|
@ -442,10 +425,9 @@ App::post('/v1/users/scrypt-modified')
|
|||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Event $queueForEvents, Hooks $hooks) {
|
||||
$user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $queueForEvents, $hooks);
|
||||
->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
$user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
|
|
|
|||
|
|
@ -775,6 +775,8 @@ App::error()
|
|||
case 'Utopia\Database\Exception\Relationship':
|
||||
$error = new AppwriteException(AppwriteException::RELATIONSHIP_VALUE_INVALID, $error->getMessage(), previous: $error);
|
||||
break;
|
||||
case 'Utopia\Database\Exception\NotFound':
|
||||
$error = new AppwriteException(AppwriteException::COLLECTION_NOT_FOUND, $error->getMessage(), previous: $error);
|
||||
}
|
||||
|
||||
$code = $error->getCode();
|
||||
|
|
|
|||
|
|
@ -11,10 +11,11 @@ use Appwrite\Event\Delete;
|
|||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\Usage;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Messaging\Adapter\Realtime;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Abuse\Abuse;
|
||||
|
|
@ -28,6 +29,7 @@ use Utopia\Database\DateTime;
|
|||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Queue\Connection;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
|
|
@ -57,8 +59,36 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar
|
|||
return $label;
|
||||
};
|
||||
|
||||
$databaseListener = function (string $event, Document $document, Document $project, Usage $queueForUsage, Database $dbForProject) {
|
||||
$eventDatabaseListener = function (Document $document, Response $response, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime) {
|
||||
// Only trigger events for user creation with the database listener.
|
||||
if ($document->getCollection() !== 'users') {
|
||||
return;
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
->setEvent('users.[userId].create')
|
||||
->setParam('userId', $document->getId())
|
||||
->setPayload($response->output($document, Response::MODEL_USER));
|
||||
|
||||
// Trigger functions, webhooks, and realtime events
|
||||
$queueForFunctions
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
|
||||
$queueForWebhooks
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
|
||||
if ($queueForEvents->getProject()->getId() === 'console') {
|
||||
return;
|
||||
}
|
||||
|
||||
$queueForRealtime
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
};
|
||||
|
||||
$usageDatabaseListener = function (string $event, Document $document, Usage $queueForUsage) {
|
||||
$value = 1;
|
||||
if ($event === Database::EVENT_DOCUMENT_DELETE) {
|
||||
$value = -1;
|
||||
|
|
@ -353,6 +383,7 @@ App::init()
|
|||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('queue')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMessaging')
|
||||
->inject('queueForAudits')
|
||||
|
|
@ -362,7 +393,7 @@ App::init()
|
|||
->inject('queueForUsage')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode) use ($databaseListener) {
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Connection $queue, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Usage $queueForUsage, Database $dbForProject, string $mode) use ($usageDatabaseListener, $eventDatabaseListener) {
|
||||
|
||||
$route = $utopia->getRoute();
|
||||
|
||||
|
|
@ -456,9 +487,24 @@ App::init()
|
|||
$queueForBuilds->setProject($project);
|
||||
$queueForMessaging->setProject($project);
|
||||
|
||||
// Clone the queues, to prevent events triggered by the database listener
|
||||
// from overwriting the events that are supposed to be triggered in the shutdown hook.
|
||||
$queueForEventsClone = new Event($queue);
|
||||
$queueForFunctions = new Func($queue);
|
||||
$queueForWebhooks = new Webhook($queue);
|
||||
$queueForRealtime = new Realtime();
|
||||
|
||||
$dbForProject
|
||||
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $databaseListener($event, $document, $project, $queueForUsage, $dbForProject))
|
||||
->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $databaseListener($event, $document, $project, $queueForUsage, $dbForProject));
|
||||
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage))
|
||||
->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage))
|
||||
->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener(
|
||||
$document,
|
||||
$response,
|
||||
$queueForEventsClone->from($queueForEvents),
|
||||
$queueForFunctions->from($queueForEvents),
|
||||
$queueForWebhooks->from($queueForEvents),
|
||||
$queueForRealtime->from($queueForEvents)
|
||||
));
|
||||
|
||||
$useCache = $route->getLabel('cache', false);
|
||||
if ($useCache) {
|
||||
|
|
@ -591,11 +637,13 @@ App::shutdown()
|
|||
->inject('queueForDatabase')
|
||||
->inject('queueForBuilds')
|
||||
->inject('queueForMessaging')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForWebhooks')
|
||||
->inject('queueForRealtime')
|
||||
->inject('dbForProject')
|
||||
->inject('mode')
|
||||
->inject('dbForConsole')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Usage $queueForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Database $dbForProject, Func $queueForFunctions, string $mode, Database $dbForConsole) use ($parseLabel) {
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, Usage $queueForUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, string $mode, Database $dbForConsole) use ($parseLabel) {
|
||||
|
||||
$responsePayload = $response->getPayload();
|
||||
|
||||
|
|
@ -604,54 +652,18 @@ App::shutdown()
|
|||
$queueForEvents->setPayload($responsePayload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger functions.
|
||||
*/
|
||||
if (!$queueForEvents->isPaused()) {
|
||||
$queueForFunctions
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
}
|
||||
/**
|
||||
* Trigger webhooks.
|
||||
*/
|
||||
$queueForEvents
|
||||
->setClass(Event::WEBHOOK_CLASS_NAME)
|
||||
->setQueue(Event::WEBHOOK_QUEUE_NAME)
|
||||
$queueForWebhooks
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
|
||||
$queueForFunctions
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
|
||||
/**
|
||||
* Trigger realtime.
|
||||
*/
|
||||
if ($project->getId() !== 'console') {
|
||||
$allEvents = Event::generateEvents($queueForEvents->getEvent(), $queueForEvents->getParams());
|
||||
$payload = new Document($queueForEvents->getPayload());
|
||||
|
||||
$db = $queueForEvents->getContext('database');
|
||||
$collection = $queueForEvents->getContext('collection');
|
||||
$bucket = $queueForEvents->getContext('bucket');
|
||||
|
||||
$target = Realtime::fromPayload(
|
||||
// Pass first, most verbose event pattern
|
||||
event: $allEvents[0],
|
||||
payload: $payload,
|
||||
project: $project,
|
||||
database: $db,
|
||||
collection: $collection,
|
||||
bucket: $bucket,
|
||||
);
|
||||
|
||||
Realtime::send(
|
||||
projectId: $target['projectId'] ?? $project->getId(),
|
||||
payload: $queueForEvents->getRealtimePayload(),
|
||||
events: $allEvents,
|
||||
channels: $target['channels'],
|
||||
roles: $target['roles'],
|
||||
options: [
|
||||
'permissionsChanged' => $target['permissionsChanged'],
|
||||
'userId' => $queueForEvents->getParam('userId')
|
||||
]
|
||||
);
|
||||
$queueForRealtime
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
14
app/init.php
14
app/init.php
|
|
@ -31,7 +31,9 @@ use Appwrite\Event\Func;
|
|||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Migration;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\Usage;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Functions\Specification;
|
||||
use Appwrite\GraphQL\Promises\Adapter\Swoole;
|
||||
|
|
@ -40,6 +42,7 @@ use Appwrite\Hooks\Hooks;
|
|||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\PubSub\Adapter\Redis as PubSub;
|
||||
use Appwrite\URL\URL as AppwriteURL;
|
||||
use Appwrite\Utopia\Request;
|
||||
use MaxMind\Db\Reader;
|
||||
|
|
@ -971,7 +974,10 @@ $register->set('pools', function () {
|
|||
$adapter->setDatabase($dsn->getPath());
|
||||
break;
|
||||
case 'pubsub':
|
||||
$adapter = $resource();
|
||||
$adapter = match ($dsn->getScheme()) {
|
||||
'redis' => new PubSub($resource()),
|
||||
default => null
|
||||
};
|
||||
break;
|
||||
case 'queue':
|
||||
$adapter = match ($dsn->getScheme()) {
|
||||
|
|
@ -1134,6 +1140,12 @@ App::setResource('queueForDeletes', function (Connection $queue) {
|
|||
App::setResource('queueForEvents', function (Connection $queue) {
|
||||
return new Event($queue);
|
||||
}, ['queue']);
|
||||
App::setResource('queueForWebhooks', function (Connection $queue) {
|
||||
return new Webhook($queue);
|
||||
}, ['queue']);
|
||||
App::setResource('queueForRealtime', function () {
|
||||
return new Realtime();
|
||||
}, []);
|
||||
App::setResource('queueForAudits', function (Connection $queue) {
|
||||
return new Audit($queue);
|
||||
}, ['queue']);
|
||||
|
|
|
|||
|
|
@ -367,17 +367,16 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
|||
}
|
||||
$start = time();
|
||||
|
||||
$redis = $register->get('pools')->get('pubsub')->pop()->getResource(); /** @var Redis $redis */
|
||||
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1);
|
||||
|
||||
if ($redis->ping(true)) {
|
||||
/** @var \Appwrite\PubSub\Adapter $pubsub */
|
||||
$pubsub = $register->get('pools')->get('pubsub')->pop()->getResource();
|
||||
if ($pubsub->ping(true)) {
|
||||
$attempts = 0;
|
||||
Console::success('Pub/sub connection established (worker: ' . $workerId . ')');
|
||||
} else {
|
||||
Console::error('Pub/sub failed (worker: ' . $workerId . ')');
|
||||
}
|
||||
|
||||
$redis->subscribe(['realtime'], function (Redis $redis, string $channel, string $payload) use ($server, $workerId, $stats, $register, $realtime) {
|
||||
$pubsub->subscribe(['realtime'], function (mixed $redis, string $channel, string $payload) use ($server, $workerId, $stats, $register, $realtime) {
|
||||
$event = json_decode($payload, true);
|
||||
|
||||
if ($event['permissionsChanged'] && isset($event['userId'])) {
|
||||
|
|
|
|||
|
|
@ -48,10 +48,10 @@
|
|||
"utopia-php/abuse": "0.43.*",
|
||||
"utopia-php/analytics": "0.10.*",
|
||||
"utopia-php/audit": "0.43.*",
|
||||
"utopia-php/cache": "0.10.*",
|
||||
"utopia-php/cache": "0.11.*",
|
||||
"utopia-php/cli": "0.15.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "dev-feat-migrations as 0.53.9",
|
||||
"utopia-php/database": "dev-feat-migrations as 0.53.16",
|
||||
"utopia-php/domains": "0.5.*",
|
||||
"utopia-php/dsn": "0.2.1",
|
||||
"utopia-php/framework": "0.33.*",
|
||||
|
|
|
|||
|
|
@ -1054,4 +1054,4 @@ volumes:
|
|||
appwrite-certificates:
|
||||
appwrite-functions:
|
||||
appwrite-builds:
|
||||
appwrite-config:
|
||||
appwrite-config:
|
||||
|
|
@ -1 +1 @@
|
|||
Get a team member by the membership unique id. All team members have read access for this resource.
|
||||
Get a team member by the membership unique id. All team members have read access for this resource. Hide sensitive attributes from the response by toggling membership privacy in the Console.
|
||||
|
|
@ -1 +1 @@
|
|||
Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint.
|
||||
Use this endpoint to list a team's members using the team's ID. All team members have read access to this endpoint. Hide sensitive attributes from the response by toggling membership privacy in the Console.
|
||||
|
|
@ -204,19 +204,6 @@ class Event
|
|||
return $this->payload;
|
||||
}
|
||||
|
||||
public function getRealtimePayload(): array
|
||||
{
|
||||
$payload = [];
|
||||
|
||||
foreach ($this->payload as $key => $value) {
|
||||
if (!isset($this->sensitive[$key])) {
|
||||
$payload[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set context for this event.
|
||||
*
|
||||
|
|
@ -315,10 +302,6 @@ class Event
|
|||
*/
|
||||
public function trigger(): string|bool
|
||||
{
|
||||
if ($this->paused) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$client = new Client($this->queue, $this->connection);
|
||||
|
||||
return $client->enqueue([
|
||||
|
|
@ -530,20 +513,21 @@ class Event
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the value of paused
|
||||
* Generate a function event from a base event
|
||||
*
|
||||
* @param Event $event
|
||||
*
|
||||
* @return self
|
||||
*
|
||||
*/
|
||||
public function isPaused(): bool
|
||||
public function from(Event $event): self
|
||||
{
|
||||
return $this->paused;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the value of paused
|
||||
*/
|
||||
public function setPaused(bool $paused): self
|
||||
{
|
||||
$this->paused = $paused;
|
||||
|
||||
$this->project = $event->getProject();
|
||||
$this->user = $event->getUser();
|
||||
$this->payload = $event->getPayload();
|
||||
$this->event = $event->getEvent();
|
||||
$this->params = $event->getParams();
|
||||
$this->context = $event->context;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -213,10 +213,6 @@ class Func extends Event
|
|||
*/
|
||||
public function trigger(): string|bool
|
||||
{
|
||||
if ($this->paused) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$client = new Client($this->queue, $this->connection);
|
||||
|
||||
$events = $this->getEvent() ? Event::generateEvents($this->getEvent(), $this->getParams()) : null;
|
||||
|
|
@ -238,22 +234,4 @@ class Func extends Event
|
|||
'method' => $this->method,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a function event from a base event
|
||||
*
|
||||
* @param Event $event
|
||||
*
|
||||
* @return self
|
||||
*
|
||||
*/
|
||||
public function from(Event $event): self
|
||||
{
|
||||
$this->project = $event->getProject();
|
||||
$this->user = $event->getUser();
|
||||
$this->payload = $event->getPayload();
|
||||
$this->event = $event->getEvent();
|
||||
$this->params = $event->getParams();
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
70
src/Appwrite/Event/Realtime.php
Normal file
70
src/Appwrite/Event/Realtime.php
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Appwrite\Messaging\Adapter\Realtime as RealtimeAdapter;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class Realtime extends Event
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function getRealtimePayload(): array
|
||||
{
|
||||
$payload = [];
|
||||
|
||||
foreach ($this->payload as $key => $value) {
|
||||
if (!isset($this->sensitive[$key])) {
|
||||
$payload[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute Event.
|
||||
*
|
||||
* @return string|bool
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function trigger(): string|bool
|
||||
{
|
||||
if (empty($this->event)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$allEvents = Event::generateEvents($this->getEvent(), $this->getParams());
|
||||
$payload = new Document($this->getPayload());
|
||||
|
||||
$db = $this->getContext('database');
|
||||
$collection = $this->getContext('collection');
|
||||
$bucket = $this->getContext('bucket');
|
||||
|
||||
$target = RealtimeAdapter::fromPayload(
|
||||
// Pass first, most verbose event pattern
|
||||
event: $allEvents[0],
|
||||
payload: $payload,
|
||||
project: $this->getProject(),
|
||||
database: $db,
|
||||
collection: $collection,
|
||||
bucket: $bucket,
|
||||
);
|
||||
|
||||
RealtimeAdapter::send(
|
||||
projectId: $target['projectId'] ?? $this->getProject()->getId(),
|
||||
payload: $this->getRealtimePayload(),
|
||||
events: $allEvents,
|
||||
channels: $target['channels'],
|
||||
roles: $target['roles'],
|
||||
options: [
|
||||
'permissionsChanged' => $target['permissionsChanged'],
|
||||
'userId' => $this->getParam('userId')
|
||||
]
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
17
src/Appwrite/Event/Webhook.php
Normal file
17
src/Appwrite/Event/Webhook.php
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Queue\Connection;
|
||||
|
||||
class Webhook extends Event
|
||||
{
|
||||
public function __construct(protected Connection $connection)
|
||||
{
|
||||
parent::__construct($connection);
|
||||
|
||||
$this
|
||||
->setQueue(Event::WEBHOOK_QUEUE_NAME)
|
||||
->setClass(Event::WEBHOOK_CLASS_NAME);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ use Utopia\Database\DateTime;
|
|||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Realtime extends Adapter
|
||||
{
|
||||
|
|
@ -139,20 +138,26 @@ class Realtime extends Adapter
|
|||
$permissionsChanged = array_key_exists('permissionsChanged', $options) && $options['permissionsChanged'];
|
||||
$userId = array_key_exists('userId', $options) ? $options['userId'] : null;
|
||||
|
||||
$redis = new \Redis(); //TODO: make this part of the constructor
|
||||
$redis->connect(System::getEnv('_APP_REDIS_HOST', ''), System::getEnv('_APP_REDIS_PORT', ''));
|
||||
$redis->publish('realtime', json_encode([
|
||||
'project' => $projectId,
|
||||
'roles' => $roles,
|
||||
'permissionsChanged' => $permissionsChanged,
|
||||
'userId' => $userId,
|
||||
'data' => [
|
||||
'events' => $events,
|
||||
'channels' => $channels,
|
||||
'timestamp' => DateTime::formatTz(DateTime::now()),
|
||||
'payload' => $payload
|
||||
]
|
||||
]));
|
||||
global $register;
|
||||
$pubsub = $register->get('pools')->get('pubsub')->pop();
|
||||
try {
|
||||
/** @var \Appwrite\PubSub\Adapter $redis */
|
||||
$redis = $pubsub->getResource();
|
||||
$redis->publish('realtime', json_encode([
|
||||
'project' => $projectId,
|
||||
'roles' => $roles,
|
||||
'permissionsChanged' => $permissionsChanged,
|
||||
'userId' => $userId,
|
||||
'data' => [
|
||||
'events' => $events,
|
||||
'channels' => $channels,
|
||||
'timestamp' => DateTime::formatTz(DateTime::now()),
|
||||
'payload' => $payload
|
||||
]
|
||||
]));
|
||||
} finally {
|
||||
$pubsub->reclaim();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\ClamAV\Network;
|
||||
use Appwrite\PubSub\Adapter;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
|
|
@ -158,6 +159,7 @@ class Doctor extends Action
|
|||
foreach ($configs as $key => $config) {
|
||||
foreach ($config as $pool) {
|
||||
try {
|
||||
/** @var Adapter $adapter */
|
||||
$adapter = $pools->get($pool)->pop()->getResource();
|
||||
|
||||
if ($adapter->ping()) {
|
||||
|
|
|
|||
13
src/Appwrite/PubSub/Adapter.php
Normal file
13
src/Appwrite/PubSub/Adapter.php
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\PubSub;
|
||||
|
||||
interface Adapter
|
||||
{
|
||||
public function ping($message = null): bool;
|
||||
|
||||
public function subscribe($channels, $callback);
|
||||
|
||||
public function publish($channel, $message);
|
||||
|
||||
}
|
||||
31
src/Appwrite/PubSub/Adapter/Redis.php
Normal file
31
src/Appwrite/PubSub/Adapter/Redis.php
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Appwrite\PubSub\Adapter;
|
||||
|
||||
use Appwrite\PubSub\Adapter;
|
||||
|
||||
class Redis implements Adapter
|
||||
{
|
||||
private \Redis $client;
|
||||
|
||||
public function __construct(\Redis $client)
|
||||
{
|
||||
$this->client = $client;
|
||||
|
||||
}
|
||||
|
||||
public function ping($message = null): bool
|
||||
{
|
||||
return $this->client->ping($message);
|
||||
}
|
||||
|
||||
public function subscribe($channels, $callback)
|
||||
{
|
||||
return $this->client->subscribe($channels, $callback);
|
||||
}
|
||||
|
||||
public function publish($channel, $message)
|
||||
{
|
||||
return $this->client->publish($channel, $message);
|
||||
}
|
||||
}
|
||||
|
|
@ -36,13 +36,13 @@ class Membership extends Model
|
|||
])
|
||||
->addRule('userName', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'User name.',
|
||||
'description' => 'User name. Hide this attribute by toggling membership privacy in the Console.',
|
||||
'default' => '',
|
||||
'example' => 'John Doe',
|
||||
])
|
||||
->addRule('userEmail', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'User email address.',
|
||||
'description' => 'User email address. Hide this attribute by toggling membership privacy in the Console.',
|
||||
'default' => '',
|
||||
'example' => 'john@appwrite.io',
|
||||
])
|
||||
|
|
@ -78,7 +78,7 @@ class Membership extends Model
|
|||
])
|
||||
->addRule('mfa', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Multi factor authentication status, true if the user has MFA enabled or false otherwise.',
|
||||
'description' => 'Multi factor authentication status, true if the user has MFA enabled or false otherwise. Hide this attribute by toggling membership privacy in the Console.',
|
||||
'default' => false,
|
||||
'example' => false,
|
||||
])
|
||||
|
|
|
|||
|
|
@ -151,6 +151,24 @@ class Project extends Model
|
|||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('authMembershipsUserName', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Whether or not to show user names in the teams membership response.',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('authMembershipsUserEmail', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Whether or not to show user emails in the teams membership response.',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('authMembershipsMfa', [
|
||||
'type' => self::TYPE_BOOLEAN,
|
||||
'description' => 'Whether or not to show user MFA status in the teams membership response.',
|
||||
'default' => false,
|
||||
'example' => true,
|
||||
])
|
||||
->addRule('oAuthProviders', [
|
||||
'type' => Response::MODEL_AUTH_PROVIDER,
|
||||
'description' => 'List of Auth Providers.',
|
||||
|
|
@ -348,6 +366,9 @@ class Project extends Model
|
|||
$document->setAttribute('authPersonalDataCheck', $authValues['personalDataCheck'] ?? false);
|
||||
$document->setAttribute('authMockNumbers', $authValues['mockNumbers'] ?? []);
|
||||
$document->setAttribute('authSessionAlerts', $authValues['sessionAlerts'] ?? false);
|
||||
$document->setAttribute('authMembershipsUserName', $authValues['membershipsUserName'] ?? true);
|
||||
$document->setAttribute('authMembershipsUserEmail', $authValues['membershipsUserEmail'] ?? true);
|
||||
$document->setAttribute('authMembershipsMfa', $authValues['membershipsMfa'] ?? true);
|
||||
|
||||
foreach ($auth as $index => $method) {
|
||||
$key = $method['key'];
|
||||
|
|
|
|||
|
|
@ -30,8 +30,8 @@ trait TeamsBaseClient
|
|||
$this->assertIsInt($response['body']['total']);
|
||||
$this->assertNotEmpty($response['body']['memberships'][0]['$id']);
|
||||
$this->assertFalse($response['body']['memberships'][0]['mfa']);
|
||||
$this->assertEquals($this->getUser()['name'], $response['body']['memberships'][0]['userName']);
|
||||
$this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['userEmail']);
|
||||
$this->assertArrayHasKey('userName', $response['body']['memberships'][0]);
|
||||
$this->assertArrayHasKey('userEmail', $response['body']['memberships'][0]);
|
||||
$this->assertEquals($teamName, $response['body']['memberships'][0]['teamName']);
|
||||
$this->assertContains('owner', $response['body']['memberships'][0]['roles']);
|
||||
$this->assertContains('player', $response['body']['memberships'][0]['roles']);
|
||||
|
|
@ -96,8 +96,8 @@ trait TeamsBaseClient
|
|||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertIsInt($response['body']['total']);
|
||||
$this->assertNotEmpty($response['body']['memberships'][0]);
|
||||
$this->assertEquals($this->getUser()['name'], $response['body']['memberships'][0]['userName']);
|
||||
$this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['userEmail']);
|
||||
$this->assertArrayHasKey('userName', $response['body']['memberships'][0]);
|
||||
$this->assertArrayHasKey('userEmail', $response['body']['memberships'][0]);
|
||||
$this->assertEquals($teamName, $response['body']['memberships'][0]['teamName']);
|
||||
$this->assertContains('owner', $response['body']['memberships'][0]['roles']);
|
||||
$this->assertContains('player', $response['body']['memberships'][0]['roles']);
|
||||
|
|
@ -112,8 +112,8 @@ trait TeamsBaseClient
|
|||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertIsInt($response['body']['total']);
|
||||
$this->assertNotEmpty($response['body']['memberships'][0]);
|
||||
$this->assertEquals($this->getUser()['name'], $response['body']['memberships'][0]['userName']);
|
||||
$this->assertEquals($this->getUser()['email'], $response['body']['memberships'][0]['userEmail']);
|
||||
$this->assertArrayHasKey('userName', $response['body']['memberships'][0]);
|
||||
$this->assertArrayHasKey('userEmail', $response['body']['memberships'][0]);
|
||||
$this->assertEquals($teamName, $response['body']['memberships'][0]['teamName']);
|
||||
$this->assertContains('owner', $response['body']['memberships'][0]['roles']);
|
||||
$this->assertContains('player', $response['body']['memberships'][0]['roles']);
|
||||
|
|
@ -157,8 +157,8 @@ trait TeamsBaseClient
|
|||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertFalse($response['body']['mfa']);
|
||||
$this->assertNotEmpty($response['body']['userId']);
|
||||
$this->assertNotEmpty($response['body']['userName']);
|
||||
$this->assertNotEmpty($response['body']['userEmail']);
|
||||
$this->assertArrayHasKey('userName', $response['body']);
|
||||
$this->assertArrayHasKey('userEmail', $response['body']);
|
||||
$this->assertNotEmpty($response['body']['teamId']);
|
||||
$this->assertNotEmpty($response['body']['teamName']);
|
||||
$this->assertCount(1, $response['body']['roles']);
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ trait TeamsBaseServer
|
|||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
return [];
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -60,6 +60,67 @@ trait TeamsBaseServer
|
|||
$this->assertEquals(true, (new DatetimeValidator())->isValid($response['body']['joined'])); // is null in DB
|
||||
$this->assertEquals(true, $response['body']['confirm']);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/auth/memberships-privacy', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
]), [
|
||||
'userName' => false,
|
||||
'userEmail' => false,
|
||||
'mfa' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Test that sensitive fields are not hidden, as we are on console
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertIsInt($response['body']['total']);
|
||||
$this->assertNotEmpty($response['body']['memberships'][0]['$id']);
|
||||
|
||||
// Assert that sensitive fields are present
|
||||
$this->assertNotEmpty($response['body']['memberships'][0]['userName']);
|
||||
$this->assertNotEmpty($response['body']['memberships'][0]['userEmail']);
|
||||
$this->assertArrayHasKey('mfa', $response['body']['memberships'][0]);
|
||||
|
||||
/**
|
||||
* Update project settings to show sensitive fields
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $this->getProject()['$id'] . '/auth/memberships-privacy', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
]), [
|
||||
'userName' => true,
|
||||
'userEmail' => true,
|
||||
'mfa' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Test that sensitive fields are shown
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertIsInt($response['body']['total']);
|
||||
$this->assertNotEmpty($response['body']['memberships'][0]['$id']);
|
||||
|
||||
// Assert that sensitive fields are present
|
||||
$this->assertNotEmpty($response['body']['memberships'][0]['userName']);
|
||||
$this->assertNotEmpty($response['body']['memberships'][0]['userEmail']);
|
||||
$this->assertArrayHasKey('mfa', $response['body']['memberships'][0]);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -14,6 +14,77 @@ class TeamsCustomClientTest extends Scope
|
|||
use ProjectCustom;
|
||||
use SideClient;
|
||||
|
||||
/**
|
||||
* @depends testGetTeamMemberships
|
||||
*/
|
||||
public function testGetMembershipPrivacy($data)
|
||||
{
|
||||
$teamUid = $data['teamUid'] ?? '';
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $projectId . '/auth/memberships-privacy', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
]), [
|
||||
'userName' => false,
|
||||
'userEmail' => false,
|
||||
'mfa' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Test that sensitive fields are hidden
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertIsInt($response['body']['total']);
|
||||
$this->assertNotEmpty($response['body']['memberships'][0]['$id']);
|
||||
|
||||
// Assert that sensitive fields are not present
|
||||
$this->assertEmpty($response['body']['memberships'][0]['userName']);
|
||||
$this->assertEmpty($response['body']['memberships'][0]['userEmail']);
|
||||
$this->assertFalse($response['body']['memberships'][0]['mfa']);
|
||||
|
||||
/**
|
||||
* Update project settings to show sensitive fields
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $projectId . '/auth/memberships-privacy', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => 'console',
|
||||
'cookie' => 'a_session_console=' . $this->getRoot()['session'],
|
||||
]), [
|
||||
'userName' => true,
|
||||
'userEmail' => true,
|
||||
'mfa' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Test that sensitive fields are shown
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_GET, '/teams/' . $teamUid . '/memberships', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertIsInt($response['body']['total']);
|
||||
$this->assertNotEmpty($response['body']['memberships'][0]['$id']);
|
||||
|
||||
// Assert that sensitive fields are present
|
||||
$this->assertNotEmpty($response['body']['memberships'][0]['userName']);
|
||||
$this->assertNotEmpty($response['body']['memberships'][0]['userEmail']);
|
||||
$this->assertArrayHasKey('mfa', $response['body']['memberships'][0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @depends testUpdateTeamMembership
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -901,6 +901,17 @@ trait WebhooksBase
|
|||
$teamId = $data['teamId'] ?? '';
|
||||
$email = uniqid() . 'friend@localhost.test';
|
||||
|
||||
// Create user to ensure team event is triggered after user event
|
||||
$user = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => 'password',
|
||||
'name' => 'Friend User',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Test for SUCCESS
|
||||
*/
|
||||
|
|
@ -909,7 +920,6 @@ trait WebhooksBase
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'email' => $email,
|
||||
'name' => 'Friend User',
|
||||
'roles' => ['admin', 'editor'],
|
||||
'url' => 'http://localhost:5000/join-us#title'
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -3,13 +3,9 @@
|
|||
namespace Tests\Unit\Event;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\URL\URL;
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Queue;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\System\System;
|
||||
|
||||
require_once __DIR__ . '/../../../app/init.php';
|
||||
|
||||
|
|
@ -20,19 +16,8 @@ class EventTest extends TestCase
|
|||
|
||||
public function setUp(): void
|
||||
{
|
||||
$fallbackForRedis = 'redis_main=' . URL::unparse([
|
||||
'scheme' => 'redis',
|
||||
'host' => System::getEnv('_APP_REDIS_HOST', 'redis'),
|
||||
'port' => System::getEnv('_APP_REDIS_PORT', '6379'),
|
||||
'user' => System::getEnv('_APP_REDIS_USER', ''),
|
||||
'pass' => System::getEnv('_APP_REDIS_PASS', ''),
|
||||
]);
|
||||
|
||||
$dsn = System::getEnv('_APP_CONNECTIONS_QUEUE', $fallbackForRedis);
|
||||
$dsn = explode('=', $dsn);
|
||||
$dsn = $dsn[1] ?? '';
|
||||
$dsn = new DSN($dsn);
|
||||
$connection = new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort());
|
||||
global $register;
|
||||
$connection = $register->get('pools')->get('queue')->pop()->getResource();
|
||||
$this->queue = 'v1-tests' . uniqid();
|
||||
$this->object = new Event($connection);
|
||||
$this->object->setClass('TestsV1');
|
||||
|
|
|
|||
|
|
@ -2,13 +2,9 @@
|
|||
|
||||
namespace Tests\Unit\Usage;
|
||||
|
||||
use Appwrite\URL\URL as AppwriteURL;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Queue;
|
||||
use Utopia\Queue\Client;
|
||||
use Utopia\Queue\Connection;
|
||||
use Utopia\System\System;
|
||||
|
||||
class StatsTest extends TestCase
|
||||
{
|
||||
|
|
@ -19,18 +15,9 @@ class StatsTest extends TestCase
|
|||
|
||||
public function setUp(): void
|
||||
{
|
||||
$env = System::getEnv('_APP_CONNECTIONS_QUEUE', 'redis_main=' . AppwriteURL::unparse([
|
||||
'scheme' => 'redis',
|
||||
'host' => System::getEnv('_APP_REDIS_HOST', 'redis'),
|
||||
'port' => System::getEnv('_APP_REDIS_PORT', '6379'),
|
||||
'user' => System::getEnv('_APP_REDIS_USER', ''),
|
||||
'pass' => System::getEnv('_APP_REDIS_PASS', ''),
|
||||
]));
|
||||
|
||||
$dsn = explode('=', $env);
|
||||
$dsn = count($dsn) > 1 ? $dsn[1] : $dsn[0];
|
||||
$dsn = new DSN($dsn);
|
||||
$this->connection = new Queue\Connection\Redis($dsn->getHost(), $dsn->getPort());
|
||||
global $register;
|
||||
$connection = $register->get('pools')->get('queue')->pop()->getResource();
|
||||
$this->connection = $connection;
|
||||
$this->client = new Client(self::QUEUE_NAME, $this->connection);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue