Merge branch 'feat-bulk-operations' into feat-bulk-create

# Conflicts:
#	tests/e2e/Services/Databases/DatabasesCustomClientTest.php
This commit is contained in:
Bradley Schofield
2024-12-03 17:06:35 +09:00
16 changed files with 3712 additions and 970 deletions
+236 -37
View File
@@ -4641,6 +4641,205 @@
}
}
}
},
"patch": {
"summary": "Update documents",
"operationId": "databasesUpdateDocuments",
"tags": [
"databases"
],
"description": "Update all documents that match your queries, If none are submitted then all documents are updated. Using the patch method you can pass only specific fields that will get updated.",
"responses": {
"200": {
"description": "Documents List",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/documentList"
}
}
}
}
},
"x-appwrite": {
"method": "updateDocuments",
"weight": 113,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "databases\/update-documents.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/update-documents.md",
"rate-limit": 120,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "documents.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"offline-model": "\/databases\/{databaseId}\/collections\/{collectionId}\/documents",
"offline-key": "",
"offline-response-key": "$id",
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "databaseId",
"description": "Database ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<DATABASE_ID>"
},
"in": "path"
},
{
"name": "collectionId",
"description": "Collection ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<COLLECTION_ID>"
},
"in": "path"
}
],
"requestBody": {
"content": {
"application\/json": {
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"x-example": "{}"
},
"permissions": {
"type": "array",
"description": "An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
"x-example": "[\"read(\"any\")\"]",
"items": {
"type": "string"
}
},
"queries": {
"type": "array",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long.",
"x-example": null,
"items": {
"type": "string"
}
}
}
}
}
}
}
},
"delete": {
"summary": "Delete documents",
"operationId": "databasesDeleteDocuments",
"tags": [
"databases"
],
"description": "Bulk delete documents using queries, if no queries are passed then all documents are deleted.",
"responses": {
"200": {
"description": "Documents List",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/documentList"
}
}
}
}
},
"x-appwrite": {
"method": "deleteDocuments",
"weight": 115,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "databases\/delete-documents.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/delete-documents.md",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "documents.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"offline-model": "\/databases\/{databaseId}\/collections\/{collectionId}\/documents",
"offline-key": "{documentId}",
"offline-response-key": "$id",
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "databaseId",
"description": "Database ID.",
"required": true,
"schema": {
"type": "string",
"x-example": "<DATABASE_ID>"
},
"in": "path"
},
{
"name": "collectionId",
"description": "Collection ID. You can create a new collection using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection).",
"required": true,
"schema": {
"type": "string",
"x-example": "<COLLECTION_ID>"
},
"in": "path"
}
],
"requestBody": {
"content": {
"application\/json": {
"schema": {
"type": "object",
"properties": {
"queries": {
"type": "array",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long.",
"x-example": null,
"items": {
"type": "string"
}
}
}
}
}
}
}
}
},
"\/databases\/{databaseId}\/collections\/{collectionId}\/documents\/{documentId}": {
@@ -4863,7 +5062,7 @@
},
"x-appwrite": {
"method": "deleteDocument",
"weight": 113,
"weight": 114,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4949,7 +5148,7 @@
},
"x-appwrite": {
"method": "listExecutions",
"weight": 306,
"weight": 308,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5037,7 +5236,7 @@
},
"x-appwrite": {
"method": "createExecution",
"weight": 305,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5154,7 +5353,7 @@
},
"x-appwrite": {
"method": "getExecution",
"weight": 307,
"weight": 309,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5230,7 +5429,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 331,
"weight": 333,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5284,7 +5483,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 330,
"weight": 332,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5338,7 +5537,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 117,
"weight": 119,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5392,7 +5591,7 @@
},
"x-appwrite": {
"method": "listCodes",
"weight": 118,
"weight": 120,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5446,7 +5645,7 @@
},
"x-appwrite": {
"method": "listContinents",
"weight": 122,
"weight": 124,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5500,7 +5699,7 @@
},
"x-appwrite": {
"method": "listCountries",
"weight": 119,
"weight": 121,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5554,7 +5753,7 @@
},
"x-appwrite": {
"method": "listCountriesEU",
"weight": 120,
"weight": 122,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5608,7 +5807,7 @@
},
"x-appwrite": {
"method": "listCountriesPhones",
"weight": 121,
"weight": 123,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5662,7 +5861,7 @@
},
"x-appwrite": {
"method": "listCurrencies",
"weight": 123,
"weight": 125,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5716,7 +5915,7 @@
},
"x-appwrite": {
"method": "listLanguages",
"weight": 124,
"weight": 126,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5770,7 +5969,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 382,
"weight": 384,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5855,7 +6054,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 386,
"weight": 388,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5932,7 +6131,7 @@
},
"x-appwrite": {
"method": "listFiles",
"weight": 208,
"weight": 210,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6020,7 +6219,7 @@
},
"x-appwrite": {
"method": "createFile",
"weight": 207,
"weight": 209,
"cookies": false,
"type": "upload",
"deprecated": false,
@@ -6120,7 +6319,7 @@
},
"x-appwrite": {
"method": "getFile",
"weight": 209,
"weight": 211,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6194,7 +6393,7 @@
},
"x-appwrite": {
"method": "updateFile",
"weight": 214,
"weight": 216,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6285,7 +6484,7 @@
},
"x-appwrite": {
"method": "deleteFile",
"weight": 215,
"weight": 217,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6354,7 +6553,7 @@
},
"x-appwrite": {
"method": "getFileDownload",
"weight": 211,
"weight": 213,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6423,7 +6622,7 @@
},
"x-appwrite": {
"method": "getFilePreview",
"weight": 210,
"weight": 212,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6641,7 +6840,7 @@
},
"x-appwrite": {
"method": "getFileView",
"weight": 212,
"weight": 214,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6717,7 +6916,7 @@
},
"x-appwrite": {
"method": "list",
"weight": 219,
"weight": 221,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6795,7 +6994,7 @@
},
"x-appwrite": {
"method": "create",
"weight": 218,
"weight": 220,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6882,7 +7081,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 220,
"weight": 222,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6946,7 +7145,7 @@
},
"x-appwrite": {
"method": "updateName",
"weight": 222,
"weight": 224,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7022,7 +7221,7 @@
},
"x-appwrite": {
"method": "delete",
"weight": 224,
"weight": 226,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7088,7 +7287,7 @@
},
"x-appwrite": {
"method": "listMemberships",
"weight": 226,
"weight": 228,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7176,7 +7375,7 @@
},
"x-appwrite": {
"method": "createMembership",
"weight": 225,
"weight": 227,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7289,7 +7488,7 @@
},
"x-appwrite": {
"method": "getMembership",
"weight": 227,
"weight": 229,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7363,7 +7562,7 @@
},
"x-appwrite": {
"method": "updateMembership",
"weight": 228,
"weight": 230,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7452,7 +7651,7 @@
},
"x-appwrite": {
"method": "deleteMembership",
"weight": 230,
"weight": 232,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7528,7 +7727,7 @@
},
"x-appwrite": {
"method": "updateMembershipStatus",
"weight": 229,
"weight": 231,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7628,7 +7827,7 @@
},
"x-appwrite": {
"method": "getPrefs",
"weight": 221,
"weight": 223,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7691,7 +7890,7 @@
},
"x-appwrite": {
"method": "updatePrefs",
"weight": 223,
"weight": 225,
"cookies": false,
"type": "",
"deprecated": false,
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+232 -37
View File
@@ -4807,6 +4807,201 @@
}
}
]
},
"patch": {
"summary": "Update documents",
"operationId": "databasesUpdateDocuments",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"databases"
],
"description": "Update all documents that match your queries, If none are submitted then all documents are updated. Using the patch method you can pass only specific fields that will get updated.",
"responses": {
"200": {
"description": "Documents List",
"schema": {
"$ref": "#\/definitions\/documentList"
}
}
},
"x-appwrite": {
"method": "updateDocuments",
"weight": 113,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "databases\/update-documents.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/update-documents.md",
"rate-limit": 120,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "documents.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"offline-model": "\/databases\/{databaseId}\/collections\/{collectionId}\/documents",
"offline-key": "",
"offline-response-key": "$id",
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "databaseId",
"description": "Database ID.",
"required": true,
"type": "string",
"x-example": "<DATABASE_ID>",
"in": "path"
},
{
"name": "collectionId",
"description": "Collection ID.",
"required": true,
"type": "string",
"x-example": "<COLLECTION_ID>",
"in": "path"
},
{
"name": "payload",
"in": "body",
"schema": {
"type": "object",
"properties": {
"data": {
"type": "object",
"description": "Document data as JSON object. Include only attribute and value pairs to be updated.",
"default": [],
"x-example": "{}"
},
"permissions": {
"type": "array",
"description": "An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
"default": null,
"x-example": "[\"read(\"any\")\"]",
"items": {
"type": "string"
}
},
"queries": {
"type": "array",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long.",
"default": [],
"x-example": null,
"items": {
"type": "string"
}
}
}
}
}
]
},
"delete": {
"summary": "Delete documents",
"operationId": "databasesDeleteDocuments",
"consumes": [
"application\/json"
],
"produces": [
"application\/json"
],
"tags": [
"databases"
],
"description": "Bulk delete documents using queries, if no queries are passed then all documents are deleted.",
"responses": {
"200": {
"description": "Documents List",
"schema": {
"$ref": "#\/definitions\/documentList"
}
}
},
"x-appwrite": {
"method": "deleteDocuments",
"weight": 115,
"cookies": false,
"type": "",
"deprecated": false,
"demo": "databases\/delete-documents.md",
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/databases\/delete-documents.md",
"rate-limit": 60,
"rate-time": 60,
"rate-key": "ip:{ip},method:{method},url:{url},userId:{userId}",
"scope": "documents.write",
"platforms": [
"client",
"server",
"server"
],
"packaging": false,
"offline-model": "\/databases\/{databaseId}\/collections\/{collectionId}\/documents",
"offline-key": "{documentId}",
"offline-response-key": "$id",
"auth": {
"Project": []
}
},
"security": [
{
"Project": [],
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "databaseId",
"description": "Database ID.",
"required": true,
"type": "string",
"x-example": "<DATABASE_ID>",
"in": "path"
},
{
"name": "collectionId",
"description": "Collection ID. You can create a new collection using the Database service [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection).",
"required": true,
"type": "string",
"x-example": "<COLLECTION_ID>",
"in": "path"
},
{
"name": "payload",
"in": "body",
"schema": {
"type": "object",
"properties": {
"queries": {
"type": "array",
"description": "Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https:\/\/appwrite.io\/docs\/queries). Maximum of 100 queries are allowed, each 4096 characters long.",
"default": [],
"x-example": null,
"items": {
"type": "string"
}
}
}
}
}
]
}
},
"\/databases\/{databaseId}\/collections\/{collectionId}\/documents\/{documentId}": {
@@ -5024,7 +5219,7 @@
},
"x-appwrite": {
"method": "deleteDocument",
"weight": 113,
"weight": 114,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5106,7 +5301,7 @@
},
"x-appwrite": {
"method": "listExecutions",
"weight": 306,
"weight": 308,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5191,7 +5386,7 @@
},
"x-appwrite": {
"method": "createExecution",
"weight": 305,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5312,7 +5507,7 @@
},
"x-appwrite": {
"method": "getExecution",
"weight": 307,
"weight": 309,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5386,7 +5581,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 331,
"weight": 333,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5462,7 +5657,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 330,
"weight": 332,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5538,7 +5733,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 117,
"weight": 119,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5594,7 +5789,7 @@
},
"x-appwrite": {
"method": "listCodes",
"weight": 118,
"weight": 120,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5650,7 +5845,7 @@
},
"x-appwrite": {
"method": "listContinents",
"weight": 122,
"weight": 124,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5706,7 +5901,7 @@
},
"x-appwrite": {
"method": "listCountries",
"weight": 119,
"weight": 121,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5762,7 +5957,7 @@
},
"x-appwrite": {
"method": "listCountriesEU",
"weight": 120,
"weight": 122,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5818,7 +6013,7 @@
},
"x-appwrite": {
"method": "listCountriesPhones",
"weight": 121,
"weight": 123,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5874,7 +6069,7 @@
},
"x-appwrite": {
"method": "listCurrencies",
"weight": 123,
"weight": 125,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5930,7 +6125,7 @@
},
"x-appwrite": {
"method": "listLanguages",
"weight": 124,
"weight": 126,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5986,7 +6181,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 382,
"weight": 384,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6075,7 +6270,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 386,
"weight": 388,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6150,7 +6345,7 @@
},
"x-appwrite": {
"method": "listFiles",
"weight": 208,
"weight": 210,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6235,7 +6430,7 @@
},
"x-appwrite": {
"method": "createFile",
"weight": 207,
"weight": 209,
"cookies": false,
"type": "upload",
"deprecated": false,
@@ -6329,7 +6524,7 @@
},
"x-appwrite": {
"method": "getFile",
"weight": 209,
"weight": 211,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6401,7 +6596,7 @@
},
"x-appwrite": {
"method": "updateFile",
"weight": 214,
"weight": 216,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6492,7 +6687,7 @@
},
"x-appwrite": {
"method": "deleteFile",
"weight": 215,
"weight": 217,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6566,7 +6761,7 @@
},
"x-appwrite": {
"method": "getFileDownload",
"weight": 211,
"weight": 213,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6640,7 +6835,7 @@
},
"x-appwrite": {
"method": "getFilePreview",
"weight": 210,
"weight": 212,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6841,7 +7036,7 @@
},
"x-appwrite": {
"method": "getFileView",
"weight": 212,
"weight": 214,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6915,7 +7110,7 @@
},
"x-appwrite": {
"method": "list",
"weight": 219,
"weight": 221,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6992,7 +7187,7 @@
},
"x-appwrite": {
"method": "create",
"weight": 218,
"weight": 220,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7086,7 +7281,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 220,
"weight": 222,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7150,7 +7345,7 @@
},
"x-appwrite": {
"method": "updateName",
"weight": 222,
"weight": 224,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7227,7 +7422,7 @@
},
"x-appwrite": {
"method": "delete",
"weight": 224,
"weight": 226,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7293,7 +7488,7 @@
},
"x-appwrite": {
"method": "listMemberships",
"weight": 226,
"weight": 228,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7378,7 +7573,7 @@
},
"x-appwrite": {
"method": "createMembership",
"weight": 225,
"weight": 227,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7495,7 +7690,7 @@
},
"x-appwrite": {
"method": "getMembership",
"weight": 227,
"weight": 229,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7567,7 +7762,7 @@
},
"x-appwrite": {
"method": "updateMembership",
"weight": 228,
"weight": 230,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7655,7 +7850,7 @@
},
"x-appwrite": {
"method": "deleteMembership",
"weight": 230,
"weight": 232,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7729,7 +7924,7 @@
},
"x-appwrite": {
"method": "updateMembershipStatus",
"weight": 229,
"weight": 231,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7827,7 +8022,7 @@
},
"x-appwrite": {
"method": "getPrefs",
"weight": 221,
"weight": 223,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7890,7 +8085,7 @@
},
"x-appwrite": {
"method": "updatePrefs",
"weight": 223,
"weight": 225,
"cookies": false,
"type": "",
"deprecated": false,
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+245
View File
@@ -46,6 +46,7 @@ use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\Structure;
use Utopia\Database\Validator\UID;
use Utopia\Locale\Locale;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\FloatValidator;
@@ -3728,6 +3729,141 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
->setPayload($response->getPayload(), sensitive: $relationships);
});
App::patch('/v1/databases/:databaseId/collections/:collectionId/documents')
->desc('Update documents')
->groups(['api', 'database'])
->label('scope', 'documents.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'documents.update')
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'databases')
->label('sdk.method', 'updateDocuments')
->label('sdk.description', '/docs/references/databases/update-documents.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOCUMENT_LIST)
->label('sdk.offline.model', '/databases/{databaseId}/collections/{collectionId}/documents')
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('data', [], new JSON(), 'Document data as JSON object. Include only attribute and value pairs to be updated.', true)
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), '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.', true)
->inject('requestTimestamp')
->inject('response')
->inject('dbForProject')
->inject('project')
->action(function (string $databaseId, string $collectionId, string|array $data, ?array $permissions, array $queries, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Document $project) {
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
if (empty($data) && \is_null($permissions)) {
throw new Exception(Exception::DOCUMENT_MISSING_PAYLOAD);
}
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId));
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$queries = Query::parseQueries($queries);
// Map aggregate permissions into the multiple permissions they represent.
$permissions = Permission::aggregate($permissions, [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
]);
// Users can only manage their own roles, API keys and Admin users can manage any
$roles = Authorization::getRoles();
if (!$isAPIKey && !$isPrivilegedUser && !\is_null($permissions)) {
foreach (Database::PERMISSIONS as $type) {
foreach ($permissions as $permission) {
$permission = Permission::parse($permission);
if ($permission->getPermission() != $type) {
continue;
}
$role = (new Role(
$permission->getRole(),
$permission->getIdentifier(),
$permission->getDimension()
))->toString();
if (!Authorization::isRole($role)) {
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')');
}
}
}
}
if (!\is_null($permissions)) {
$data['$permissions'] = $permissions;
}
$partialDocument = new Document($data);
$documents = $dbForProject->withRequestTimestamp(
$requestTimestamp,
fn () => $dbForProject->updateDocuments(
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
$partialDocument,
$queries
)
);
$processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) {
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$collectionId', $collection->getId());
$relationships = \array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
);
foreach ($relationships as $relationship) {
$related = $document->getAttribute($relationship->getAttribute('key'));
if (empty($related)) {
continue;
}
if (!\is_array($related)) {
$related = [$related];
}
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
$relatedCollection = Authorization::skip(
fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)
);
foreach ($related as $relation) {
if ($relation instanceof Document) {
$processDocument($relatedCollection, $relation);
}
}
}
};
foreach ($documents as $document) {
$processDocument($collection, $document);
}
$response->dynamic(new Document([
'total' => \count($documents),
'documents' => $documents
]), Response::MODEL_DOCUMENT_LIST);
});
App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId')
->alias('/v1/database/collections/:collectionId/documents/:documentId', ['databaseId' => 'default'])
->desc('Delete document')
@@ -3848,6 +3984,115 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
$response->noContent();
});
App::delete('/v1/databases/:databaseId/collections/:collectionId/documents')
->desc('Delete documents')
->groups(['api', 'database'])
->label('scope', 'documents.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'documents.delete')
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'databases')
->label('sdk.method', 'deleteDocuments')
->label('sdk.description', '/docs/references/databases/delete-documents.md')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.model', Response::MODEL_DOCUMENT_LIST)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.offline.model', '/databases/{databaseId}/collections/{collectionId}/documents')
->label('sdk.offline.key', '{documentId}')
->param('databaseId', '', new UID(), 'Database ID.')
->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('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), '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.', true)
->inject('requestTimestamp')
->inject('response')
->inject('dbForProject')
->inject('queueForUsage')
->inject('project')
->action(function (string $databaseId, string $collectionId, array $queries, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Usage $queueForUsage, Document $project) {
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
if ($database->isEmpty() || (!$database->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId));
if ($collection->isEmpty() || (!$collection->getAttribute('enabled', false) && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$queries = Query::parseQueries($queries);
$documents = $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $queries) {
return $dbForProject->deleteDocuments(
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
$queries,
intval(System::getEnv('_APP_DATABASE_BATCH_SIZE', 10_000))
);
});
// DB Storage Calculation
$queueForUsage
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection
$processDocument = (function (Document $collection, Document &$document) use (&$processDocument, $dbForProject, $database): bool {
if ($document->isEmpty()) {
return false;
}
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$collectionId', $collection->getId());
$relationships = \array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
);
foreach ($relationships as $relationship) {
$related = $document->getAttribute($relationship->getAttribute('key'));
if (empty($related)) {
continue;
}
if (!\is_array($related)) {
$relations = [$related];
} else {
$relations = $related;
}
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
$relatedCollection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId));
foreach ($relations as $index => $doc) {
if ($doc instanceof Document) {
if (!$processDocument($relatedCollection, $doc)) {
unset($relations[$index]);
}
}
}
if (\is_array($related)) {
$document->setAttribute($relationship->getAttribute('key'), \array_values($relations));
} elseif (empty($relations)) {
$document->setAttribute($relationship->getAttribute('key'), null);
}
}
return true;
});
$response->dynamic(new Document([
'total' => \count($documents),
'documents' => $documents,
]), Response::MODEL_DOCUMENT_LIST);
});
App::get('/v1/databases/usage')
->desc('Get databases usage stats')
->groups(['api', 'database', 'usage'])
+1
View File
@@ -90,6 +90,7 @@ $eventDatabaseListener = function (Document $document, Response $response, Event
$usageDatabaseListener = function (string $event, Document $document, Usage $queueForUsage) {
$value = 1;
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$value = -1;
}
Generated
+1 -1
View File
@@ -8558,7 +8558,7 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {},
"stability-flags": [],
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
@@ -0,0 +1 @@
Bulk delete documents using queries, if no queries are passed then all documents are deleted.
@@ -0,0 +1 @@
Update all documents that match your queries, If none are submitted then all documents are updated. Using the patch method you can pass only specific fields that will get updated.
+10
View File
@@ -0,0 +1,10 @@
{
"name": "@appwrite.io/repo",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@appwrite.io/repo"
}
}
}
@@ -3,6 +3,7 @@
namespace Appwrite\Messaging\Adapter;
use Appwrite\Messaging\Adapter;
use Utopia\CLI\Console;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
@@ -132,6 +133,7 @@ class Realtime extends Adapter
public static function send(string $projectId, array $payload, array $events, array $channels, array $roles, array $options = []): void
{
if (empty($channels) || empty($roles) || empty($projectId)) {
Console::error('Missing required parameters for Realtime event');
return;
}
+882 -7
View File
@@ -1514,12 +1514,6 @@ trait DatabasesBase
$this->assertEquals(400, $document4['headers']['status-code']);
// Delete document 4 with incomplete path
$this->assertEquals(404, $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()))['headers']['status-code']);
return $data;
}
@@ -4798,6 +4792,12 @@ trait DatabasesBase
]);
$this->assertEquals(408, $response['headers']['status-code']);
$this->client->call(Client::METHOD_DELETE, '/databases/' . $data['databaseId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
}
public function testBulkCreate(): void
@@ -4834,7 +4834,6 @@ trait DatabasesBase
$this->assertEquals(201, $collection['headers']['status-code']);
$data = [
'$id' => $collection['body']['$id'],
'databaseId' => $collection['body']['databaseId']
@@ -5072,4 +5071,880 @@ trait DatabasesBase
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(4, $response['body']['total']);
}
public function testBulkDeletes(): void
{
// Create database
$database = $this->client->call(Client::METHOD_POST, '/databases', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'databaseId' => ID::unique(),
'name' => 'Bulk Deletes'
]);
$this->assertNotEmpty($database['body']['$id']);
$databaseId = $database['body']['$id'];
$collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Bulk Deletes',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::delete(Role::any()),
],
]);
$this->assertEquals(201, $collection['headers']['status-code']);
$data = [
'$id' => $collection['body']['$id'],
'databaseId' => $collection['body']['databaseId']
];
// Await attribute
$numberAttribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['$id'] . '/attributes/integer', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'number',
'required' => true,
]);
$this->assertEquals(202, $numberAttribute['headers']['status-code']);
// wait for database worker to create attributes
sleep(2);
// Create documents
$createBulkDocuments = function ($amount = 10) use ($data) {
$documents = [];
for ($x = 0; $x <= $amount; $x++) {
$documents[] = [
'$id' => ID::unique(),
'number' => $x,
];
}
$doc = $this->client->call(Client::METHOD_POST, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => $documents,
]);
$this->assertEquals(201, $doc['headers']['status-code']);
};
$createBulkDocuments();
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(11, $documents['body']['total']);
// TEST: Delete all documents
$response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(11, $response['body']['total']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(0, $documents['body']['total']);
// TEST: Delete documents with query
$createBulkDocuments();
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(11, $documents['body']['total']);
$response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::lessThan('number', 5)->toString(),
],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(5, $response['body']['total']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(6, $documents['body']['total']);
foreach ($documents['body']['documents'] as $document) {
$this->assertGreaterThanOrEqual(5, $document['number']);
}
// Cleanup
$response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(6, $response['body']['total']);
// SUCCESS: Delete documents with query
$createBulkDocuments();
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(11, $documents['body']['total']);
$response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::lessThan('number', 5)->toString(),
],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(5, $response['body']['total']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(6, $documents['body']['total']);
// Cleanup
$response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(6, $response['body']['total']);
// SUCCESS: Delete Documents with limit query
$createBulkDocuments();
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(11, $documents['body']['total']);
$response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::limit(2)->toString(),
],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(2, $response['body']['total']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(9, $documents['body']['total']);
// Cleanup
$response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(9, $response['body']['total']);
// SUCCESS: Delete Documents with offset query
$createBulkDocuments();
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(11, $documents['body']['total']);
$response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [
Query::offset(5)->toString(),
],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(6, $response['body']['total']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(5, $documents['body']['total']);
$lastDoc = end($documents['body']['documents']);
$this->assertNotEmpty($lastDoc);
$this->assertEquals(4, $lastDoc['number']);
// Cleanup
$response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(5, $response['body']['total']);
// SUCCESS: Delete over 1k documents
$createBulkDocuments(1000);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(1001, $documents['body']['total']);
$response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(1001, $response['body']['total']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(0, $documents['body']['total']);
// Delete Database
$response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $data['databaseId'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
}
public function testBulkDeletesRelationshipRestrict()
{
$database = $this->client->call(Client::METHOD_POST, '/databases', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'databaseId' => ID::unique(),
'name' => 'Bulk Deletes'
]);
$this->assertNotEmpty($database['body']['$id']);
$databaseId = $database['body']['$id'];
$collection1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Collection1',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::delete(Role::any()),
],
]);
$collection2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Collection2',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::delete(Role::any()),
],
]);
$collection1 = $collection1['body']['$id'];
$collection2 = $collection2['body']['$id'];
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1 . '/attributes/relationship', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'relatedCollectionId' => $collection2,
'type' => Database::RELATION_ONE_TO_MANY,
'key' => 'collection2',
'onDelete' => Database::RELATION_MUTATE_RESTRICT,
]);
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1 . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'name',
'size' => 256,
'required' => true,
]);
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection2 . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'name',
'size' => 256,
'required' => true,
]);
sleep(3);
$document1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1 . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'documentId' => ID::unique(),
'data' => [
'name' => 'Document 1',
'collection2' => [
[
'name' => 'Document 2',
],
],
],
]);
$this->assertEquals(201, $document1['headers']['status-code']);
$response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $collection1 . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()));
$this->assertEquals(403, $response['headers']['status-code']);
$this->assertEquals('document_delete_restricted', $response['body']['type']);
}
public function testBulkDeletesRelationshipNull()
{
$database = $this->client->call(Client::METHOD_POST, '/databases', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'databaseId' => ID::unique(),
'name' => 'Bulk Deletes'
]);
$this->assertNotEmpty($database['body']['$id']);
$databaseId = $database['body']['$id'];
$collection1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Collection1',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::delete(Role::any()),
],
]);
$collection2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Collection2',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::delete(Role::any()),
],
]);
$collection1 = $collection1['body']['$id'];
$collection2 = $collection2['body']['$id'];
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1 . '/attributes/relationship', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'relatedCollectionId' => $collection2,
'type' => Database::RELATION_ONE_TO_MANY,
'key' => 'collection2',
'onDelete' => Database::RELATION_MUTATE_SET_NULL,
'twoWay' => true,
'twoWayKey' => 'collection1',
]);
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1 . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'name',
'size' => 256,
'required' => true,
]);
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection2 . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'name',
'size' => 256,
'required' => true,
]);
sleep(3);
$document1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1 . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'documentId' => ID::unique(),
'data' => [
'name' => 'Document 1',
'collection2' => [
[
'name' => 'Document 2',
],
],
],
]);
$this->assertEquals(201, $document1['headers']['status-code']);
$document2 = $document1['body']['collection2'][0]['$id'];
$response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $collection1 . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$document2 = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collection2 . '/documents/' . $document2, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()));
$this->assertEquals(200, $document2['headers']['status-code']);
$this->assertEmpty($document2['body']['collection1']);
}
public function testBulkDeletesRelationshipCascade()
{
$database = $this->client->call(Client::METHOD_POST, '/databases', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'databaseId' => ID::unique(),
'name' => 'Bulk Deletes'
]);
$this->assertNotEmpty($database['body']['$id']);
$databaseId = $database['body']['$id'];
$collection1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Collection1',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::delete(Role::any()),
],
]);
$collection2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Collection2',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::delete(Role::any()),
],
]);
$collection1 = $collection1['body']['$id'];
$collection2 = $collection2['body']['$id'];
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1 . '/attributes/relationship', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'relatedCollectionId' => $collection2,
'type' => Database::RELATION_ONE_TO_ONE,
'key' => 'collection2',
'onDelete' => Database::RELATION_MUTATE_CASCADE
]);
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1 . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'name',
'size' => 256,
'required' => true,
]);
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection2 . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'name',
'size' => 256,
'required' => true,
]);
sleep(3);
for ($i = 0; $i < 5; $i++) {
$document1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1 . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'documentId' => ID::unique(),
'data' => [
'name' => 'Document 1',
'collection2' => [
'name' => 'Document 2',
],
],
]);
$this->assertEquals(201, $document1['headers']['status-code']);
}
$response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $collection1 . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$collection1docs = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collection1 . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()));
$this->assertEquals(200, $collection1docs['headers']['status-code']);
$this->assertEquals(0, $collection1docs['body']['total']);
$collection2docs = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collection2 . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()));
$this->assertEquals(200, $collection2docs['headers']['status-code']);
$this->assertEquals(0, $collection2docs['body']['total']);
}
public function testBulkUpdates(): void
{
// Create database
$database = $this->client->call(Client::METHOD_POST, '/databases', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'databaseId' => ID::unique(),
'name' => 'Bulk Updates'
]);
$this->assertNotEmpty($database['body']['$id']);
$databaseId = $database['body']['$id'];
$collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Bulk Updates',
'documentSecurity' => true,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::delete(Role::any()),
Permission::update(Role::any()),
],
]);
$this->assertEquals(201, $collection['headers']['status-code']);
$data = [
'$id' => $collection['body']['$id'],
'databaseId' => $collection['body']['databaseId']
];
// Await attribute
$numberAttribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['$id'] . '/attributes/integer', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'number',
'required' => true,
]);
$this->assertEquals(202, $numberAttribute['headers']['status-code']);
// wait for database worker to create attributes
sleep(2);
// Create documents
$createBulkDocuments = function ($amount = 10) use ($data) {
$documents = [];
for ($x = 0; $x <= $amount; $x++) {
$documents[] = [
'$id' => ID::unique(),
'number' => $x,
];
}
$doc = $this->client->call(Client::METHOD_POST, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => $documents,
]);
$this->assertEquals(201, $doc['headers']['status-code']);
};
$createBulkDocuments();
// TEST: Update all documents
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'number' => 100
],
'permissions' => [
Permission::read(Role::user($this->getUser()['$id'])),
Permission::update(Role::user($this->getUser()['$id'])),
Permission::delete(Role::user($this->getUser()['$id'])),
]
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(10, $response['body']['documents']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
Query::equal('number', [100])->toString(),
]);
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(10, $documents['body']['total']);
foreach ($documents['body']['documents'] as $document) {
$this->assertEquals([
Permission::read(Role::user($this->getUser()['$id'])),
Permission::update(Role::user($this->getUser()['$id'])),
Permission::delete(Role::user($this->getUser()['$id'])),
], $document['$permissions']);
$this->assertEquals($collection['body']['$id'], $document['$collectionId']);
$this->assertEquals($data['databaseId'], $document['$databaseId']);
}
// TEST: Check permissions persist
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'number' => 200
],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(10, $response['body']['documents']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
Query::equal('number', [200])->toString(),
]);
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(10, $documents['body']['total']);
foreach ($documents['body']['documents'] as $document) {
$this->assertEquals([
Permission::read(Role::user($this->getUser()['$id'])),
Permission::update(Role::user($this->getUser()['$id'])),
Permission::delete(Role::user($this->getUser()['$id'])),
], $document['$permissions']);
}
// TEST: Update documents with limit
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'number' => 300
],
'queries' => [
Query::limit(5)->toString(),
],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(5, $response['body']['documents']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [Query::equal('number', [200])->toString()]
]);
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(5, $documents['body']['total']);
// TEST: Update documents with offset
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'number' => 300
],
'queries' => [
Query::offset(5)->toString(),
],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(5, $response['body']['documents']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [Query::equal('number', [300])->toString()]
]);
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(10, $documents['body']['total']);
// TEST: Update documents with equals filter
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'number' => 400
],
'queries' => [
Query::equal('number', [300])->toString(),
],
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(10, $response['body']['documents']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'queries' => [Query::equal('number', [400])->toString()]
]);
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(10, $documents['body']['total']);
}
}
@@ -10,6 +10,7 @@ use Utopia\Database\Database;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
class DatabasesCustomClientTest extends Scope
{
@@ -1103,4 +1104,410 @@ class DatabasesCustomClientTest extends Scope
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertCount(3, $response['body']['documents']);
}
// Bulk Updates
public function testBulkUpdatesPermissions(): void
{
// Create database
$database = $this->client->call(Client::METHOD_POST, '/databases', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'databaseId' => ID::unique(),
'name' => 'Bulk Update Perms'
]);
$this->assertNotEmpty($database['body']['$id']);
$databaseId = $database['body']['$id'];
$collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Bulk Updates Perms',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::user($this->getUser()['$id']))
],
]);
$this->assertEquals(201, $collection['headers']['status-code']);
$data = [
'$id' => $collection['body']['$id'],
'databaseId' => $collection['body']['databaseId']
];
// Await attribute
$numberAttribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['$id'] . '/attributes/integer', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'number',
'required' => true,
]);
$this->assertEquals(202, $numberAttribute['headers']['status-code']);
sleep(2);
// TEST: Update all documents with invalid collection level permissions
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'number' => 100
]
]);
$this->assertEquals(401, $response['headers']['status-code']);
$collection = $this->client->call(Client::METHOD_PUT, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'name' => 'Bulk Updates Perms',
'documentSecurity' => true
]);
$this->assertEquals(200, $collection['headers']['status-code']);
// TEST: Make sure we can update only documents we have permissions for
for ($i = 0; $i < 6; $i++) {
$doc = $this->client->call(Client::METHOD_POST, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documentId' => ID::unique(),
'data' => [
'number' => $i,
],
'permissions' => [
Permission::update(Role::user($this->getUser()['$id']))
]
]);
$this->assertEquals(201, $doc['headers']['status-code']);
}
$doc = $this->client->call(Client::METHOD_POST, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'documentId' => ID::unique(),
'data' => [
'number' => 6,
],
'permissions' => [
Permission::update(Role::user('user2')),
Permission::read(Role::user($this->getUser()['$id'])),
]
]);
$this->assertEquals(201, $doc['headers']['status-code']);
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'data' => [
'number' => 100
]
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(6, $response['body']['documents']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'queries' => [Query::notEqual('number', 100)->toString()]
]);
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(1, $documents['body']['total']);
}
public function testBulkUpdatesRelationship()
{
$database = $this->client->call(Client::METHOD_POST, '/databases', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'databaseId' => ID::unique(),
'name' => 'Bulk Updates'
]);
$this->assertNotEmpty($database['body']['$id']);
$databaseId = $database['body']['$id'];
$collection1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Collection1',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::delete(Role::any()),
Permission::update(Role::any()),
],
]);
$this->assertEquals(201, $collection1['headers']['status-code']);
$collection2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Collection2',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::delete(Role::any()),
Permission::update(Role::any()),
],
]);
$this->assertEquals(201, $collection1['headers']['status-code']);
$collection1 = $collection1['body']['$id'];
$collection2 = $collection2['body']['$id'];
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1 . '/attributes/relationship', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'relatedCollectionId' => $collection2,
'type' => Database::RELATION_ONE_TO_MANY,
'key' => 'collection2',
'onDelete' => Database::RELATION_MUTATE_RESTRICT,
'twoWay' => true,
'twoWayKey' => 'collection1',
]);
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1 . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'name',
'size' => 256,
'required' => true,
]);
$this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection2 . '/attributes/string', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'name',
'size' => 256,
'required' => true,
]);
sleep(3);
$document1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1 . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'documentId' => ID::unique(),
'data' => [
'name' => 'Document 1',
'collection2' => [
[
'name' => 'Document 2',
],
],
],
]);
$this->assertEquals(201, $document1['headers']['status-code']);
$document1 = $document1['body']['$id'];
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collection1 . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'data' => [
'name' => 'Document 1 Updated',
]
]);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(1, $response['body']['documents']);
$response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collection2 . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$document2 = $response['body']['documents'][0];
$this->assertEquals('Document 2', $document2['name']);
$this->assertEquals('Document 1 Updated', $document2['collection1']['name']);
$response = $this->client->call(Client::METHOD_PATCH, '/databases/' . $databaseId . '/collections/' . $collection2 . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()), [
'data' => [
'name' => 'Document 2 Updated',
]
]);
$this->assertEquals(200, $response['headers']['status-code']);
$response = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collection1 . '/documents/' . $document1, array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id']
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals('Document 1 Updated', $response['body']['name']);
$document2 = $response['body']['collection2'][0];
$this->assertEquals('Document 2 Updated', $document2['name']);
}
public function testBulkDeletesPermissions(): void
{
// Create database
$database = $this->client->call(Client::METHOD_POST, '/databases', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'databaseId' => ID::unique(),
'name' => 'Bulk Deletes Perms'
]);
$this->assertNotEmpty($database['body']['$id']);
$databaseId = $database['body']['$id'];
$collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'collectionId' => ID::unique(),
'name' => 'Bulk Deletes Perms',
'documentSecurity' => false,
'permissions' => [
Permission::create(Role::user($this->getUser()['$id']))
],
]);
$this->assertEquals(201, $collection['headers']['status-code']);
$data = [
'$id' => $collection['body']['$id'],
'databaseId' => $collection['body']['databaseId']
];
// Await attribute
$numberAttribute = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $data['$id'] . '/attributes/integer', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'key' => 'number',
'required' => true,
]);
$this->assertEquals(202, $numberAttribute['headers']['status-code']);
sleep(2);
// TEST: Delete all documents with invalid collection level permissions
$response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(401, $response['headers']['status-code']);
$collection = $this->client->call(Client::METHOD_PUT, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'], array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]), [
'name' => 'Bulk Deletes Perms',
'documentSecurity' => true
]);
$this->assertEquals(200, $collection['headers']['status-code']);
// TEST: Make sure we can delete only documents we have permissions for
for ($i = 0; $i < 6; $i++) {
$doc = $this->client->call(Client::METHOD_POST, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documentId' => ID::unique(),
'data' => [
'number' => $i,
],
'permissions' => [
Permission::delete(Role::user($this->getUser()['$id']))
]
]);
$this->assertEquals(201, $doc['headers']['status-code']);
}
$doc = $this->client->call(Client::METHOD_POST, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', [
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
], [
'documentId' => ID::unique(),
'data' => [
'number' => 6,
],
'permissions' => [
Permission::delete(Role::user('user2'))
]
]);
$this->assertEquals(201, $doc['headers']['status-code']);
$response = $this->client->call(Client::METHOD_DELETE, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertEquals(6, $response['body']['total']);
$documents = $this->client->call(Client::METHOD_GET, '/databases/' . $data['databaseId'] . '/collections/' . $data['$id'] . '/documents', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']
]));
$this->assertEquals(200, $documents['headers']['status-code']);
$this->assertEquals(1, $documents['body']['total']);
}
}
@@ -650,6 +650,7 @@ class RealtimeCustomClientTest extends Scope
$user = $this->getUser();
$session = $user['session'] ?? '';
$projectId = $this->getProject()['$id'];
$documentIds = [];
$client = $this->getWebsocket(['documents', 'collections'], [
'origin' => 'http://localhost',
@@ -739,6 +740,7 @@ class RealtimeCustomClientTest extends Scope
$response = json_decode($client->receive(), true);
$documentId = $document['body']['$id'];
$documentIds[] = $documentId;
$this->assertArrayHasKey('type', $response);
$this->assertArrayHasKey('data', $response);