Compare commits

...
Author SHA1 Message Date
Bradley Schofield f7e69624c6 Address comments 2024-12-11 10:19:10 +09:00
Bradley Schofield e9c2ebbcf5 Implement SDK Multiplexing for Create Document/s 2024-12-10 12:31:26 +09:00
Bradley Schofield 879ddb3074 Implement SDK Method Multiplexing 2024-12-09 14:21:56 +09:00
Jake BarnbyandGitHub 8c1f6aabb7 Merge pull request #8968 from appwrite/feat-bulk-create
Feat: Bulk Create Documents
2024-12-04 19:03:31 +13:00
Bradley Schofield f707be6ed3 Add extra tests 2024-12-04 09:13:08 +09:00
Bradley Schofield bcb4ffa404 Fix Tests 2024-12-03 17:39:25 +09:00
Bradley Schofield c0d4b58eea Merge branch 'feat-bulk-operations' into feat-bulk-create
# Conflicts:
#	tests/e2e/Services/Databases/DatabasesCustomClientTest.php
2024-12-03 17:06:35 +09:00
Bradley Schofield e2ab6a05ab Fix permissions and accompanying tests 2024-12-03 16:40:03 +09:00
Jake BarnbyandGitHub 6bbb522a87 Merge pull request #8955 from appwrite/feat-bulk-updates
Feat bulk updates
2024-12-03 19:35:45 +13:00
Bradley Schofield 7f2d3beb31 Run Linter 2024-12-03 15:19:58 +09:00
Bradley Schofield 3d4917727b Address Comments 2024-12-03 15:14:07 +09:00
Bradley SchofieldandGitHub 41ead533b8 Linter 2024-12-03 05:03:51 +00:00
Bradley SchofieldandGitHub c47f65d27c Merge remote-tracking branch 'origin/feat-bulk-operations' into feat-bulk-updates 2024-12-03 05:03:02 +00:00
Bradley SchofieldandGitHub 116ba6bc7b Fix Problems with tests and params 2024-12-03 04:38:59 +00:00
Jake BarnbyandGitHub adc94cf1b4 Merge pull request #8942 from appwrite/feat-bulk-deletes
Feat bulk deletes
2024-12-03 17:36:06 +13:00
Jake BarnbyandGitHub 257733aec0 Update app/controllers/api/databases.php 2024-12-03 17:35:46 +13:00
Bradley Schofield fbbf846eb9 Run Linter 2024-12-02 19:59:48 +09:00
Bradley Schofield fbf985ea90 Add event 2024-12-02 19:57:21 +09:00
Bradley Schofield f061f724e0 Fix params 2024-12-02 19:06:34 +09:00
Bradley Schofield 125585831b Remove bulk deletes realtime test 2024-12-02 18:16:04 +09:00
Bradley Schofield 3c418c1607 Address params and events 2024-12-02 18:08:10 +09:00
Bradley Schofield 8f8c008e8b Fix params and usage 2024-12-02 18:06:04 +09:00
Bradley Schofield 84ba7febe7 Run Linter 2024-12-02 16:45:03 +09:00
Bradley Schofield 7b79642f4b Run Linter 2024-12-02 16:44:15 +09:00
Bradley Schofield 949347764a Add new database events 2024-12-02 16:43:19 +09:00
Bradley Schofield 32c4debffc Remove realtime/events from bulk operations and get usage events working 2024-12-02 16:40:19 +09:00
Bradley Schofield 2850879428 Remove Events for bulk updates 2024-12-02 16:07:32 +09:00
Bradley Schofield 1c0c8f0eb6 Run Formatter 2024-12-02 13:15:41 +09:00
Bradley Schofield 00e3fe12f4 Revert "Fix wrong params on feedback, disable debugging in compose file"
This reverts commit 5c895579ed.
2024-11-28 17:25:38 +09:00
Bradley Schofield 5c895579ed Fix wrong params on feedback, disable debugging in compose file 2024-11-28 17:24:53 +09:00
Bradley Schofield 104a9b6074 Remove realtime support for bulk operations 2024-11-28 16:29:22 +09:00
Bradley Schofield 2b08db9a38 Address Comments 2024-11-25 16:22:02 +09:00
Bradley Schofield 9d652d9e79 Remove double realtime 2024-11-25 16:08:50 +09:00
Bradley Schofield e1fffa775e Fix Tests 2024-11-25 15:23:22 +09:00
Bradley Schofield 2e60698ec1 Fix double event execution on document creation 2024-11-25 15:21:56 +09:00
Bradley Schofield 90521c6bd7 Update database library version 2024-11-25 15:02:36 +09:00
Bradley Schofield 558f7f06b4 Fix Missing Model 2024-11-25 14:53:35 +09:00
Bradley Schofield 19194dabc0 Remove Bulk Operation Model 2024-11-25 14:53:01 +09:00
Bradley Schofield e1230f7465 Address Comments 2024-11-25 14:45:21 +09:00
Bradley Schofield def25a8c9b Merge branch 'feat-bulk-deletes' of https://github.com/appwrite/appwrite into feat-bulk-deletes 2024-11-25 14:34:29 +09:00
Bradley Schofield 675227d25a Address Comments 2024-11-25 14:34:23 +09:00
4e5ad33d73 Update app/controllers/api/databases.php
Co-authored-by: Jake Barnby <jakeb994@gmail.com>
2024-11-25 14:29:51 +09:00
Bradley Schofield 54260b5a9a Use new API Spec and get realtime/events working 2024-11-22 11:51:17 +09:00
Bradley Schofield 67b95c4b39 Update API Signature to DB Lib and get realtime/events working 2024-11-22 11:07:05 +09:00
Bradley Schofield 90c4bad826 Run Linter 2024-11-22 10:44:19 +09:00
Bradley Schofield 791f0dda79 Update to use new DB Lib and get realtime working 2024-11-22 10:43:45 +09:00
Bradley Schofield 79c043ec1f Fix Tests 2024-11-19 11:47:23 +09:00
Bradley Schofield 755e86020f Continue work on getting events working 2024-11-19 10:09:55 +09:00
Bradley Schofield 96e69b55e1 Add tests that are currently failing because of realtime and attempt to fix them 2024-11-15 12:00:11 +09:00
Bradley Schofield c210b2b874 Address Comments 2024-11-14 17:16:39 +09:00
Bradley Schofield c0ef832784 Try and fix events 2024-11-14 17:15:28 +09:00
Bradley Schofield bb7234fa98 Update database version 2024-11-13 13:45:16 +09:00
Bradley Schofield dd6bd745df Address Comments, fix permission bug 2024-11-13 13:41:18 +09:00
Bradley Schofield a8f3b63ded Add Tests for Relationships and Permissions, Update error messages to be a little clearer 2024-11-13 10:00:40 +09:00
Bradley Schofield aa42608528 Regen Specs 2024-11-13 09:22:47 +09:00
Bradley Schofield 172d25d67c Address Comments 2024-11-12 15:43:36 +09:00
Bradley Schofield 92c93357cf Address Jake's Comments 2024-11-12 11:41:08 +09:00
Bradley Schofield 6faad29907 Initial Version 2024-11-12 10:20:46 +09:00
Bradley Schofield 07738bdcf9 Update Specs 2024-11-11 15:29:59 +09:00
Bradley Schofield c6ca830801 Merge branch 'feat-bulk-operations' into feat-bulk-updates 2024-11-11 15:27:37 +09:00
Bradley Schofield 44234da995 Implement Bulk Updates API 2024-11-11 15:27:26 +09:00
Bradley SchofieldandGitHub 22dfcb78c4 Merge branch 'feat-bulk-operations' into feat-bulk-deletes 2024-11-11 03:59:23 +00:00
Bradley SchofieldandGitHub 60da24e521 Run Linter 2024-11-11 03:58:24 +00:00
Bradley Schofield f703a0db18 Merge branch 'feat-bulk-operations' into feat-bulk-deletes
# Conflicts:
#	app/config/specs/open-api3-latest-client.json
#	app/config/specs/open-api3-latest-console.json
#	app/config/specs/open-api3-latest-server.json
#	app/config/specs/swagger2-latest-client.json
#	app/config/specs/swagger2-latest-console.json
#	app/config/specs/swagger2-latest-server.json
#	src/Appwrite/Utopia/Response/Model/BulkOperation.php
2024-11-11 12:41:24 +09:00
Bradley Schofield 2a8ab8d610 Add base bulk operation models and envvars 2024-11-11 12:36:58 +09:00
Bradley Schofield 5bf20289a5 Address Comments 2024-11-08 16:17:29 +09:00
8a601ee058 Update src/Appwrite/Utopia/Response/Model/BulkOperation.php
Co-authored-by: Jake Barnby <jakeb994@gmail.com>
2024-11-07 14:41:14 +09:00
Bradley Schofield 95861d068d Fix Tests 2024-11-07 13:14:15 +09:00
Bradley Schofield 313adc7023 Merge branch '1.6.x' into feat-bulk-deletes
# Conflicts:
#	composer.json
#	composer.lock
2024-11-07 12:37:08 +09:00
Bradley Schofield bb40455cb7 Add documentation, update specs 2024-11-07 12:34:18 +09:00
Bradley Schofield 86053e9028 Implement Bulk Deletes API 2024-11-07 12:30:17 +09:00
23 changed files with 6626 additions and 1506 deletions
+1
View File
@@ -33,6 +33,7 @@ _APP_DB_SCHEMA=appwrite
_APP_DB_USER=user
_APP_DB_PASS=password
_APP_DB_ROOT_PASS=rootsecretpassword
_APP_DATABASE_BATCH_SIZE=10_000
_APP_STORAGE_DEVICE=Local
_APP_STORAGE_S3_ACCESS_KEY=
_APP_STORAGE_S3_SECRET=
+282 -45
View File
@@ -4537,14 +4537,21 @@
"tags": [
"databases"
],
"description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
"description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
"responses": {
"201": {
"description": "Document",
"description": "Document, or Documents List",
"content": {
"application\/json": {
"schema": {
"$ref": "#\/components\/schemas\/document"
"oneOf": [
{
"$ref": "#\/components\/schemas\/document"
},
{
"$ref": "#\/components\/schemas\/documentList"
}
]
}
}
}
@@ -4571,6 +4578,33 @@
"offline-model": "\/databases\/{databaseId}\/collections\/{collectionId}\/documents",
"offline-key": "{documentId}",
"offline-response-key": "$id",
"multiplex": {
"createDocument": {
"name": "Create Document",
"parameters": [
"documentId",
"data",
"permissions"
],
"required": [
"documentId",
"data"
],
"response": "document",
"description": "\/docs\/references\/databases\/create-document.md"
},
"createDocuments": {
"name": "Create Documents",
"parameters": [
"documents"
],
"required": [
"documents"
],
"response": "documentList",
"description": "\/docs\/references\/databases\/create-documents.md"
}
},
"auth": {
"Project": []
}
@@ -4620,6 +4654,14 @@
"description": "Document data as JSON object.",
"x-example": "{}"
},
"documents": {
"type": "array",
"description": "Array of documents data as JSON object.",
"x-example": null,
"items": {
"type": "object"
}
},
"permissions": {
"type": "array",
"description": "An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
@@ -4628,11 +4670,206 @@
"type": "string"
}
}
},
"required": [
"documentId",
"data"
]
}
}
}
}
}
},
"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"
}
}
}
}
}
}
@@ -4859,7 +5096,7 @@
},
"x-appwrite": {
"method": "deleteDocument",
"weight": 113,
"weight": 114,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4945,7 +5182,7 @@
},
"x-appwrite": {
"method": "listExecutions",
"weight": 306,
"weight": 308,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5033,7 +5270,7 @@
},
"x-appwrite": {
"method": "createExecution",
"weight": 305,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5150,7 +5387,7 @@
},
"x-appwrite": {
"method": "getExecution",
"weight": 307,
"weight": 309,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5226,7 +5463,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 331,
"weight": 333,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5280,7 +5517,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 330,
"weight": 332,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5334,7 +5571,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 117,
"weight": 119,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5388,7 +5625,7 @@
},
"x-appwrite": {
"method": "listCodes",
"weight": 118,
"weight": 120,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5442,7 +5679,7 @@
},
"x-appwrite": {
"method": "listContinents",
"weight": 122,
"weight": 124,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5496,7 +5733,7 @@
},
"x-appwrite": {
"method": "listCountries",
"weight": 119,
"weight": 121,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5550,7 +5787,7 @@
},
"x-appwrite": {
"method": "listCountriesEU",
"weight": 120,
"weight": 122,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5604,7 +5841,7 @@
},
"x-appwrite": {
"method": "listCountriesPhones",
"weight": 121,
"weight": 123,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5658,7 +5895,7 @@
},
"x-appwrite": {
"method": "listCurrencies",
"weight": 123,
"weight": 125,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5712,7 +5949,7 @@
},
"x-appwrite": {
"method": "listLanguages",
"weight": 124,
"weight": 126,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5766,7 +6003,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 382,
"weight": 384,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5851,7 +6088,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 386,
"weight": 388,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5928,7 +6165,7 @@
},
"x-appwrite": {
"method": "listFiles",
"weight": 208,
"weight": 210,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6016,7 +6253,7 @@
},
"x-appwrite": {
"method": "createFile",
"weight": 207,
"weight": 209,
"cookies": false,
"type": "upload",
"deprecated": false,
@@ -6116,7 +6353,7 @@
},
"x-appwrite": {
"method": "getFile",
"weight": 209,
"weight": 211,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6190,7 +6427,7 @@
},
"x-appwrite": {
"method": "updateFile",
"weight": 214,
"weight": 216,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6281,7 +6518,7 @@
},
"x-appwrite": {
"method": "deleteFile",
"weight": 215,
"weight": 217,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6350,7 +6587,7 @@
},
"x-appwrite": {
"method": "getFileDownload",
"weight": 211,
"weight": 213,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6419,7 +6656,7 @@
},
"x-appwrite": {
"method": "getFilePreview",
"weight": 210,
"weight": 212,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6637,7 +6874,7 @@
},
"x-appwrite": {
"method": "getFileView",
"weight": 212,
"weight": 214,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6713,7 +6950,7 @@
},
"x-appwrite": {
"method": "list",
"weight": 219,
"weight": 221,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6791,7 +7028,7 @@
},
"x-appwrite": {
"method": "create",
"weight": 218,
"weight": 220,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6878,7 +7115,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 220,
"weight": 222,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6942,7 +7179,7 @@
},
"x-appwrite": {
"method": "updateName",
"weight": 222,
"weight": 224,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7018,7 +7255,7 @@
},
"x-appwrite": {
"method": "delete",
"weight": 224,
"weight": 226,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7084,7 +7321,7 @@
},
"x-appwrite": {
"method": "listMemberships",
"weight": 226,
"weight": 228,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7172,7 +7409,7 @@
},
"x-appwrite": {
"method": "createMembership",
"weight": 225,
"weight": 227,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7285,7 +7522,7 @@
},
"x-appwrite": {
"method": "getMembership",
"weight": 227,
"weight": 229,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7359,7 +7596,7 @@
},
"x-appwrite": {
"method": "updateMembership",
"weight": 228,
"weight": 230,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7448,7 +7685,7 @@
},
"x-appwrite": {
"method": "deleteMembership",
"weight": 230,
"weight": 232,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7524,7 +7761,7 @@
},
"x-appwrite": {
"method": "updateMembershipStatus",
"weight": 229,
"weight": 231,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7624,7 +7861,7 @@
},
"x-appwrite": {
"method": "getPrefs",
"weight": 221,
"weight": 223,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7687,7 +7924,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
+281 -47
View File
@@ -4709,12 +4709,19 @@
"tags": [
"databases"
],
"description": "Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
"description": "Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https:\/\/appwrite.io\/docs\/server\/databases#databasesCreateCollection) API or directly from your database console.",
"responses": {
"201": {
"description": "Document",
"description": "Document, or Documents List",
"schema": {
"$ref": "#\/definitions\/document"
"x-oneOf": [
{
"$ref": "#\/definitions\/document"
},
{
"$ref": "#\/definitions\/documentList"
}
]
}
}
},
@@ -4739,6 +4746,33 @@
"offline-model": "\/databases\/{databaseId}\/collections\/{collectionId}\/documents",
"offline-key": "{documentId}",
"offline-response-key": "$id",
"multiplex": {
"createDocument": {
"name": "Create Document",
"parameters": [
"documentId",
"data",
"permissions"
],
"required": [
"documentId",
"data"
],
"response": "document",
"description": "\/docs\/references\/databases\/create-document.md"
},
"createDocuments": {
"name": "Create Documents",
"parameters": [
"documents"
],
"required": [
"documents"
],
"response": "documentList",
"description": "\/docs\/references\/databases\/create-documents.md"
}
},
"auth": {
"Project": []
}
@@ -4776,15 +4810,24 @@
"documentId": {
"type": "string",
"description": "Document ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.",
"default": null,
"default": "",
"x-example": "<DOCUMENT_ID>"
},
"data": {
"type": "object",
"description": "Document data as JSON object.",
"default": {},
"default": [],
"x-example": "{}"
},
"documents": {
"type": "array",
"description": "Array of documents data as JSON object.",
"default": [],
"x-example": null,
"items": {
"type": "object"
}
},
"permissions": {
"type": "array",
"description": "An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https:\/\/appwrite.io\/docs\/permissions).",
@@ -4794,11 +4837,202 @@
"type": "string"
}
}
},
"required": [
"documentId",
"data"
]
}
}
}
]
},
"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"
}
}
}
}
}
]
@@ -5019,7 +5253,7 @@
},
"x-appwrite": {
"method": "deleteDocument",
"weight": 113,
"weight": 114,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5101,7 +5335,7 @@
},
"x-appwrite": {
"method": "listExecutions",
"weight": 306,
"weight": 308,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5186,7 +5420,7 @@
},
"x-appwrite": {
"method": "createExecution",
"weight": 305,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5307,7 +5541,7 @@
},
"x-appwrite": {
"method": "getExecution",
"weight": 307,
"weight": 309,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5381,7 +5615,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 331,
"weight": 333,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5457,7 +5691,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 330,
"weight": 332,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5533,7 +5767,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 117,
"weight": 119,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5589,7 +5823,7 @@
},
"x-appwrite": {
"method": "listCodes",
"weight": 118,
"weight": 120,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5645,7 +5879,7 @@
},
"x-appwrite": {
"method": "listContinents",
"weight": 122,
"weight": 124,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5701,7 +5935,7 @@
},
"x-appwrite": {
"method": "listCountries",
"weight": 119,
"weight": 121,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5757,7 +5991,7 @@
},
"x-appwrite": {
"method": "listCountriesEU",
"weight": 120,
"weight": 122,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5813,7 +6047,7 @@
},
"x-appwrite": {
"method": "listCountriesPhones",
"weight": 121,
"weight": 123,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5869,7 +6103,7 @@
},
"x-appwrite": {
"method": "listCurrencies",
"weight": 123,
"weight": 125,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5925,7 +6159,7 @@
},
"x-appwrite": {
"method": "listLanguages",
"weight": 124,
"weight": 126,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5981,7 +6215,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 382,
"weight": 384,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6070,7 +6304,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 386,
"weight": 388,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6145,7 +6379,7 @@
},
"x-appwrite": {
"method": "listFiles",
"weight": 208,
"weight": 210,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6230,7 +6464,7 @@
},
"x-appwrite": {
"method": "createFile",
"weight": 207,
"weight": 209,
"cookies": false,
"type": "upload",
"deprecated": false,
@@ -6324,7 +6558,7 @@
},
"x-appwrite": {
"method": "getFile",
"weight": 209,
"weight": 211,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6396,7 +6630,7 @@
},
"x-appwrite": {
"method": "updateFile",
"weight": 214,
"weight": 216,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6487,7 +6721,7 @@
},
"x-appwrite": {
"method": "deleteFile",
"weight": 215,
"weight": 217,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6561,7 +6795,7 @@
},
"x-appwrite": {
"method": "getFileDownload",
"weight": 211,
"weight": 213,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6635,7 +6869,7 @@
},
"x-appwrite": {
"method": "getFilePreview",
"weight": 210,
"weight": 212,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6836,7 +7070,7 @@
},
"x-appwrite": {
"method": "getFileView",
"weight": 212,
"weight": 214,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6910,7 +7144,7 @@
},
"x-appwrite": {
"method": "list",
"weight": 219,
"weight": 221,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6987,7 +7221,7 @@
},
"x-appwrite": {
"method": "create",
"weight": 218,
"weight": 220,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7081,7 +7315,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 220,
"weight": 222,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7145,7 +7379,7 @@
},
"x-appwrite": {
"method": "updateName",
"weight": 222,
"weight": 224,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7222,7 +7456,7 @@
},
"x-appwrite": {
"method": "delete",
"weight": 224,
"weight": 226,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7288,7 +7522,7 @@
},
"x-appwrite": {
"method": "listMemberships",
"weight": 226,
"weight": 228,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7373,7 +7607,7 @@
},
"x-appwrite": {
"method": "createMembership",
"weight": 225,
"weight": 227,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7490,7 +7724,7 @@
},
"x-appwrite": {
"method": "getMembership",
"weight": 227,
"weight": 229,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7562,7 +7796,7 @@
},
"x-appwrite": {
"method": "updateMembership",
"weight": 228,
"weight": 230,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7650,7 +7884,7 @@
},
"x-appwrite": {
"method": "deleteMembership",
"weight": 230,
"weight": 232,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7724,7 +7958,7 @@
},
"x-appwrite": {
"method": "updateMembershipStatus",
"weight": 229,
"weight": 231,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7822,7 +8056,7 @@
},
"x-appwrite": {
"method": "getPrefs",
"weight": 221,
"weight": 223,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7885,7 +8119,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
+371 -65
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;
@@ -2844,7 +2845,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
->alias('/v1/database/collections/:collectionId/documents', ['databaseId' => 'default'])
->desc('Create document')
->groups(['api', 'database'])
->label('event', 'databases.[databaseId].collections.[collectionId].documents.[documentId].create')
->label('scope', 'documents.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'document.create')
@@ -2854,34 +2854,70 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
->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', 'createDocument')
->label('sdk.method', [
'createDocument' => [
'name' => 'Create Document',
'parameters' => [
'documentId',
'data',
'permissions',
],
'required' => ['documentId', 'data'],
'response' => Response::MODEL_DOCUMENT,
'description' => '/docs/references/databases/create-document.md',
],
'createDocuments' => [
'name' => 'Create Documents',
'parameters' => [
'documents'
],
'required' => ['documents'],
'response' => Response::MODEL_DOCUMENT_LIST,
'description' => '/docs/references/databases/create-documents.md'
],
])
->label('sdk.description', '/docs/references/databases/create-document.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOCUMENT)
->label('sdk.response.model', [Response::MODEL_DOCUMENT, Response::MODEL_DOCUMENT_LIST])
->label('sdk.offline.model', '/databases/{databaseId}/collections/{collectionId}/documents')
->label('sdk.offline.key', '{documentId}')
->param('databaseId', '', new UID(), 'Database ID.')
->param('documentId', '', new CustomId(), 'Document ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('documentId', '', new CustomId(), 'Document ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', true)
->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). Make sure to define attributes before creating documents.')
->param('data', [], new JSON(), 'Document data as JSON object.')
->param('data', [], new JSON(), 'Document data as JSON object.', true)
->param('documents', [], new ArrayList(new JSON(), APP_LIMIT_ARRAY_DOCUMENTS_SIZE), 'Array of documents data as JSON object.', 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, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
->inject('response')
->inject('dbForProject')
->inject('user')
->inject('queueForEvents')
->inject('queueForUsage')
->inject('project')
->inject('mode')
->action(function (string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, Usage $queueForUsage, string $mode) {
->action(function (string $databaseId, ?string $documentId, string $collectionId, string|array|null $data, ?array $documents, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, Usage $queueForUsage, Document $project, string $mode) {
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
$isBulk = true;
if (empty($data)) {
if (empty($data) && empty($documents)) {
throw new Exception(Exception::DOCUMENT_MISSING_DATA);
}
if (isset($data['$id'])) {
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, '$id is not allowed for creating new documents, try update instead');
if (!empty($data) && !empty($documents)) {
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, 'You can only send one of the following parameters: data, documents');
}
if (!empty($data) && empty($documentId)) {
throw new Exception(Exception::DOCUMENT_MISSING_DATA, 'Document ID is required when creating a single document');
}
if (!empty($documents) && !empty($documentId)) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Param "documentId" is disallowed when creating multiple documents, use $id inside the documents');
}
if (!empty($data)) {
$isBulk = false;
$documents = [$data];
}
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
@@ -2905,43 +2941,46 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
Database::PERMISSION_DELETE,
];
// Map aggregate permissions to into the set of individual permissions they represent.
$permissions = Permission::aggregate($permissions, $allowedPermissions);
// Add permissions for current the user if none were provided.
if (\is_null($permissions)) {
$permissions = [];
if (!empty($user->getId())) {
foreach ($allowedPermissions as $permission) {
$permissions[] = (new Permission($permission, 'user', $user->getId()))->toString();
}
$setPermissions = function (Document $document, ?array $permissions) use ($user, $allowedPermissions, $isAPIKey, $isPrivilegedUser, $isBulk) {
// Map aggregate permissions to into the set of individual permissions they represent.
if ($isBulk) {
$permissions = $document['$permissions'] ?? null;
}
}
// Users can only manage their own roles, API keys and Admin users can manage any
if (!$isAPIKey && !$isPrivilegedUser) {
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(', ', Authorization::getRoles()) . ')');
$permissions = Permission::aggregate($permissions, $allowedPermissions);
// Add permissions for current the user if none were provided.
if (\is_null($permissions)) {
$permissions = [];
if (!empty($user->getId())) {
foreach ($allowedPermissions as $permission) {
$permissions[] = (new Permission($permission, 'user', $user->getId()))->toString();
}
}
}
}
$data['$collection'] = $collection->getId(); // Adding this param to make API easier for developers
$data['$id'] = $documentId == 'unique()' ? ID::unique() : $documentId;
$data['$permissions'] = $permissions;
$document = new Document($data);
// Users can only manage their own roles, API keys and Admin users can manage any
if (!$isAPIKey && !$isPrivilegedUser) {
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(', ', Authorization::getRoles()) . ')');
}
}
}
}
$document->setAttribute('$permissions', $permissions);
};
$checkPermissions = function (Document $collection, Document $document, string $permission) use (&$checkPermissions, $dbForProject, $database) {
$documentSecurity = $collection->getAttribute('documentSecurity', false);
@@ -3023,10 +3062,29 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
}
};
$checkPermissions($collection, $document, Database::PERMISSION_CREATE);
$documents = array_map(function ($document) use ($collection, $permissions, $checkPermissions, $isBulk, $documentId, $setPermissions) {
$document['$collection'] = $collection->getId();
if (!$isBulk) {
$document['$id'] = $documentId == 'unique()' ? ID::unique() : $documentId;
} else {
if (empty($document['$id'])) {
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, '$id is required inside documents when creating bulk documents');
}
$document['$id'] = $document['$id'] == 'unique()' ? ID::unique() : $document['$id'];
}
$document = new Document($document);
$setPermissions($document, $permissions);
$checkPermissions($collection, $document, Database::PERMISSION_CREATE);
return $document;
}, $documents);
try {
$document = $dbForProject->createDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $document);
$dbForProject->createDocuments('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documents);
} catch (StructureException $e) {
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
} catch (DuplicateException $e) {
@@ -3035,8 +3093,15 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$queueForEvents
->setParam('databaseId', $databaseId)
->setParam('collectionId', $collection->getId())
->setContext('collection', $collection)
->setContext('database', $database);
// Add $collectionId and $databaseId for all documents
$processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) {
$processDocument = function (Document $collection, Document &$document) use (&$processDocument, $dbForProject, $database) {
$document->removeAttribute('$collection');
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$collectionId', $collection->getId());
@@ -3068,28 +3133,26 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
}
};
$processDocument($collection, $document);
foreach ($documents as $document) {
$processDocument($collection, $document);
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($document, Response::MODEL_DOCUMENT);
$relationships = \array_map(
fn ($document) => $document->getAttribute('key'),
\array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
)
);
$queueForEvents
->setParam('databaseId', $databaseId)
->setParam('collectionId', $collection->getId())
->setParam('documentId', $document->getId())
->setContext('collection', $collection)
->setContext('database', $database)
->setPayload($response->getPayload(), sensitive: $relationships);
if ($isBulk) {
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic(new Document([
'total' => count($documents),
'documents' => $documents
]), Response::MODEL_DOCUMENT_LIST);
} else {
$queueForEvents
->setParam('documentId', $document->getId())
->setEvent('databases.[databaseId].collections.[collectionId].documents.[documentId].create');
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($documents[0], Response::MODEL_DOCUMENT);
}
$queueForUsage
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection
@@ -3172,7 +3235,6 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
return false;
}
$document->removeAttribute('$collection');
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$collectionId', $collection->getId());
@@ -3688,6 +3750,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')
@@ -3808,6 +4005,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'])
+8
View File
@@ -90,10 +90,17 @@ $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;
}
if ($event === Database::EVENT_DOCUMENTS_DELETE) {
$value = -1 * $document->getAttribute('modified', 0);
} elseif ($event === Database::EVENT_DOCUMENTS_CREATE) {
$value = $document->getAttribute('modified', 0);
}
switch (true) {
case $document->getCollection() === 'teams':
$queueForUsage
@@ -496,6 +503,7 @@ App::init()
$dbForProject
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForUsage))
->on(Database::EVENT_DOCUMENTS_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,
+1
View File
@@ -113,6 +113,7 @@ const APP_LIMIT_COMPRESSION = 20_000_000; //20MB
const APP_LIMIT_ARRAY_PARAMS_SIZE = 100; // Default maximum of how many elements can there be in API parameter that expects array value
const APP_LIMIT_ARRAY_LABELS_SIZE = 1000; // Default maximum of how many labels elements can there be in API parameter that expects array value
const APP_LIMIT_ARRAY_ELEMENT_SIZE = 4096; // Default maximum length of element in array parameter represented by maximum URL length.
const APP_LIMIT_ARRAY_DOCUMENTS_SIZE = 10_000; // Default maximum of how many documents can be inserted in a single request
const APP_LIMIT_SUBQUERY = 1000;
const APP_LIMIT_SUBSCRIBERS_SUBQUERY = 1_000_000;
const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate period
+5 -1
View File
@@ -51,7 +51,7 @@
"utopia-php/cache": "0.11.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "0.53.16",
"utopia-php/database": "0.53.27",
"utopia-php/domains": "0.5.*",
"utopia-php/dsn": "0.2.1",
"utopia-php/framework": "0.33.*",
@@ -96,6 +96,10 @@
"config": {
"platform": {
"php": "8.3"
},
"allow-plugins": {
"php-http/discovery": false,
"tbachert/spi": false
}
}
}
Generated
+1908 -402
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1 +1 @@
Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection) API or directly from your database console.
Create new Documents. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection) API or directly from your database console.
@@ -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;
}
@@ -122,8 +122,12 @@ class OpenAPI3 extends Format
$scope = $route->getLabel('scope', '');
$consumes = [$route->getLabel('sdk.request.type', 'application/json')];
$method = $route->getLabel('sdk.method', \uniqid());
if (!empty($method) && is_array($method)) {
$method = array_keys($method)[0];
}
$desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath(__DIR__ . '/../../../../' . $route->getLabel('sdk.description', '')) : null;
$produces = $route->getLabel('sdk.response.type', null);
$model = $route->getLabel('sdk.response.model', 'none');
@@ -178,6 +182,10 @@ class OpenAPI3 extends Format
],
];
if (is_array($route->getLabel('sdk.method', ''))) {
$temp['x-appwrite']['multiplex'] = $route->getLabel('sdk.method', '');
}
foreach ($this->models as $value) {
if (\is_array($model)) {
$model = \array_map(fn ($m) => $m === $value->getType() ? $value : $m, $model);
@@ -121,6 +121,11 @@ class Swagger2 extends Format
$consumes = [$route->getLabel('sdk.request.type', 'application/json')];
$method = $route->getLabel('sdk.method', \uniqid());
if (!empty($method) && is_array($method)) {
$method = array_keys($method)[0];
}
$desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath(__DIR__ . '/../../../../' . $route->getLabel('sdk.description', '')) : null;
$produces = $route->getLabel('sdk.response.type', null);
$model = $route->getLabel('sdk.response.model', 'none');
@@ -181,6 +186,10 @@ class Swagger2 extends Format
$temp['produces'][] = $produces;
}
if (is_array($route->getLabel('sdk.method', ''))) {
$temp['x-appwrite']['multiplex'] = $route->getLabel('sdk.method', '');
}
foreach ($this->models as $value) {
if (\is_array($model)) {
$model = \array_map(fn ($m) => $m === $value->getType() ? $value : $m, $model);
+32
View File
@@ -0,0 +1,32 @@
import { check } from "k6";
import http from "k6/http";
const amount = 100_000;
const databaseId = "674918b20017411b94b2";
const collectionId = "674918b4002b46c47d5d";
const documents = Array(amount).fill({
$id: "unique()",
name: "asd",
});
export default function () {
const payload = JSON.stringify({
documents,
});
const res = http.post(`http://localhost/v1/databases/${databaseId}/collections/${collectionId}/documents`,
payload,
{
headers: {
"X-Appwrite-Key": "standard_fa89c4834660f39e95ca2c2996fe7dd4ff498725e37c09323234c009570c1719f1c10610bf3541cf9ead120c107e41397a4eae1c787c83bdf577857bbc5963341641c77f582cc41e11a0d50eb4c2e4b1fda74418a8b9a253d6e63008e33560ba35310b9dc2fed5f09ca599e646f744cc6308b8ccd27ff04f9e498ec5a5f2c3db",
"X-Appwrite-Project": "674818bc0017934d58dd",
"Content-Type": "application/json",
},
}
);
check(res, {
"status is 200": (r) => r.status === 201,
});
}
File diff suppressed because it is too large Load Diff
@@ -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
{
@@ -890,4 +891,623 @@ class DatabasesCustomClientTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
}
public function testBulkCreatePermissions(): 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 Create'
]);
$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 Create',
'documentSecurity' => false,
'permissions' => [
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']);
sleep(2);
// TEST FAIL - Can't create document with missing collection level permissions
$response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$data['$id']}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => [
[
'$id' => ID::unique(),
'number' => 1,
],
[
'$id' => ID::unique(),
'number' => 2,
],
[
'$id' => ID::unique(),
'number' => 3,
],
],
]);
$this->assertEquals(401, $response['headers']['status-code']);
$this->assertEquals('The current user is not authorized to perform the requested action.', $response['body']['message']);
// TEST FAIL - Can't create document with missing document level permissions
$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 Creates Perms',
'documentSecurity' => true
]);
$this->assertEquals(200, $collection['headers']['status-code']);
$response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$data['$id']}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => [
[
'$id' => ID::unique(),
'number' => 1,
],
[
'$id' => ID::unique(),
'number' => 2,
],
[
'$id' => ID::unique(),
'number' => 3,
],
],
]);
$this->assertEquals(401, $response['headers']['status-code']);
$this->assertEquals('The current user is not authorized to perform the requested action.', $response['body']['message']);
// TEST FAIL - Can't create document with permissions that aren't our own.
$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 Creates Perms',
'documentSecurity' => true,
'permissions' => [
Permission::create(Role::user($this->getUser()['$id'])),
Permission::read(Role::user($this->getUser()['$id'])),
Permission::update(Role::user($this->getUser()['$id'])),
Permission::delete(Role::user($this->getUser()['$id'])),
]
]);
$this->assertEquals(200, $collection['headers']['status-code']);
$response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$data['$id']}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => [
[
'$id' => ID::unique(),
'number' => 1,
'$permissions' => [
Permission::create(Role::user('aaaaaa')),
Permission::read(Role::user('aaaaaa')),
Permission::update(Role::user('aaaaaa')),
Permission::delete(Role::user('aaaaaa')),
]
],
[
'$id' => ID::unique(),
'number' => 2,
'$permissions' => [
Permission::create(Role::user('aaaaaa')),
Permission::read(Role::user('aaaaaa')),
Permission::update(Role::user('aaaaaa')),
Permission::delete(Role::user('aaaaaa')),
]
],
[
'$id' => ID::unique(),
'number' => 3,
'$permissions' => [
Permission::create(Role::user('aaaaaa')),
Permission::read(Role::user('aaaaaa')),
Permission::update(Role::user('aaaaaa')),
Permission::delete(Role::user('aaaaaa')),
]
],
],
]);
$this->assertEquals(401, $response['headers']['status-code']);
// TEST SUCCESS - Can create document with our own permissions.
$response = $this->client->call(Client::METHOD_POST, "/databases/{$databaseId}/collections/{$data['$id']}/documents", array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'documents' => [
[
'$id' => ID::unique(),
'number' => 1,
'$permissions' => [
Permission::create(Role::user($this->getUser()['$id'])),
Permission::read(Role::user($this->getUser()['$id'])),
Permission::update(Role::user($this->getUser()['$id'])),
Permission::delete(Role::user($this->getUser()['$id'])),
]
],
[
'$id' => ID::unique(),
'number' => 2,
'$permissions' => [
Permission::create(Role::user($this->getUser()['$id'])),
Permission::read(Role::user($this->getUser()['$id'])),
Permission::update(Role::user($this->getUser()['$id'])),
Permission::delete(Role::user($this->getUser()['$id'])),
]
],
[
'$id' => ID::unique(),
'number' => 3,
'$permissions' => [
Permission::create(Role::user($this->getUser()['$id'])),
Permission::read(Role::user($this->getUser()['$id'])),
Permission::update(Role::user($this->getUser()['$id'])),
Permission::delete(Role::user($this->getUser()['$id'])),
]
],
],
]);
$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);