Merge remote-tracking branch 'origin/feat-bulk-operations' into feat-test-migrations

This commit is contained in:
Jake Barnby
2025-03-17 21:14:11 +13:00
31 changed files with 7782 additions and 2102 deletions
+277 -43
View File
@@ -4367,7 +4367,7 @@
"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.\n",
"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.\n",
"responses": {
"201": {
"description": "Document",
@@ -4398,6 +4398,51 @@
"server"
],
"packaging": false,
"methods": [
{
"name": "createDocument",
"parameters": [
"databaseId",
"collectionId",
"documentId",
"data",
"permissions"
],
"required": [
"databaseId",
"collectionId",
"documentId",
"data"
],
"responses": [
{
"code": 201,
"model": "#\/components\/schemas\/document"
}
],
"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.\n"
},
{
"name": "createDocuments",
"parameters": [
"databaseId",
"collectionId",
"documents"
],
"required": [
"databaseId",
"collectionId",
"documents"
],
"responses": [
{
"code": 201,
"model": "#\/components\/schemas\/documentList"
}
],
"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.\n"
}
],
"auth": {
"Project": []
}
@@ -4447,6 +4492,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).",
@@ -4455,11 +4508,192 @@
"type": "string"
}
}
},
"required": [
"documentId",
"data"
]
}
}
}
}
}
},
"patch": {
"summary": "Update documents",
"operationId": "databasesUpdateDocuments",
"tags": [
"databases"
],
"description": "Update all documents that match your queries, if no queries are submitted then all documents are updated. You can pass only specific fields to be 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,
"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": "{}"
},
"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,
"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"
}
}
}
}
}
}
@@ -4680,7 +4914,7 @@
},
"x-appwrite": {
"method": "deleteDocument",
"weight": 113,
"weight": 114,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4763,7 +4997,7 @@
},
"x-appwrite": {
"method": "listExecutions",
"weight": 306,
"weight": 308,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4848,7 +5082,7 @@
},
"x-appwrite": {
"method": "createExecution",
"weight": 305,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4962,7 +5196,7 @@
},
"x-appwrite": {
"method": "getExecution",
"weight": 307,
"weight": 309,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5035,7 +5269,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 331,
"weight": 333,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5086,7 +5320,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 330,
"weight": 332,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5137,7 +5371,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 117,
"weight": 119,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5188,7 +5422,7 @@
},
"x-appwrite": {
"method": "listCodes",
"weight": 118,
"weight": 120,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5239,7 +5473,7 @@
},
"x-appwrite": {
"method": "listContinents",
"weight": 122,
"weight": 124,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5290,7 +5524,7 @@
},
"x-appwrite": {
"method": "listCountries",
"weight": 119,
"weight": 121,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5341,7 +5575,7 @@
},
"x-appwrite": {
"method": "listCountriesEU",
"weight": 120,
"weight": 122,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5392,7 +5626,7 @@
},
"x-appwrite": {
"method": "listCountriesPhones",
"weight": 121,
"weight": 123,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5443,7 +5677,7 @@
},
"x-appwrite": {
"method": "listCurrencies",
"weight": 123,
"weight": 125,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5494,7 +5728,7 @@
},
"x-appwrite": {
"method": "listLanguages",
"weight": 124,
"weight": 126,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5545,7 +5779,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 376,
"weight": 378,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5627,7 +5861,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 380,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5701,7 +5935,7 @@
},
"x-appwrite": {
"method": "listFiles",
"weight": 208,
"weight": 210,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5786,7 +6020,7 @@
},
"x-appwrite": {
"method": "createFile",
"weight": 207,
"weight": 209,
"cookies": false,
"type": "upload",
"deprecated": false,
@@ -5883,7 +6117,7 @@
},
"x-appwrite": {
"method": "getFile",
"weight": 209,
"weight": 211,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5954,7 +6188,7 @@
},
"x-appwrite": {
"method": "updateFile",
"weight": 214,
"weight": 216,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6042,7 +6276,7 @@
},
"x-appwrite": {
"method": "deleteFile",
"weight": 215,
"weight": 217,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6108,7 +6342,7 @@
},
"x-appwrite": {
"method": "getFileDownload",
"weight": 211,
"weight": 213,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6174,7 +6408,7 @@
},
"x-appwrite": {
"method": "getFilePreview",
"weight": 210,
"weight": 212,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6390,7 +6624,7 @@
},
"x-appwrite": {
"method": "getFileView",
"weight": 212,
"weight": 214,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6463,7 +6697,7 @@
},
"x-appwrite": {
"method": "list",
"weight": 219,
"weight": 221,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6538,7 +6772,7 @@
},
"x-appwrite": {
"method": "create",
"weight": 218,
"weight": 220,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6622,7 +6856,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 220,
"weight": 222,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6683,7 +6917,7 @@
},
"x-appwrite": {
"method": "updateName",
"weight": 222,
"weight": 224,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6756,7 +6990,7 @@
},
"x-appwrite": {
"method": "delete",
"weight": 224,
"weight": 226,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6819,7 +7053,7 @@
},
"x-appwrite": {
"method": "listMemberships",
"weight": 226,
"weight": 228,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6904,7 +7138,7 @@
},
"x-appwrite": {
"method": "createMembership",
"weight": 225,
"weight": 227,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7014,7 +7248,7 @@
},
"x-appwrite": {
"method": "getMembership",
"weight": 227,
"weight": 229,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7085,7 +7319,7 @@
},
"x-appwrite": {
"method": "updateMembership",
"weight": 228,
"weight": 230,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7171,7 +7405,7 @@
},
"x-appwrite": {
"method": "deleteMembership",
"weight": 230,
"weight": 232,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7244,7 +7478,7 @@
},
"x-appwrite": {
"method": "updateMembershipStatus",
"weight": 229,
"weight": 231,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7341,7 +7575,7 @@
},
"x-appwrite": {
"method": "getPrefs",
"weight": 221,
"weight": 223,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7401,7 +7635,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
+277 -43
View File
@@ -4367,7 +4367,7 @@
"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.\n",
"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.\n",
"responses": {
"201": {
"description": "Document",
@@ -4398,6 +4398,51 @@
"server"
],
"packaging": false,
"methods": [
{
"name": "createDocument",
"parameters": [
"databaseId",
"collectionId",
"documentId",
"data",
"permissions"
],
"required": [
"databaseId",
"collectionId",
"documentId",
"data"
],
"responses": [
{
"code": 201,
"model": "#\/components\/schemas\/document"
}
],
"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.\n"
},
{
"name": "createDocuments",
"parameters": [
"databaseId",
"collectionId",
"documents"
],
"required": [
"databaseId",
"collectionId",
"documents"
],
"responses": [
{
"code": 201,
"model": "#\/components\/schemas\/documentList"
}
],
"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.\n"
}
],
"auth": {
"Project": []
}
@@ -4447,6 +4492,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).",
@@ -4455,11 +4508,192 @@
"type": "string"
}
}
},
"required": [
"documentId",
"data"
]
}
}
}
}
}
},
"patch": {
"summary": "Update documents",
"operationId": "databasesUpdateDocuments",
"tags": [
"databases"
],
"description": "Update all documents that match your queries, if no queries are submitted then all documents are updated. You can pass only specific fields to be 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,
"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": "{}"
},
"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,
"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"
}
}
}
}
}
}
@@ -4680,7 +4914,7 @@
},
"x-appwrite": {
"method": "deleteDocument",
"weight": 113,
"weight": 114,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4763,7 +4997,7 @@
},
"x-appwrite": {
"method": "listExecutions",
"weight": 306,
"weight": 308,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4848,7 +5082,7 @@
},
"x-appwrite": {
"method": "createExecution",
"weight": 305,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4962,7 +5196,7 @@
},
"x-appwrite": {
"method": "getExecution",
"weight": 307,
"weight": 309,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5035,7 +5269,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 331,
"weight": 333,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5086,7 +5320,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 330,
"weight": 332,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5137,7 +5371,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 117,
"weight": 119,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5188,7 +5422,7 @@
},
"x-appwrite": {
"method": "listCodes",
"weight": 118,
"weight": 120,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5239,7 +5473,7 @@
},
"x-appwrite": {
"method": "listContinents",
"weight": 122,
"weight": 124,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5290,7 +5524,7 @@
},
"x-appwrite": {
"method": "listCountries",
"weight": 119,
"weight": 121,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5341,7 +5575,7 @@
},
"x-appwrite": {
"method": "listCountriesEU",
"weight": 120,
"weight": 122,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5392,7 +5626,7 @@
},
"x-appwrite": {
"method": "listCountriesPhones",
"weight": 121,
"weight": 123,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5443,7 +5677,7 @@
},
"x-appwrite": {
"method": "listCurrencies",
"weight": 123,
"weight": 125,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5494,7 +5728,7 @@
},
"x-appwrite": {
"method": "listLanguages",
"weight": 124,
"weight": 126,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5545,7 +5779,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 376,
"weight": 378,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5627,7 +5861,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 380,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5701,7 +5935,7 @@
},
"x-appwrite": {
"method": "listFiles",
"weight": 208,
"weight": 210,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5786,7 +6020,7 @@
},
"x-appwrite": {
"method": "createFile",
"weight": 207,
"weight": 209,
"cookies": false,
"type": "upload",
"deprecated": false,
@@ -5883,7 +6117,7 @@
},
"x-appwrite": {
"method": "getFile",
"weight": 209,
"weight": 211,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5954,7 +6188,7 @@
},
"x-appwrite": {
"method": "updateFile",
"weight": 214,
"weight": 216,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6042,7 +6276,7 @@
},
"x-appwrite": {
"method": "deleteFile",
"weight": 215,
"weight": 217,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6108,7 +6342,7 @@
},
"x-appwrite": {
"method": "getFileDownload",
"weight": 211,
"weight": 213,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6174,7 +6408,7 @@
},
"x-appwrite": {
"method": "getFilePreview",
"weight": 210,
"weight": 212,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6390,7 +6624,7 @@
},
"x-appwrite": {
"method": "getFileView",
"weight": 212,
"weight": 214,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6463,7 +6697,7 @@
},
"x-appwrite": {
"method": "list",
"weight": 219,
"weight": 221,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6538,7 +6772,7 @@
},
"x-appwrite": {
"method": "create",
"weight": 218,
"weight": 220,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6622,7 +6856,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 220,
"weight": 222,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6683,7 +6917,7 @@
},
"x-appwrite": {
"method": "updateName",
"weight": 222,
"weight": 224,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6756,7 +6990,7 @@
},
"x-appwrite": {
"method": "delete",
"weight": 224,
"weight": 226,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6819,7 +7053,7 @@
},
"x-appwrite": {
"method": "listMemberships",
"weight": 226,
"weight": 228,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6904,7 +7138,7 @@
},
"x-appwrite": {
"method": "createMembership",
"weight": 225,
"weight": 227,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7014,7 +7248,7 @@
},
"x-appwrite": {
"method": "getMembership",
"weight": 227,
"weight": 229,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7085,7 +7319,7 @@
},
"x-appwrite": {
"method": "updateMembership",
"weight": 228,
"weight": 230,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7171,7 +7405,7 @@
},
"x-appwrite": {
"method": "deleteMembership",
"weight": 230,
"weight": 232,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7244,7 +7478,7 @@
},
"x-appwrite": {
"method": "updateMembershipStatus",
"weight": 229,
"weight": 231,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7341,7 +7575,7 @@
},
"x-appwrite": {
"method": "getPrefs",
"weight": 221,
"weight": 223,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7401,7 +7635,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
+275 -45
View File
@@ -4549,7 +4549,7 @@
"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.\n",
"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.\n",
"responses": {
"201": {
"description": "Document",
@@ -4576,6 +4576,51 @@
"server"
],
"packaging": false,
"methods": [
{
"name": "createDocument",
"parameters": [
"databaseId",
"collectionId",
"documentId",
"data",
"permissions"
],
"required": [
"databaseId",
"collectionId",
"documentId",
"data"
],
"responses": [
{
"code": 201,
"model": "#\/definitions\/document"
}
],
"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.\n"
},
{
"name": "createDocuments",
"parameters": [
"databaseId",
"collectionId",
"documents"
],
"required": [
"databaseId",
"collectionId",
"documents"
],
"responses": [
{
"code": 201,
"model": "#\/definitions\/documentList"
}
],
"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.\n"
}
],
"auth": {
"Project": []
}
@@ -4613,15 +4658,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).",
@@ -4631,11 +4685,187 @@
"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 no queries are submitted then all documents are updated. You can pass only specific fields to be 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,
"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": "{}"
},
"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,
"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"
}
}
}
}
}
]
@@ -4850,7 +5080,7 @@
},
"x-appwrite": {
"method": "deleteDocument",
"weight": 113,
"weight": 114,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4929,7 +5159,7 @@
},
"x-appwrite": {
"method": "listExecutions",
"weight": 306,
"weight": 308,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5011,7 +5241,7 @@
},
"x-appwrite": {
"method": "createExecution",
"weight": 305,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5129,7 +5359,7 @@
},
"x-appwrite": {
"method": "getExecution",
"weight": 307,
"weight": 309,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5200,7 +5430,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 331,
"weight": 333,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5273,7 +5503,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 330,
"weight": 332,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5346,7 +5576,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 117,
"weight": 119,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5399,7 +5629,7 @@
},
"x-appwrite": {
"method": "listCodes",
"weight": 118,
"weight": 120,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5452,7 +5682,7 @@
},
"x-appwrite": {
"method": "listContinents",
"weight": 122,
"weight": 124,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5505,7 +5735,7 @@
},
"x-appwrite": {
"method": "listCountries",
"weight": 119,
"weight": 121,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5558,7 +5788,7 @@
},
"x-appwrite": {
"method": "listCountriesEU",
"weight": 120,
"weight": 122,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5611,7 +5841,7 @@
},
"x-appwrite": {
"method": "listCountriesPhones",
"weight": 121,
"weight": 123,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5664,7 +5894,7 @@
},
"x-appwrite": {
"method": "listCurrencies",
"weight": 123,
"weight": 125,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5717,7 +5947,7 @@
},
"x-appwrite": {
"method": "listLanguages",
"weight": 124,
"weight": 126,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5770,7 +6000,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 376,
"weight": 378,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5854,7 +6084,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 380,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5926,7 +6156,7 @@
},
"x-appwrite": {
"method": "listFiles",
"weight": 208,
"weight": 210,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6008,7 +6238,7 @@
},
"x-appwrite": {
"method": "createFile",
"weight": 207,
"weight": 209,
"cookies": false,
"type": "upload",
"deprecated": false,
@@ -6099,7 +6329,7 @@
},
"x-appwrite": {
"method": "getFile",
"weight": 209,
"weight": 211,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6168,7 +6398,7 @@
},
"x-appwrite": {
"method": "updateFile",
"weight": 214,
"weight": 216,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6256,7 +6486,7 @@
},
"x-appwrite": {
"method": "deleteFile",
"weight": 215,
"weight": 217,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6327,7 +6557,7 @@
},
"x-appwrite": {
"method": "getFileDownload",
"weight": 211,
"weight": 213,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6398,7 +6628,7 @@
},
"x-appwrite": {
"method": "getFilePreview",
"weight": 210,
"weight": 212,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6597,7 +6827,7 @@
},
"x-appwrite": {
"method": "getFileView",
"weight": 212,
"weight": 214,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6668,7 +6898,7 @@
},
"x-appwrite": {
"method": "list",
"weight": 219,
"weight": 221,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6742,7 +6972,7 @@
},
"x-appwrite": {
"method": "create",
"weight": 218,
"weight": 220,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6833,7 +7063,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 220,
"weight": 222,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6894,7 +7124,7 @@
},
"x-appwrite": {
"method": "updateName",
"weight": 222,
"weight": 224,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6968,7 +7198,7 @@
},
"x-appwrite": {
"method": "delete",
"weight": 224,
"weight": 226,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7031,7 +7261,7 @@
},
"x-appwrite": {
"method": "listMemberships",
"weight": 226,
"weight": 228,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7113,7 +7343,7 @@
},
"x-appwrite": {
"method": "createMembership",
"weight": 225,
"weight": 227,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7227,7 +7457,7 @@
},
"x-appwrite": {
"method": "getMembership",
"weight": 227,
"weight": 229,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7296,7 +7526,7 @@
},
"x-appwrite": {
"method": "updateMembership",
"weight": 228,
"weight": 230,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7381,7 +7611,7 @@
},
"x-appwrite": {
"method": "deleteMembership",
"weight": 230,
"weight": 232,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7452,7 +7682,7 @@
},
"x-appwrite": {
"method": "updateMembershipStatus",
"weight": 229,
"weight": 231,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7547,7 +7777,7 @@
},
"x-appwrite": {
"method": "getPrefs",
"weight": 221,
"weight": 223,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7607,7 +7837,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
+275 -45
View File
@@ -4549,7 +4549,7 @@
"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.\n",
"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.\n",
"responses": {
"201": {
"description": "Document",
@@ -4576,6 +4576,51 @@
"server"
],
"packaging": false,
"methods": [
{
"name": "createDocument",
"parameters": [
"databaseId",
"collectionId",
"documentId",
"data",
"permissions"
],
"required": [
"databaseId",
"collectionId",
"documentId",
"data"
],
"responses": [
{
"code": 201,
"model": "#\/definitions\/document"
}
],
"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.\n"
},
{
"name": "createDocuments",
"parameters": [
"databaseId",
"collectionId",
"documents"
],
"required": [
"databaseId",
"collectionId",
"documents"
],
"responses": [
{
"code": 201,
"model": "#\/definitions\/documentList"
}
],
"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.\n"
}
],
"auth": {
"Project": []
}
@@ -4613,15 +4658,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).",
@@ -4631,11 +4685,187 @@
"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 no queries are submitted then all documents are updated. You can pass only specific fields to be 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,
"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": "{}"
},
"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,
"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"
}
}
}
}
}
]
@@ -4850,7 +5080,7 @@
},
"x-appwrite": {
"method": "deleteDocument",
"weight": 113,
"weight": 114,
"cookies": false,
"type": "",
"deprecated": false,
@@ -4929,7 +5159,7 @@
},
"x-appwrite": {
"method": "listExecutions",
"weight": 306,
"weight": 308,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5011,7 +5241,7 @@
},
"x-appwrite": {
"method": "createExecution",
"weight": 305,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5129,7 +5359,7 @@
},
"x-appwrite": {
"method": "getExecution",
"weight": 307,
"weight": 309,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5200,7 +5430,7 @@
},
"x-appwrite": {
"method": "query",
"weight": 331,
"weight": 333,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5273,7 +5503,7 @@
},
"x-appwrite": {
"method": "mutation",
"weight": 330,
"weight": 332,
"cookies": false,
"type": "graphql",
"deprecated": false,
@@ -5346,7 +5576,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 117,
"weight": 119,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5399,7 +5629,7 @@
},
"x-appwrite": {
"method": "listCodes",
"weight": 118,
"weight": 120,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5452,7 +5682,7 @@
},
"x-appwrite": {
"method": "listContinents",
"weight": 122,
"weight": 124,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5505,7 +5735,7 @@
},
"x-appwrite": {
"method": "listCountries",
"weight": 119,
"weight": 121,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5558,7 +5788,7 @@
},
"x-appwrite": {
"method": "listCountriesEU",
"weight": 120,
"weight": 122,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5611,7 +5841,7 @@
},
"x-appwrite": {
"method": "listCountriesPhones",
"weight": 121,
"weight": 123,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5664,7 +5894,7 @@
},
"x-appwrite": {
"method": "listCurrencies",
"weight": 123,
"weight": 125,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5717,7 +5947,7 @@
},
"x-appwrite": {
"method": "listLanguages",
"weight": 124,
"weight": 126,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5770,7 +6000,7 @@
},
"x-appwrite": {
"method": "createSubscriber",
"weight": 376,
"weight": 378,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5854,7 +6084,7 @@
},
"x-appwrite": {
"method": "deleteSubscriber",
"weight": 380,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@@ -5926,7 +6156,7 @@
},
"x-appwrite": {
"method": "listFiles",
"weight": 208,
"weight": 210,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6008,7 +6238,7 @@
},
"x-appwrite": {
"method": "createFile",
"weight": 207,
"weight": 209,
"cookies": false,
"type": "upload",
"deprecated": false,
@@ -6099,7 +6329,7 @@
},
"x-appwrite": {
"method": "getFile",
"weight": 209,
"weight": 211,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6168,7 +6398,7 @@
},
"x-appwrite": {
"method": "updateFile",
"weight": 214,
"weight": 216,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6256,7 +6486,7 @@
},
"x-appwrite": {
"method": "deleteFile",
"weight": 215,
"weight": 217,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6327,7 +6557,7 @@
},
"x-appwrite": {
"method": "getFileDownload",
"weight": 211,
"weight": 213,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6398,7 +6628,7 @@
},
"x-appwrite": {
"method": "getFilePreview",
"weight": 210,
"weight": 212,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6597,7 +6827,7 @@
},
"x-appwrite": {
"method": "getFileView",
"weight": 212,
"weight": 214,
"cookies": false,
"type": "location",
"deprecated": false,
@@ -6668,7 +6898,7 @@
},
"x-appwrite": {
"method": "list",
"weight": 219,
"weight": 221,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6742,7 +6972,7 @@
},
"x-appwrite": {
"method": "create",
"weight": 218,
"weight": 220,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6833,7 +7063,7 @@
},
"x-appwrite": {
"method": "get",
"weight": 220,
"weight": 222,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6894,7 +7124,7 @@
},
"x-appwrite": {
"method": "updateName",
"weight": 222,
"weight": 224,
"cookies": false,
"type": "",
"deprecated": false,
@@ -6968,7 +7198,7 @@
},
"x-appwrite": {
"method": "delete",
"weight": 224,
"weight": 226,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7031,7 +7261,7 @@
},
"x-appwrite": {
"method": "listMemberships",
"weight": 226,
"weight": 228,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7113,7 +7343,7 @@
},
"x-appwrite": {
"method": "createMembership",
"weight": 225,
"weight": 227,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7227,7 +7457,7 @@
},
"x-appwrite": {
"method": "getMembership",
"weight": 227,
"weight": 229,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7296,7 +7526,7 @@
},
"x-appwrite": {
"method": "updateMembership",
"weight": 228,
"weight": 230,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7381,7 +7611,7 @@
},
"x-appwrite": {
"method": "deleteMembership",
"weight": 230,
"weight": 232,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7452,7 +7682,7 @@
},
"x-appwrite": {
"method": "updateMembershipStatus",
"weight": 229,
"weight": 231,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7547,7 +7777,7 @@
},
"x-appwrite": {
"method": "getPrefs",
"weight": 221,
"weight": 223,
"cookies": false,
"type": "",
"deprecated": false,
@@ -7607,7 +7837,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
+1
View File
@@ -2006,6 +2006,7 @@ App::post('/v1/account/tokens/magic-url')
$senderEmail = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
$senderName = System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server');
$replyTo = "";
if ($smtpEnabled) {
+411 -66
View File
@@ -3115,7 +3115,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')
@@ -3137,33 +3136,79 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
model: Response::MODEL_DOCUMENT,
)
],
contentType: ContentType::JSON
contentType: ContentType::JSON,
parameters: [
'databaseId' => ['optional' => false],
'collectionId' => ['optional' => false],
'documentId' => ['optional' => false],
'data' => ['optional' => false],
'permissions' => ['optional' => true],
]
),
new Method(
namespace: 'databases',
name: 'createDocuments',
description: '/docs/references/databases/create-document.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_DOCUMENT_LIST,
)
],
contentType: ContentType::JSON,
parameters: [
'databaseId' => ['optional' => false],
'collectionId' => ['optional' => false],
'documents' => ['optional' => false],
]
)
]
)
->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_DOCUMENTS_CREATE), '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('queueForStatsUsage')
->action(function (string $databaseId, string $documentId, string $collectionId, string|array $data, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage) {
->inject('project')
->action(function (string $databaseId, ?string $documentId, string $collectionId, string|array|null $data, ?array $documents, ?array $permissions, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage, Document $project) {
$data = \is_string($data)
? \json_decode($data, true)
: $data;
$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::GENERAL_BAD_REQUEST, '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, set "$id" in each document instead');
}
if (!empty($documents) && !empty($permissions)) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Param "permissions" is disallowed when creating multiple documents, set "$permissions" in each document instead');
}
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if (!empty($data)) {
$isBulk = false;
$documents = [$data];
}
$database = Authorization::skip(fn () => $dbForProject->getDocument(
'databases',
$databaseId
));
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
@@ -3184,43 +3229,45 @@ 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) {
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);
};
$operations = 0;
@@ -3306,21 +3353,50 @@ 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 must be set in each document 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,
APP_LIMIT_DATABASE_BATCH
);
} catch (StructureException $e) {
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
} catch (DuplicateException $e) {
} catch (DuplicateException) {
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
} catch (NotFoundException $e) {
} catch (NotFoundException) {
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());
@@ -3352,34 +3428,35 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
}
};
$processDocument($collection, $document);
foreach ($documents as $document) {
$processDocument($collection, $document);
}
$queueForStatsUsage
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, $operations)
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), $operations)
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection
$response->addHeader('X-Debug-Operations', $operations);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($document, Response::MODEL_DOCUMENT);
->addHeader('X-Debug-Operations', $operations);
$relationships = \array_map(
fn ($document) => $document->getAttribute('key'),
\array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
)
);
if ($isBulk) {
$response->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->dynamic(
$documents[0],
Response::MODEL_DOCUMENT
);
}
$queueForEvents
->setParam('databaseId', $databaseId)
->setParam('collectionId', $collection->getId())
->setParam('documentId', $document->getId())
->setContext('collection', $collection)
->setContext('database', $database)
->setPayload($response->getPayload(), sensitive: $relationships);
});
App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
@@ -3467,7 +3544,6 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
$operations++;
$document->removeAttribute('$collection');
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$collectionId', $collection->getId());
@@ -4037,6 +4113,155 @@ 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', new Method(
namespace: 'databases',
name: 'updateDocuments',
description: '/docs/references/databases/update-documents.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_DOCUMENT_LIST,
)
],
contentType: ContentType::JSON
))
->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('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 $queries, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Document $project) {
$data = \is_string($data)
? \json_decode($data, true)
: $data;
if (empty($data)) {
throw new Exception(Exception::DOCUMENT_MISSING_PAYLOAD);
}
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
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);
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$permissions = $data['$permissions'] ?? null;
// 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,
APP_LIMIT_DATABASE_BATCH
)
);
$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')
@@ -4162,9 +4387,129 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
->setContext('database', $database)
->setPayload($response->output($document, Response::MODEL_DOCUMENT), sensitive: $relationships);
$queueForStatsUsage
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection
$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', new Method(
namespace: 'databases',
name: 'deleteDocuments',
description: '/docs/references/databases/delete-documents.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_DOCUMENT_LIST,
)
],
contentType: ContentType::JSON
))
->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('queueForStatsUsage')
->inject('project')
->action(function (string $databaseId, string $collectionId, array $queries, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, StatsUsage $queueForStatsUsage, 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);
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$documents = $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $queries) {
return $dbForProject->deleteDocuments(
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
$queries,
APP_LIMIT_DATABASE_BATCH
);
});
// DB Storage Calculation
$queueForStatsUsage
->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'])
+9
View File
@@ -92,10 +92,17 @@ $eventDatabaseListener = function (Document $project, Document $document, Respon
$usageDatabaseListener = function (string $event, Document $document, StatsUsage $queueForStatsUsage) {
$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':
$queueForStatsUsage->addMetric(METRIC_TEAMS, $value); // per project
@@ -508,6 +515,8 @@ App::init()
$dbForProject
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENTS_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENTS_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener(
$project,
$document,
+2
View File
@@ -26,6 +26,8 @@ const APP_LIMIT_SUBSCRIBERS_SUBQUERY = 1_000_000;
const APP_LIMIT_WRITE_RATE_DEFAULT = 60; // Default maximum write rate per rate period
const APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT = 60; // Default maximum write rate period in seconds
const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return in list API calls
const APP_LIMIT_DOCUMENTS_CREATE = 5_000; // Default maximum of how many documents can be inserted in a single request
const APP_LIMIT_DATABASE_BATCH = 1_000; // Default batch size for database operations
const APP_KEY_ACCESS = 24 * 60 * 60; // 24 hours
const APP_USER_ACCESS = 24 * 60 * 60; // 24 hours
const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours
+3 -3
View File
@@ -30,7 +30,7 @@
}
},
"require": {
"php": ">=8.0.0",
"php": ">=8.3.0",
"ext-curl": "*",
"ext-imagick": "*",
"ext-mbstring": "*",
@@ -99,8 +99,8 @@
"php": "8.3"
},
"allow-plugins": {
"php-http/discovery": false,
"tbachert/spi": false
"php-http/discovery": true,
"tbachert/spi": true
}
}
}
Generated
+2 -2
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "44c6436ced36b0b026139edba252052e",
"content-hash": "54fc27a2c8d4f5ab4959dbc93be319e1",
"packages": [
{
"name": "adhocore/jwt",
@@ -8406,7 +8406,7 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=8.0.0",
"php": ">=8.3.0",
"ext-curl": "*",
"ext-imagick": "*",
"ext-mbstring": "*",
+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 no queries are submitted then all documents are updated. You can pass only specific fields to be updated.
+13 -7
View File
@@ -85,13 +85,19 @@ App::post('/v1/account/create')
```php
App::post('/v1/account/jwt')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION])
->label('sdk.namespace', 'account')
->label('sdk.method', 'createJWT')
->label('sdk.description', '/docs/references/account/create-jwt.md')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_JWT)
->label('sdk', new Method(
namespace: 'account',
name: 'createJWT',
description: '/docs/references/account/create-jwt.md',
auth: [],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_JWT,
)
],
responseType: ResponseType::JSON,
))
```
#### Cache
+17 -10
View File
@@ -131,10 +131,8 @@ class OpenAPI3 extends Format
$additionalMethods = null;
if (is_array($sdk)) {
$mainSdk = array_shift($sdk);
$additionalMethods = $sdk;
$sdk = $mainSdk;
$sdk = $sdk[0];
}
/**
@@ -202,14 +200,16 @@ class OpenAPI3 extends Format
if (!empty($additionalMethods)) {
$temp['x-appwrite']['additional-methods'] = [];
$temp['x-appwrite']['methods'] = [];
foreach ($additionalMethods as $method) {
/** @var \Appwrite\SDK\Method $method */
$desc = $method->getDescriptionFilePath();
$additionalMethod = [
'name' => $method->getMethodName(),
'parameters' => [],
'required' => [],
'responses' => []
'responses' => [],
'description' => ($desc) ? \file_get_contents($desc) : '',
];
foreach ($method->getParameters() as $name => $param) {
@@ -222,13 +222,20 @@ class OpenAPI3 extends Format
foreach ($method->getResponses() as $response) {
/** @var \Appwrite\SDK\Response $response */
$additionalMethod['responses'][] = [
'code' => $response->getCode(),
'model' => '#/components/schemas/' . $response->getModel()
];
if (\is_array($response->getModel())) {
$additionalMethod['responses'][] = [
'code' => $response->getCode(),
'model' => \array_map(fn ($m) => '#/components/schemas/' . $m, $response->getModel())
];
} else {
$additionalMethod['responses'][] = [
'code' => $response->getCode(),
'model' => '#/components/schemas/' . $response->getModel()
];
}
}
$temp['x-appwrite']['additional-methods'][] = $additionalMethod;
$temp['x-appwrite']['methods'][] = $additionalMethod;
}
}
+17 -10
View File
@@ -130,10 +130,8 @@ class Swagger2 extends Format
$additionalMethods = null;
if (is_array($sdk)) {
$mainSdk = array_shift($sdk);
$additionalMethods = $sdk;
$sdk = $mainSdk;
$sdk = $sdk[0];
}
$consumes = [$sdk->getRequestType()];
@@ -203,15 +201,17 @@ class Swagger2 extends Format
}
if (!empty($additionalMethods)) {
$temp['x-appwrite']['additional-methods'] = [];
$temp['x-appwrite']['methods'] = [];
foreach ($additionalMethods as $method) {
/** @var \Appwrite\SDK\Method $method */
$desc = $method->getDescriptionFilePath();
$additionalMethod = [
'name' => $method->getMethodName(),
'parameters' => [],
'required' => [],
'responses' => [],
'description' => $method->getDescription(),
'description' => ($desc) ? \file_get_contents($desc) : '',
];
foreach ($method->getParameters() as $name => $param) {
@@ -224,13 +224,20 @@ class Swagger2 extends Format
foreach ($method->getResponses() as $response) {
/** @var \Appwrite\SDK\Response $response */
$additionalMethod['responses'][] = [
'code' => $response->getCode(),
'model' => '#/definitions/' . $response->getModel()
];
if (\is_array($response->getModel())) {
$additionalMethod['responses'][] = [
'code' => $response->getCode(),
'model' => \array_map(fn ($m) => '#/definitions/' . $m, $response->getModel())
];
} else {
$additionalMethod['responses'][] = [
'code' => $response->getCode(),
'model' => '#/definitions/' . $response->getModel()
];
}
}
$temp['x-appwrite']['additional-methods'][] = $additionalMethod;
$temp['x-appwrite']['methods'][] = $additionalMethod;
}
}
@@ -0,0 +1,127 @@
import { check, sleep } from "k6";
import http from "k6/http";
import { provisionProject, provisionDatabase, cleanup, unique } from "./utils.js";
const millionRecords = 1_000_000;
const batchSize = 10_000;
const numBatches = millionRecords / batchSize;
export function setup() {
const resources = provisionProject({
endpoint: 'http://localhost/v1',
email: 'test@test.com',
password: 'password123',
name: 'Test User',
projectName: 'Large Document Creation Test'
});
const { databaseId, collectionId } = provisionDatabase({
endpoint: 'http://localhost/v1',
apiHeaders: resources.apiHeaders
});
// Wait to ensure that provisioning is complete
sleep(5);
// Create an index for the collection
const index = {
key: "name",
type: "fulltext",
orders: ["ASC"],
attributes: ["name", "email"]
};
const indexRes = http.post(`http://localhost/v1/databases/${databaseId}/collections/${collectionId}/indexes`,
JSON.stringify(index), {
headers: resources.apiHeaders
});
console.log(indexRes.status);
check(indexRes, {
"status is 202": (r) => r.status === 202,
});
console.log(`----- Inserting ${millionRecords} documents in ${numBatches} batches of ${batchSize} -----`);
const timeStart = new Date();
const requests = [];
for (let i = 0; i < numBatches; i++) {
const docs = Array.from({ length: batchSize }, () => ({
$id: unique(),
name: "bulk_document",
age: Math.floor(Math.random() * 100),
email: `${unique()}@test.com`,
height: Math.random() * 100
}));
requests.push({
method: "POST",
url: `http://localhost/v1/databases/${databaseId}/collections/${collectionId}/documents`,
body: JSON.stringify({ documents: docs }),
params: {
headers: resources.apiHeaders,
timeout: '300s'
}
});
}
const responses = http.batch(requests);
responses.forEach((res, index) => {
if (res.status !== 201) {
throw new Error(`Batch ${index + 1} failed with status ${res.status}`);
}
});
const timeEnd = new Date();
const timeTaken = timeEnd - timeStart;
console.log(`Created 1 million documents in ${timeTaken} milliseconds`);
return {
databaseId,
collectionId,
apiHeaders: resources.apiHeaders,
resources
};
}
export default function (data) {
const docs = Array.from({ length: 10000 }, () => ({
$id: unique(),
name: "performance_document",
age: Math.floor(Math.random() * 100),
email: `${unique()}@test.com`,
height: Math.random() * 100
}));
const payload = JSON.stringify({ documents: docs });
const res = http.post(
`http://localhost/v1/databases/${data.databaseId}/collections/${data.collectionId}/documents`,
payload,
{
headers: data.apiHeaders,
timeout: '300s'
}
);
check(res, {
"status is 201": (r) => r.status === 201
});
sleep(1);
}
export function teardown(data) {
cleanup(data.resources);
}
export const options = {
scenarios: {
large_document_creation: {
executor: 'per-vu-iterations',
vus: 1,
iterations: 20,
exec: 'default'
}
}
};
@@ -0,0 +1,82 @@
import { check, sleep } from "k6";
import http from "k6/http";
import { provisionProject, provisionDatabase, cleanup, unique } from "./utils.js";
const amount = 10_000;
export function setup() {
const resources = provisionProject({
endpoint: 'http://localhost/v1',
email: 'test@test.com',
password: 'password123',
name: 'Test User',
projectName: 'Bulk Operations Test'
});
const { databaseId, collectionId } = provisionDatabase({
endpoint: 'http://localhost/v1',
apiHeaders: resources.apiHeaders
});
sleep(3); // Await Attributes to be provisioned
console.log(`----- Amount of documents: ${amount} -----`);
return {
databaseId,
collectionId,
apiHeaders: resources.apiHeaders,
resources
};
}
export function teardown(data) {
cleanup(data.resources);
}
let documents = Array(amount).fill({
$id: "unique()",
name: "asd",
});
documents = documents.map((document) => {
return {
...document,
age: Math.floor(Math.random() * 100),
email: `${unique()}@test.com`,
height: Math.random() * 100,
};
});
export default function (data) {
const payload = JSON.stringify({
documents,
});
const res = http.post(
`http://localhost/v1/databases/${data.databaseId}/collections/${data.collectionId}/documents`,
payload,
{
headers: data.apiHeaders
}
);
check(res, {
"status is 201": (r) => r.status === 201,
});
return {
resources: data.resources
};
}
export const options = {
scenarios: {
bulk_create: {
executor: 'per-vu-iterations',
vus: 1,
iterations: 20,
exec: 'default'
}
}
};
@@ -0,0 +1,144 @@
import { check, sleep } from "k6";
import http from "k6/http";
import { Trend } from "k6/metrics";
import { provisionProject, provisionDatabase, cleanup, unique } from "./utils.js";
// Custom Trend metric for light response time tracking
export const lightResponseTime = new Trend("light_response_time", true);
const BULK_AMOUNT = 100_000; // Heavy operation amount
const LIGHT_AMOUNT = 10; // Light operation amount
export function setup() {
// Set up two separate projects - one for bulk operations (noisy neighbor) and one for light operations
const heavyResources = provisionProject({
endpoint: 'http://localhost/v1',
email: 'heavy@test.com',
password: 'password123',
name: 'Heavy User',
projectName: 'Noisy Neighbor - Heavy'
});
const lightResources = provisionProject({
endpoint: 'http://localhost/v1',
email: 'light@test.com',
password: 'password123',
name: 'Light User',
projectName: 'Noisy Neighbor - Light'
});
// Set up databases for both projects
const heavy = provisionDatabase({
endpoint: 'http://localhost/v1',
apiHeaders: heavyResources.apiHeaders
});
const light = provisionDatabase({
endpoint: 'http://localhost/v1',
apiHeaders: lightResources.apiHeaders
});
sleep(3); // Await Attributes to be provisioned
console.log(`----- Heavy operations: ${BULK_AMOUNT} documents | Light operations: ${LIGHT_AMOUNT} document -----`);
return {
heavy: {
databaseId: heavy.databaseId,
collectionId: heavy.collectionId,
apiHeaders: heavyResources.apiHeaders,
resources: heavyResources
},
light: {
databaseId: light.databaseId,
collectionId: light.collectionId,
apiHeaders: lightResources.apiHeaders,
resources: lightResources
}
};
}
export function teardown(data) {
cleanup(data.heavy.resources);
cleanup(data.light.resources);
}
// Create document payloads
function createDocuments(amount) {
let documents = Array(amount).fill({
$id: "unique()",
name: "test",
});
return documents.map((document) => ({
...document,
age: Math.floor(Math.random() * 100),
email: `${unique()}@test.com`,
height: Math.random() * 100,
}));
}
// Heavy operation function
export function heavy(data) {
const documents = createDocuments(BULK_AMOUNT);
const payload = JSON.stringify({ documents });
const res = http.post(
`http://localhost/v1/databases/${data.heavy.databaseId}/collections/${data.heavy.collectionId}/documents`,
payload,
{
headers: data.heavy.apiHeaders
}
);
check(res, {
"heavy operation status is 201": (r) => r.status === 201,
});
}
// Light operation function
export function light(data) {
const documents = createDocuments(LIGHT_AMOUNT);
const payload = JSON.stringify({ documents });
const startTime = new Date();
const res = http.post(
`http://localhost/v1/databases/${data.light.databaseId}/collections/${data.light.collectionId}/documents`,
payload,
{
headers: data.light.apiHeaders
}
);
const duration = new Date() - startTime;
// Record the light operation response time using the custom Trend metric
lightResponseTime.add(duration);
check(res, {
"light operation status is 201": (r) => r.status === 201,
});
}
export const options = {
scenarios: {
// Heavy bulk operations running continuously
heavy_load: {
executor: 'constant-vus',
vus: 5,
duration: '30s',
exec: 'heavy'
},
// Light operations to measure impact
light_operations: {
executor: 'constant-arrival-rate',
rate: 5,
timeUnit: '1s',
duration: '30s',
preAllocatedVUs: 10,
exec: 'light'
}
},
thresholds: {
http_req_duration: ['p(95)<2000'], // 95% of requests should complete within 2s
}
};
+336
View File
@@ -0,0 +1,336 @@
import http from 'k6/http';
import { check } from 'k6';
/**
* @typedef {Object} AuthHeaders
* @property {string} 'Content-Type' - Content type header
* @property {string} 'Cookie' - Session cookie
* @property {string} 'X-Appwrite-Project' - Project ID header
*/
/**
* @typedef {Object} ApiHeaders
* @property {string} 'Content-Type' - Content type header
* @property {string} 'X-Appwrite-Project' - Project ID header
* @property {string} 'X-Appwrite-Key' - API key header
*/
/**
* @typedef {Object} ProvisionedResources
* @property {string} userId - The ID of the created user
* @property {string} teamId - The ID of the created team
* @property {string} projectId - The ID of the created project
* @property {string} cookies - Session cookies for authentication
* @property {AuthHeaders} headers - Headers for cookie-based authentication
* @property {string} apiKey - The API key secret
* @property {ApiHeaders} apiHeaders - Headers for API key authentication
*/
function assert(response, checkName, condition) {
const result = check(response, {
[checkName]: condition
});
if (!result) {
console.error(`Assertion failed: ${checkName}`);
console.error(`Response status: ${response.status}`);
console.error(`Response body: ${response.body}`);
throw new Error(`Assertion failed: ${checkName}`);
}
}
/**
* Provisions an Appwrite project setup including:
* - Account creation
* - Session creation
* - Team creation
* - Project creation
* - API Key creation
*
* @param {Object} config Configuration object
* @param {string} config.endpoint Base endpoint URL (e.g., 'http://localhost:80/v1')
* @param {string} config.email Email for account creation
* @param {string} config.password Password for account creation
* @param {string} config.name Name for account creation
* @param {string} config.projectName Name for the project
* @returns {ProvisionedResources} Object containing all created resource IDs and session information
*/
export function provisionProject(config) {
const {
endpoint,
email,
password,
name,
projectName,
} = config;
// Step 1: Create Account
const accountResponse = http.post(`${endpoint}/account`, JSON.stringify({
userId: 'unique()',
email,
password,
name
}), {
headers: {
'Content-Type': 'application/json',
}
});
assert(accountResponse, 'account created successfully', (r) => r.status === 201 || r.status === 409);
const userId = accountResponse.json('$id');
// Step 2: Create Session
const sessionResponse = http.post(`${endpoint}/account/sessions/email`, JSON.stringify({
email,
password
}), {
headers: {
'Content-Type': 'application/json',
}
});
assert(sessionResponse, 'session created successfully', (r) => r.status === 201);
// Keep manual control of the cookies to allow for simultaneous requests
const jar = http.cookieJar();
jar.clear(`${endpoint}`);
// Extract cookies for subsequent requests
const cookies = sessionResponse.headers['Set-Cookie'];
// Common headers for authenticated requests
const authHeaders = {
'Content-Type': 'application/json',
'Cookie': cookies
};
// Step 3: Create Team
const teamResponse = http.post(`${endpoint}/teams`, JSON.stringify({
teamId: 'unique()',
name: `${projectName} Team`
}), {
headers: authHeaders
});
assert(teamResponse, 'team created successfully', (r) => r.status === 201);
const teamId = teamResponse.json('$id');
// Step 4: Create Project
const projectResponse = http.post(`${endpoint}/projects`, JSON.stringify({
projectId: 'unique()',
name: projectName,
teamId: teamId
}), {
headers: authHeaders
});
assert(projectResponse, 'project created successfully', (r) => r.status === 201);
const projectId = projectResponse.json('$id');
// Step 5: Create API Key
const apiKeyResponse = http.post(`${endpoint}/projects/${projectId}/keys`, JSON.stringify({
name: 'Test API Key',
scopes: SCOPES, // All permissions
}), {
headers: authHeaders
});
assert(apiKeyResponse, 'api key created successfully', (r) => r.status === 201);
const apiKey = apiKeyResponse.json('secret');
// Create a new headers object for API key authentication
const apiHeaders = {
'Content-Type': 'application/json',
'X-Appwrite-Project': projectId,
'X-Appwrite-Key': apiKey
};
// Return all created resources and session info
return {
endpoint,
userId,
teamId,
projectId,
cookies,
headers: authHeaders,
apiKey,
apiHeaders
};
}
/**
* Example usage:
*
* const config = {
* endpoint: 'http://localhost:80/v1',
* email: 'test@example.com',
* password: 'complex-password',
* name: 'Test User',
* projectName: 'Test Project'
* };
*
* const resources = provisionProject(config);
*/
const SCOPES = [
"sessions.write",
"users.read",
"users.write",
"teams.read",
"teams.write",
"databases.read",
"databases.write",
"collections.read",
"collections.write",
"attributes.read",
"attributes.write",
"indexes.read",
"indexes.write",
"documents.read",
"documents.write",
"files.read",
"files.write",
"buckets.read",
"buckets.write",
"functions.read",
"functions.write",
"execution.read",
"execution.write",
"targets.read",
"targets.write",
"providers.read",
"providers.write",
"messages.read",
"messages.write",
"topics.read",
"topics.write",
"subscribers.read",
"subscribers.write",
"locale.read",
"avatars.read",
"health.read",
"migrations.read",
"migrations.write"
]
export function provisionDatabase(config) {
const {
endpoint,
apiHeaders
} = config;
// Create database
const databaseResponse = http.post(
`${endpoint}/databases`,
JSON.stringify({
databaseId: 'unique()',
name: 'Bulk Test DB'
}),
{ headers: apiHeaders }
);
assert(databaseResponse, 'database created successfully', (r) => r.status === 201);
const databaseId = databaseResponse.json('$id');
// Create collection
const collectionResponse = http.post(
`${endpoint}/databases/${databaseId}/collections`,
JSON.stringify({
collectionId: 'unique()',
name: 'Bulk Test Collection',
permissions: ['read("any")', 'write("any")'],
documentSecurity: false
}),
{ headers: apiHeaders }
);
assert(collectionResponse, 'collection created successfully', (r) => r.status === 201);
const collectionId = collectionResponse.json('$id');
// Create name attribute
const nameAttributeResponse = http.post(
`${endpoint}/databases/${databaseId}/collections/${collectionId}/attributes/string`,
JSON.stringify({
key: 'name',
size: 100,
required: false,
default: null,
array: false,
encrypt: false
}),
{ headers: apiHeaders }
);
assert(nameAttributeResponse, 'name attribute created successfully', (r) => r.status === 202);
// Create age attribute
const ageAttributeResponse = http.post(
`${endpoint}/databases/${databaseId}/collections/${collectionId}/attributes/integer`,
JSON.stringify({
key: 'age',
required: false,
}),
{ headers: apiHeaders }
);
assert(ageAttributeResponse, 'age attribute created successfully', (r) => r.status === 202);
// Create email attribute
const emailAttributeResponse = http.post(
`${endpoint}/databases/${databaseId}/collections/${collectionId}/attributes/email`,
JSON.stringify({
key: 'email',
required: false,
}),
{ headers: apiHeaders }
);
assert(emailAttributeResponse, 'email attribute created successfully', (r) => r.status === 202);
// Create height attribute
const heightAttributeResponse = http.post(
`${endpoint}/databases/${databaseId}/collections/${collectionId}/attributes/float`,
JSON.stringify({
key: 'height',
required: false,
}),
{ headers: apiHeaders }
);
assert(heightAttributeResponse, 'height attribute created successfully', (r) => r.status === 202);
return {
databaseId,
collectionId
};
}
export function cleanup(config) {
const {
endpoint,
teamId,
headers
} = config;
// Delete Organization
const organizationResponse = http.del(
`${endpoint}/teams/${teamId}`,
null,
{
headers
}
);
assert(organizationResponse, 'organization deleted successfully', (r) => r.status === 204);
}
export function unique() {
const timestamp = Date.now().toString(36);
const randomPart = Math.random().toString(36).substring(2, 15);
return `${timestamp}-${randomPart}`;
}
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']);
}
}
@@ -674,6 +674,7 @@ class RealtimeCustomClientTest extends Scope
$user = $this->getUser();
$session = $user['session'] ?? '';
$projectId = $this->getProject()['$id'];
$documentIds = [];
$client = $this->getWebsocket(['documents', 'collections'], [
'origin' => 'http://localhost',
@@ -763,6 +764,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);