Merge pull request #9065 from appwrite/feat-bulk-operations

Feat bulk operations
This commit is contained in:
Jake Barnby 2025-05-08 14:26:52 +00:00 committed by GitHub
commit 932ed94b8c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
104 changed files with 8345 additions and 152389 deletions

View file

@ -245,7 +245,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '16.0.0',
'version' => '16.1.0-rc.1',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@ -263,7 +263,7 @@ return [
[
'key' => 'deno',
'name' => 'Deno',
'version' => '14.0.0',
'version' => '14.1.0-rc.1',
'url' => 'https://github.com/appwrite/sdk-for-deno',
'package' => 'https://deno.land/x/appwrite',
'enabled' => true,
@ -281,7 +281,7 @@ return [
[
'key' => 'php',
'name' => 'PHP',
'version' => '14.0.0',
'version' => '14.1.0-rc.1',
'url' => 'https://github.com/appwrite/sdk-for-php',
'package' => 'https://packagist.org/packages/appwrite/appwrite',
'enabled' => true,
@ -299,7 +299,7 @@ return [
[
'key' => 'python',
'name' => 'Python',
'version' => '10.0.0',
'version' => '10.1.0-rc.1',
'url' => 'https://github.com/appwrite/sdk-for-python',
'package' => 'https://pypi.org/project/appwrite/',
'enabled' => true,
@ -317,7 +317,7 @@ return [
[
'key' => 'ruby',
'name' => 'Ruby',
'version' => '15.0.0',
'version' => '15.1.0-rc.1',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,
@ -335,7 +335,7 @@ return [
[
'key' => 'go',
'name' => 'Go',
'version' => '0.5.0',
'version' => '0.6.0-rc.1',
'url' => 'https://github.com/appwrite/sdk-for-go',
'package' => 'https://github.com/appwrite/sdk-for-go',
'enabled' => true,
@ -353,7 +353,7 @@ return [
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.12.0',
'version' => '0.13.0-rc.1',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => true,
@ -371,7 +371,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '15.0.0',
'version' => '15.1.0-rc.1',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
@ -389,7 +389,7 @@ return [
[
'key' => 'kotlin',
'name' => 'Kotlin',
'version' => '8.0.0',
'version' => '8.1.0-rc.1',
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
'enabled' => true,
@ -411,7 +411,7 @@ return [
[
'key' => 'swift',
'name' => 'Swift',
'version' => '9.0.0',
'version' => '9.1.0-rc.1',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,

View file

@ -65,9 +65,6 @@ return [
'tests' => false,
'optional' => true,
'icon' => '/images/services/databases.png',
'globalAttributes' => [
'databaseId'
]
],
'locale' => [
'key' => 'locale',

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -2646,7 +2646,10 @@
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-token-email.md",
"rate-limit": 10,
"rate-time": 3600,
"rate-key": "url:{url},email:{param-email}",
"rate-key": [
"url:{url},email:{param-email}",
"url:{url},ip:{ip}"
],
"scope": "sessions.write",
"platforms": [
"server",
@ -4427,7 +4430,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 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.",
"responses": {
"201": {
"description": "Document",
@ -4459,6 +4462,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 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."
},
{
"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": []
}
@ -4515,12 +4563,16 @@
"items": {
"type": "string"
}
},
"documents": {
"type": "array",
"description": "Array of documents data as JSON objects.",
"x-example": null,
"items": {
"type": "object"
}
}
},
"required": [
"documentId",
"data"
]
}
}
}
}
@ -4744,7 +4796,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 113,
"weight": 115,
"cookies": false,
"type": "",
"deprecated": false,
@ -4828,7 +4880,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 305,
"weight": 308,
"cookies": false,
"type": "",
"deprecated": false,
@ -4914,7 +4966,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 304,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@ -5029,7 +5081,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 306,
"weight": 309,
"cookies": false,
"type": "",
"deprecated": false,
@ -5103,7 +5155,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 330,
"weight": 333,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -5155,7 +5207,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 329,
"weight": 332,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -5207,7 +5259,7 @@
"x-appwrite": {
"method": "get",
"group": null,
"weight": 117,
"weight": 120,
"cookies": false,
"type": "",
"deprecated": false,
@ -5259,7 +5311,7 @@
"x-appwrite": {
"method": "listCodes",
"group": null,
"weight": 118,
"weight": 121,
"cookies": false,
"type": "",
"deprecated": false,
@ -5311,7 +5363,7 @@
"x-appwrite": {
"method": "listContinents",
"group": null,
"weight": 122,
"weight": 125,
"cookies": false,
"type": "",
"deprecated": false,
@ -5363,7 +5415,7 @@
"x-appwrite": {
"method": "listCountries",
"group": null,
"weight": 119,
"weight": 122,
"cookies": false,
"type": "",
"deprecated": false,
@ -5415,7 +5467,7 @@
"x-appwrite": {
"method": "listCountriesEU",
"group": null,
"weight": 120,
"weight": 123,
"cookies": false,
"type": "",
"deprecated": false,
@ -5467,7 +5519,7 @@
"x-appwrite": {
"method": "listCountriesPhones",
"group": null,
"weight": 121,
"weight": 124,
"cookies": false,
"type": "",
"deprecated": false,
@ -5519,7 +5571,7 @@
"x-appwrite": {
"method": "listCurrencies",
"group": null,
"weight": 123,
"weight": 126,
"cookies": false,
"type": "",
"deprecated": false,
@ -5571,7 +5623,7 @@
"x-appwrite": {
"method": "listLanguages",
"group": null,
"weight": 124,
"weight": 127,
"cookies": false,
"type": "",
"deprecated": false,
@ -5623,7 +5675,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 375,
"weight": 378,
"cookies": false,
"type": "",
"deprecated": false,
@ -5706,7 +5758,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 379,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@ -5781,7 +5833,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 207,
"weight": 210,
"cookies": false,
"type": "",
"deprecated": false,
@ -5867,7 +5919,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 206,
"weight": 209,
"cookies": false,
"type": "upload",
"deprecated": false,
@ -5965,7 +6017,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 208,
"weight": 211,
"cookies": false,
"type": "",
"deprecated": false,
@ -6037,7 +6089,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 213,
"weight": 216,
"cookies": false,
"type": "",
"deprecated": false,
@ -6126,7 +6178,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 214,
"weight": 217,
"cookies": false,
"type": "",
"deprecated": false,
@ -6193,7 +6245,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 210,
"weight": 213,
"cookies": false,
"type": "location",
"deprecated": false,
@ -6260,7 +6312,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 209,
"weight": 212,
"cookies": false,
"type": "location",
"deprecated": false,
@ -6477,7 +6529,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 211,
"weight": 214,
"cookies": false,
"type": "location",
"deprecated": false,
@ -6551,7 +6603,7 @@
"x-appwrite": {
"method": "list",
"group": "teams",
"weight": 218,
"weight": 221,
"cookies": false,
"type": "",
"deprecated": false,
@ -6627,7 +6679,7 @@
"x-appwrite": {
"method": "create",
"group": "teams",
"weight": 217,
"weight": 220,
"cookies": false,
"type": "",
"deprecated": false,
@ -6712,7 +6764,7 @@
"x-appwrite": {
"method": "get",
"group": "teams",
"weight": 219,
"weight": 222,
"cookies": false,
"type": "",
"deprecated": false,
@ -6774,7 +6826,7 @@
"x-appwrite": {
"method": "updateName",
"group": "teams",
"weight": 221,
"weight": 224,
"cookies": false,
"type": "",
"deprecated": false,
@ -6848,7 +6900,7 @@
"x-appwrite": {
"method": "delete",
"group": "teams",
"weight": 223,
"weight": 226,
"cookies": false,
"type": "",
"deprecated": false,
@ -6912,7 +6964,7 @@
"x-appwrite": {
"method": "listMemberships",
"group": "memberships",
"weight": 225,
"weight": 228,
"cookies": false,
"type": "",
"deprecated": false,
@ -6998,7 +7050,7 @@
"x-appwrite": {
"method": "createMembership",
"group": "memberships",
"weight": 224,
"weight": 227,
"cookies": false,
"type": "",
"deprecated": false,
@ -7109,7 +7161,7 @@
"x-appwrite": {
"method": "getMembership",
"group": "memberships",
"weight": 226,
"weight": 229,
"cookies": false,
"type": "",
"deprecated": false,
@ -7181,7 +7233,7 @@
"x-appwrite": {
"method": "updateMembership",
"group": "memberships",
"weight": 227,
"weight": 230,
"cookies": false,
"type": "",
"deprecated": false,
@ -7268,7 +7320,7 @@
"x-appwrite": {
"method": "deleteMembership",
"group": "memberships",
"weight": 229,
"weight": 232,
"cookies": false,
"type": "",
"deprecated": false,
@ -7342,7 +7394,7 @@
"x-appwrite": {
"method": "updateMembershipStatus",
"group": "memberships",
"weight": 228,
"weight": 231,
"cookies": false,
"type": "",
"deprecated": false,
@ -7440,7 +7492,7 @@
"x-appwrite": {
"method": "getPrefs",
"group": "teams",
"weight": 220,
"weight": 223,
"cookies": false,
"type": "",
"deprecated": false,
@ -7501,7 +7553,7 @@
"x-appwrite": {
"method": "updatePrefs",
"group": "teams",
"weight": 222,
"weight": 225,
"cookies": false,
"type": "",
"deprecated": false,
@ -7564,85 +7616,67 @@
"tags": [
{
"name": "account",
"description": "The Account service allows you to authenticate and manage a user account.",
"x-globalAttributes": []
"description": "The Account service allows you to authenticate and manage a user account."
},
{
"name": "avatars",
"description": "The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars.",
"x-globalAttributes": []
"description": "The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars."
},
{
"name": "databases",
"description": "The Databases service allows you to create structured collections of documents, query and filter lists of documents",
"x-globalAttributes": [
"databaseId"
]
"description": "The Databases service allows you to create structured collections of documents, query and filter lists of documents"
},
{
"name": "locale",
"description": "The Locale service allows you to customize your app based on your users' location.",
"x-globalAttributes": []
"description": "The Locale service allows you to customize your app based on your users' location."
},
{
"name": "health",
"description": "The Health service allows you to both validate and monitor your Appwrite server's health.",
"x-globalAttributes": []
"description": "The Health service allows you to both validate and monitor your Appwrite server's health."
},
{
"name": "projects",
"description": "The Project service allows you to manage all the projects in your Appwrite server.",
"x-globalAttributes": []
"description": "The Project service allows you to manage all the projects in your Appwrite server."
},
{
"name": "project",
"description": "The Project service allows you to manage all the projects in your Appwrite server.",
"x-globalAttributes": []
"description": "The Project service allows you to manage all the projects in your Appwrite server."
},
{
"name": "storage",
"description": "The Storage service allows you to manage your project files.",
"x-globalAttributes": []
"description": "The Storage service allows you to manage your project files."
},
{
"name": "teams",
"description": "The Teams service allows you to group users of your project and to enable them to share read and write access to your project resources",
"x-globalAttributes": []
"description": "The Teams service allows you to group users of your project and to enable them to share read and write access to your project resources"
},
{
"name": "users",
"description": "The Users service allows you to manage your project users.",
"x-globalAttributes": []
"description": "The Users service allows you to manage your project users."
},
{
"name": "functions",
"description": "The Functions Service allows you view, create and manage your Cloud Functions.",
"x-globalAttributes": []
"description": "The Functions Service allows you view, create and manage your Cloud Functions."
},
{
"name": "proxy",
"description": "The Proxy Service allows you to configure actions for your domains beyond DNS configuration.",
"x-globalAttributes": []
"description": "The Proxy Service allows you to configure actions for your domains beyond DNS configuration."
},
{
"name": "graphql",
"description": "The GraphQL API allows you to query and mutate your Appwrite server using GraphQL.",
"x-globalAttributes": []
"description": "The GraphQL API allows you to query and mutate your Appwrite server using GraphQL."
},
{
"name": "console",
"description": "The Console service allows you to interact with console relevant informations.",
"x-globalAttributes": []
"description": "The Console service allows you to interact with console relevant informations."
},
{
"name": "migrations",
"description": "The Migrations service allows you to migrate third-party data to your Appwrite project.",
"x-globalAttributes": []
"description": "The Migrations service allows you to migrate third-party data to your Appwrite project."
},
{
"name": "messaging",
"description": "The Messaging service allows you to send messages to any provider type (SMTP, push notification, SMS, etc.).",
"x-globalAttributes": []
"description": "The Messaging service allows you to send messages to any provider type (SMTP, push notification, SMS, etc.)."
}
],
"components": {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2646,7 +2646,10 @@
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-token-email.md",
"rate-limit": 10,
"rate-time": 3600,
"rate-key": "url:{url},email:{param-email}",
"rate-key": [
"url:{url},email:{param-email}",
"url:{url},ip:{ip}"
],
"scope": "sessions.write",
"platforms": [
"server",
@ -4427,7 +4430,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 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.",
"responses": {
"201": {
"description": "Document",
@ -4459,6 +4462,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 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."
},
{
"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": []
}
@ -4515,12 +4563,16 @@
"items": {
"type": "string"
}
},
"documents": {
"type": "array",
"description": "Array of documents data as JSON objects.",
"x-example": null,
"items": {
"type": "object"
}
}
},
"required": [
"documentId",
"data"
]
}
}
}
}
@ -4744,7 +4796,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 113,
"weight": 115,
"cookies": false,
"type": "",
"deprecated": false,
@ -4828,7 +4880,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 305,
"weight": 308,
"cookies": false,
"type": "",
"deprecated": false,
@ -4914,7 +4966,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 304,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@ -5029,7 +5081,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 306,
"weight": 309,
"cookies": false,
"type": "",
"deprecated": false,
@ -5103,7 +5155,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 330,
"weight": 333,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -5155,7 +5207,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 329,
"weight": 332,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -5207,7 +5259,7 @@
"x-appwrite": {
"method": "get",
"group": null,
"weight": 117,
"weight": 120,
"cookies": false,
"type": "",
"deprecated": false,
@ -5259,7 +5311,7 @@
"x-appwrite": {
"method": "listCodes",
"group": null,
"weight": 118,
"weight": 121,
"cookies": false,
"type": "",
"deprecated": false,
@ -5311,7 +5363,7 @@
"x-appwrite": {
"method": "listContinents",
"group": null,
"weight": 122,
"weight": 125,
"cookies": false,
"type": "",
"deprecated": false,
@ -5363,7 +5415,7 @@
"x-appwrite": {
"method": "listCountries",
"group": null,
"weight": 119,
"weight": 122,
"cookies": false,
"type": "",
"deprecated": false,
@ -5415,7 +5467,7 @@
"x-appwrite": {
"method": "listCountriesEU",
"group": null,
"weight": 120,
"weight": 123,
"cookies": false,
"type": "",
"deprecated": false,
@ -5467,7 +5519,7 @@
"x-appwrite": {
"method": "listCountriesPhones",
"group": null,
"weight": 121,
"weight": 124,
"cookies": false,
"type": "",
"deprecated": false,
@ -5519,7 +5571,7 @@
"x-appwrite": {
"method": "listCurrencies",
"group": null,
"weight": 123,
"weight": 126,
"cookies": false,
"type": "",
"deprecated": false,
@ -5571,7 +5623,7 @@
"x-appwrite": {
"method": "listLanguages",
"group": null,
"weight": 124,
"weight": 127,
"cookies": false,
"type": "",
"deprecated": false,
@ -5623,7 +5675,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 375,
"weight": 378,
"cookies": false,
"type": "",
"deprecated": false,
@ -5706,7 +5758,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 379,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@ -5781,7 +5833,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 207,
"weight": 210,
"cookies": false,
"type": "",
"deprecated": false,
@ -5867,7 +5919,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 206,
"weight": 209,
"cookies": false,
"type": "upload",
"deprecated": false,
@ -5965,7 +6017,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 208,
"weight": 211,
"cookies": false,
"type": "",
"deprecated": false,
@ -6037,7 +6089,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 213,
"weight": 216,
"cookies": false,
"type": "",
"deprecated": false,
@ -6126,7 +6178,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 214,
"weight": 217,
"cookies": false,
"type": "",
"deprecated": false,
@ -6193,7 +6245,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 210,
"weight": 213,
"cookies": false,
"type": "location",
"deprecated": false,
@ -6260,7 +6312,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 209,
"weight": 212,
"cookies": false,
"type": "location",
"deprecated": false,
@ -6477,7 +6529,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 211,
"weight": 214,
"cookies": false,
"type": "location",
"deprecated": false,
@ -6551,7 +6603,7 @@
"x-appwrite": {
"method": "list",
"group": "teams",
"weight": 218,
"weight": 221,
"cookies": false,
"type": "",
"deprecated": false,
@ -6627,7 +6679,7 @@
"x-appwrite": {
"method": "create",
"group": "teams",
"weight": 217,
"weight": 220,
"cookies": false,
"type": "",
"deprecated": false,
@ -6712,7 +6764,7 @@
"x-appwrite": {
"method": "get",
"group": "teams",
"weight": 219,
"weight": 222,
"cookies": false,
"type": "",
"deprecated": false,
@ -6774,7 +6826,7 @@
"x-appwrite": {
"method": "updateName",
"group": "teams",
"weight": 221,
"weight": 224,
"cookies": false,
"type": "",
"deprecated": false,
@ -6848,7 +6900,7 @@
"x-appwrite": {
"method": "delete",
"group": "teams",
"weight": 223,
"weight": 226,
"cookies": false,
"type": "",
"deprecated": false,
@ -6912,7 +6964,7 @@
"x-appwrite": {
"method": "listMemberships",
"group": "memberships",
"weight": 225,
"weight": 228,
"cookies": false,
"type": "",
"deprecated": false,
@ -6998,7 +7050,7 @@
"x-appwrite": {
"method": "createMembership",
"group": "memberships",
"weight": 224,
"weight": 227,
"cookies": false,
"type": "",
"deprecated": false,
@ -7109,7 +7161,7 @@
"x-appwrite": {
"method": "getMembership",
"group": "memberships",
"weight": 226,
"weight": 229,
"cookies": false,
"type": "",
"deprecated": false,
@ -7181,7 +7233,7 @@
"x-appwrite": {
"method": "updateMembership",
"group": "memberships",
"weight": 227,
"weight": 230,
"cookies": false,
"type": "",
"deprecated": false,
@ -7268,7 +7320,7 @@
"x-appwrite": {
"method": "deleteMembership",
"group": "memberships",
"weight": 229,
"weight": 232,
"cookies": false,
"type": "",
"deprecated": false,
@ -7342,7 +7394,7 @@
"x-appwrite": {
"method": "updateMembershipStatus",
"group": "memberships",
"weight": 228,
"weight": 231,
"cookies": false,
"type": "",
"deprecated": false,
@ -7440,7 +7492,7 @@
"x-appwrite": {
"method": "getPrefs",
"group": "teams",
"weight": 220,
"weight": 223,
"cookies": false,
"type": "",
"deprecated": false,
@ -7501,7 +7553,7 @@
"x-appwrite": {
"method": "updatePrefs",
"group": "teams",
"weight": 222,
"weight": 225,
"cookies": false,
"type": "",
"deprecated": false,
@ -7564,85 +7616,67 @@
"tags": [
{
"name": "account",
"description": "The Account service allows you to authenticate and manage a user account.",
"x-globalAttributes": []
"description": "The Account service allows you to authenticate and manage a user account."
},
{
"name": "avatars",
"description": "The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars.",
"x-globalAttributes": []
"description": "The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars."
},
{
"name": "databases",
"description": "The Databases service allows you to create structured collections of documents, query and filter lists of documents",
"x-globalAttributes": [
"databaseId"
]
"description": "The Databases service allows you to create structured collections of documents, query and filter lists of documents"
},
{
"name": "locale",
"description": "The Locale service allows you to customize your app based on your users' location.",
"x-globalAttributes": []
"description": "The Locale service allows you to customize your app based on your users' location."
},
{
"name": "health",
"description": "The Health service allows you to both validate and monitor your Appwrite server's health.",
"x-globalAttributes": []
"description": "The Health service allows you to both validate and monitor your Appwrite server's health."
},
{
"name": "projects",
"description": "The Project service allows you to manage all the projects in your Appwrite server.",
"x-globalAttributes": []
"description": "The Project service allows you to manage all the projects in your Appwrite server."
},
{
"name": "project",
"description": "The Project service allows you to manage all the projects in your Appwrite server.",
"x-globalAttributes": []
"description": "The Project service allows you to manage all the projects in your Appwrite server."
},
{
"name": "storage",
"description": "The Storage service allows you to manage your project files.",
"x-globalAttributes": []
"description": "The Storage service allows you to manage your project files."
},
{
"name": "teams",
"description": "The Teams service allows you to group users of your project and to enable them to share read and write access to your project resources",
"x-globalAttributes": []
"description": "The Teams service allows you to group users of your project and to enable them to share read and write access to your project resources"
},
{
"name": "users",
"description": "The Users service allows you to manage your project users.",
"x-globalAttributes": []
"description": "The Users service allows you to manage your project users."
},
{
"name": "functions",
"description": "The Functions Service allows you view, create and manage your Cloud Functions.",
"x-globalAttributes": []
"description": "The Functions Service allows you view, create and manage your Cloud Functions."
},
{
"name": "proxy",
"description": "The Proxy Service allows you to configure actions for your domains beyond DNS configuration.",
"x-globalAttributes": []
"description": "The Proxy Service allows you to configure actions for your domains beyond DNS configuration."
},
{
"name": "graphql",
"description": "The GraphQL API allows you to query and mutate your Appwrite server using GraphQL.",
"x-globalAttributes": []
"description": "The GraphQL API allows you to query and mutate your Appwrite server using GraphQL."
},
{
"name": "console",
"description": "The Console service allows you to interact with console relevant informations.",
"x-globalAttributes": []
"description": "The Console service allows you to interact with console relevant informations."
},
{
"name": "migrations",
"description": "The Migrations service allows you to migrate third-party data to your Appwrite project.",
"x-globalAttributes": []
"description": "The Migrations service allows you to migrate third-party data to your Appwrite project."
},
{
"name": "messaging",
"description": "The Messaging service allows you to send messages to any provider type (SMTP, push notification, SMS, etc.).",
"x-globalAttributes": []
"description": "The Messaging service allows you to send messages to any provider type (SMTP, push notification, SMS, etc.)."
}
],
"components": {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -2765,7 +2765,10 @@
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-token-email.md",
"rate-limit": 10,
"rate-time": 3600,
"rate-key": "url:{url},email:{param-email}",
"rate-key": [
"url:{url},email:{param-email}",
"url:{url},ip:{ip}"
],
"scope": "sessions.write",
"platforms": [
"server",
@ -4571,7 +4574,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 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.",
"responses": {
"201": {
"description": "Document",
@ -4599,6 +4602,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 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."
},
{
"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": []
}
@ -4636,13 +4684,13 @@
"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": "{}"
},
"permissions": {
@ -4653,12 +4701,17 @@
"items": {
"type": "string"
}
},
"documents": {
"type": "array",
"description": "Array of documents data as JSON objects.",
"default": [],
"x-example": null,
"items": {
"type": "object"
}
}
},
"required": [
"documentId",
"data"
]
}
}
}
]
@ -4874,7 +4927,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 113,
"weight": 115,
"cookies": false,
"type": "",
"deprecated": false,
@ -4952,7 +5005,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 305,
"weight": 308,
"cookies": false,
"type": "",
"deprecated": false,
@ -5035,7 +5088,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 304,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@ -5152,7 +5205,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 306,
"weight": 309,
"cookies": false,
"type": "",
"deprecated": false,
@ -5224,7 +5277,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 330,
"weight": 333,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -5298,7 +5351,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 329,
"weight": 332,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -5370,7 +5423,7 @@
"x-appwrite": {
"method": "get",
"group": null,
"weight": 117,
"weight": 120,
"cookies": false,
"type": "",
"deprecated": false,
@ -5422,7 +5475,7 @@
"x-appwrite": {
"method": "listCodes",
"group": null,
"weight": 118,
"weight": 121,
"cookies": false,
"type": "",
"deprecated": false,
@ -5474,7 +5527,7 @@
"x-appwrite": {
"method": "listContinents",
"group": null,
"weight": 122,
"weight": 125,
"cookies": false,
"type": "",
"deprecated": false,
@ -5526,7 +5579,7 @@
"x-appwrite": {
"method": "listCountries",
"group": null,
"weight": 119,
"weight": 122,
"cookies": false,
"type": "",
"deprecated": false,
@ -5578,7 +5631,7 @@
"x-appwrite": {
"method": "listCountriesEU",
"group": null,
"weight": 120,
"weight": 123,
"cookies": false,
"type": "",
"deprecated": false,
@ -5630,7 +5683,7 @@
"x-appwrite": {
"method": "listCountriesPhones",
"group": null,
"weight": 121,
"weight": 124,
"cookies": false,
"type": "",
"deprecated": false,
@ -5682,7 +5735,7 @@
"x-appwrite": {
"method": "listCurrencies",
"group": null,
"weight": 123,
"weight": 126,
"cookies": false,
"type": "",
"deprecated": false,
@ -5734,7 +5787,7 @@
"x-appwrite": {
"method": "listLanguages",
"group": null,
"weight": 124,
"weight": 127,
"cookies": false,
"type": "",
"deprecated": false,
@ -5788,7 +5841,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 375,
"weight": 378,
"cookies": false,
"type": "",
"deprecated": false,
@ -5873,7 +5926,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 379,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@ -5944,7 +5997,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 207,
"weight": 210,
"cookies": false,
"type": "",
"deprecated": false,
@ -6027,7 +6080,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 206,
"weight": 209,
"cookies": false,
"type": "upload",
"deprecated": false,
@ -6117,7 +6170,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 208,
"weight": 211,
"cookies": false,
"type": "",
"deprecated": false,
@ -6187,7 +6240,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 213,
"weight": 216,
"cookies": false,
"type": "",
"deprecated": false,
@ -6276,7 +6329,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 214,
"weight": 217,
"cookies": false,
"type": "",
"deprecated": false,
@ -6346,7 +6399,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 210,
"weight": 213,
"cookies": false,
"type": "location",
"deprecated": false,
@ -6416,7 +6469,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 209,
"weight": 212,
"cookies": false,
"type": "location",
"deprecated": false,
@ -6614,7 +6667,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 211,
"weight": 214,
"cookies": false,
"type": "location",
"deprecated": false,
@ -6684,7 +6737,7 @@
"x-appwrite": {
"method": "list",
"group": "teams",
"weight": 218,
"weight": 221,
"cookies": false,
"type": "",
"deprecated": false,
@ -6759,7 +6812,7 @@
"x-appwrite": {
"method": "create",
"group": "teams",
"weight": 217,
"weight": 220,
"cookies": false,
"type": "",
"deprecated": false,
@ -6849,7 +6902,7 @@
"x-appwrite": {
"method": "get",
"group": "teams",
"weight": 219,
"weight": 222,
"cookies": false,
"type": "",
"deprecated": false,
@ -6911,7 +6964,7 @@
"x-appwrite": {
"method": "updateName",
"group": "teams",
"weight": 221,
"weight": 224,
"cookies": false,
"type": "",
"deprecated": false,
@ -6986,7 +7039,7 @@
"x-appwrite": {
"method": "delete",
"group": "teams",
"weight": 223,
"weight": 226,
"cookies": false,
"type": "",
"deprecated": false,
@ -7048,7 +7101,7 @@
"x-appwrite": {
"method": "listMemberships",
"group": "memberships",
"weight": 225,
"weight": 228,
"cookies": false,
"type": "",
"deprecated": false,
@ -7131,7 +7184,7 @@
"x-appwrite": {
"method": "createMembership",
"group": "memberships",
"weight": 224,
"weight": 227,
"cookies": false,
"type": "",
"deprecated": false,
@ -7244,7 +7297,7 @@
"x-appwrite": {
"method": "getMembership",
"group": "memberships",
"weight": 226,
"weight": 229,
"cookies": false,
"type": "",
"deprecated": false,
@ -7314,7 +7367,7 @@
"x-appwrite": {
"method": "updateMembership",
"group": "memberships",
"weight": 227,
"weight": 230,
"cookies": false,
"type": "",
"deprecated": false,
@ -7400,7 +7453,7 @@
"x-appwrite": {
"method": "deleteMembership",
"group": "memberships",
"weight": 229,
"weight": 232,
"cookies": false,
"type": "",
"deprecated": false,
@ -7472,7 +7525,7 @@
"x-appwrite": {
"method": "updateMembershipStatus",
"group": "memberships",
"weight": 228,
"weight": 231,
"cookies": false,
"type": "",
"deprecated": false,
@ -7566,7 +7619,7 @@
"x-appwrite": {
"method": "getPrefs",
"group": "teams",
"weight": 220,
"weight": 223,
"cookies": false,
"type": "",
"deprecated": false,
@ -7627,7 +7680,7 @@
"x-appwrite": {
"method": "updatePrefs",
"group": "teams",
"weight": 222,
"weight": 225,
"cookies": false,
"type": "",
"deprecated": false,
@ -7687,85 +7740,67 @@
"tags": [
{
"name": "account",
"description": "The Account service allows you to authenticate and manage a user account.",
"x-globalAttributes": []
"description": "The Account service allows you to authenticate and manage a user account."
},
{
"name": "avatars",
"description": "The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars.",
"x-globalAttributes": []
"description": "The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars."
},
{
"name": "databases",
"description": "The Databases service allows you to create structured collections of documents, query and filter lists of documents",
"x-globalAttributes": [
"databaseId"
]
"description": "The Databases service allows you to create structured collections of documents, query and filter lists of documents"
},
{
"name": "locale",
"description": "The Locale service allows you to customize your app based on your users' location.",
"x-globalAttributes": []
"description": "The Locale service allows you to customize your app based on your users' location."
},
{
"name": "health",
"description": "The Health service allows you to both validate and monitor your Appwrite server's health.",
"x-globalAttributes": []
"description": "The Health service allows you to both validate and monitor your Appwrite server's health."
},
{
"name": "projects",
"description": "The Project service allows you to manage all the projects in your Appwrite server.",
"x-globalAttributes": []
"description": "The Project service allows you to manage all the projects in your Appwrite server."
},
{
"name": "project",
"description": "The Project service allows you to manage all the projects in your Appwrite server.",
"x-globalAttributes": []
"description": "The Project service allows you to manage all the projects in your Appwrite server."
},
{
"name": "storage",
"description": "The Storage service allows you to manage your project files.",
"x-globalAttributes": []
"description": "The Storage service allows you to manage your project files."
},
{
"name": "teams",
"description": "The Teams service allows you to group users of your project and to enable them to share read and write access to your project resources",
"x-globalAttributes": []
"description": "The Teams service allows you to group users of your project and to enable them to share read and write access to your project resources"
},
{
"name": "users",
"description": "The Users service allows you to manage your project users.",
"x-globalAttributes": []
"description": "The Users service allows you to manage your project users."
},
{
"name": "functions",
"description": "The Functions Service allows you view, create and manage your Cloud Functions.",
"x-globalAttributes": []
"description": "The Functions Service allows you view, create and manage your Cloud Functions."
},
{
"name": "proxy",
"description": "The Proxy Service allows you to configure actions for your domains beyond DNS configuration.",
"x-globalAttributes": []
"description": "The Proxy Service allows you to configure actions for your domains beyond DNS configuration."
},
{
"name": "graphql",
"description": "The GraphQL API allows you to query and mutate your Appwrite server using GraphQL.",
"x-globalAttributes": []
"description": "The GraphQL API allows you to query and mutate your Appwrite server using GraphQL."
},
{
"name": "console",
"description": "The Console service allows you to interact with console relevant informations.",
"x-globalAttributes": []
"description": "The Console service allows you to interact with console relevant informations."
},
{
"name": "migrations",
"description": "The Migrations service allows you to migrate third-party data to your Appwrite project.",
"x-globalAttributes": []
"description": "The Migrations service allows you to migrate third-party data to your Appwrite project."
},
{
"name": "messaging",
"description": "The Messaging service allows you to send messages to any provider type (SMTP, push notification, SMS, etc.).",
"x-globalAttributes": []
"description": "The Messaging service allows you to send messages to any provider type (SMTP, push notification, SMS, etc.)."
}
],
"definitions": {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2765,7 +2765,10 @@
"edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/account\/create-token-email.md",
"rate-limit": 10,
"rate-time": 3600,
"rate-key": "url:{url},email:{param-email}",
"rate-key": [
"url:{url},email:{param-email}",
"url:{url},ip:{ip}"
],
"scope": "sessions.write",
"platforms": [
"server",
@ -4571,7 +4574,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 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.",
"responses": {
"201": {
"description": "Document",
@ -4599,6 +4602,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 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."
},
{
"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": []
}
@ -4636,13 +4684,13 @@
"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": "{}"
},
"permissions": {
@ -4653,12 +4701,17 @@
"items": {
"type": "string"
}
},
"documents": {
"type": "array",
"description": "Array of documents data as JSON objects.",
"default": [],
"x-example": null,
"items": {
"type": "object"
}
}
},
"required": [
"documentId",
"data"
]
}
}
}
]
@ -4874,7 +4927,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 113,
"weight": 115,
"cookies": false,
"type": "",
"deprecated": false,
@ -4952,7 +5005,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 305,
"weight": 308,
"cookies": false,
"type": "",
"deprecated": false,
@ -5035,7 +5088,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 304,
"weight": 307,
"cookies": false,
"type": "",
"deprecated": false,
@ -5152,7 +5205,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 306,
"weight": 309,
"cookies": false,
"type": "",
"deprecated": false,
@ -5224,7 +5277,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 330,
"weight": 333,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -5298,7 +5351,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 329,
"weight": 332,
"cookies": false,
"type": "graphql",
"deprecated": false,
@ -5370,7 +5423,7 @@
"x-appwrite": {
"method": "get",
"group": null,
"weight": 117,
"weight": 120,
"cookies": false,
"type": "",
"deprecated": false,
@ -5422,7 +5475,7 @@
"x-appwrite": {
"method": "listCodes",
"group": null,
"weight": 118,
"weight": 121,
"cookies": false,
"type": "",
"deprecated": false,
@ -5474,7 +5527,7 @@
"x-appwrite": {
"method": "listContinents",
"group": null,
"weight": 122,
"weight": 125,
"cookies": false,
"type": "",
"deprecated": false,
@ -5526,7 +5579,7 @@
"x-appwrite": {
"method": "listCountries",
"group": null,
"weight": 119,
"weight": 122,
"cookies": false,
"type": "",
"deprecated": false,
@ -5578,7 +5631,7 @@
"x-appwrite": {
"method": "listCountriesEU",
"group": null,
"weight": 120,
"weight": 123,
"cookies": false,
"type": "",
"deprecated": false,
@ -5630,7 +5683,7 @@
"x-appwrite": {
"method": "listCountriesPhones",
"group": null,
"weight": 121,
"weight": 124,
"cookies": false,
"type": "",
"deprecated": false,
@ -5682,7 +5735,7 @@
"x-appwrite": {
"method": "listCurrencies",
"group": null,
"weight": 123,
"weight": 126,
"cookies": false,
"type": "",
"deprecated": false,
@ -5734,7 +5787,7 @@
"x-appwrite": {
"method": "listLanguages",
"group": null,
"weight": 124,
"weight": 127,
"cookies": false,
"type": "",
"deprecated": false,
@ -5788,7 +5841,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 375,
"weight": 378,
"cookies": false,
"type": "",
"deprecated": false,
@ -5873,7 +5926,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 379,
"weight": 382,
"cookies": false,
"type": "",
"deprecated": false,
@ -5944,7 +5997,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 207,
"weight": 210,
"cookies": false,
"type": "",
"deprecated": false,
@ -6027,7 +6080,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 206,
"weight": 209,
"cookies": false,
"type": "upload",
"deprecated": false,
@ -6117,7 +6170,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 208,
"weight": 211,
"cookies": false,
"type": "",
"deprecated": false,
@ -6187,7 +6240,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 213,
"weight": 216,
"cookies": false,
"type": "",
"deprecated": false,
@ -6276,7 +6329,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 214,
"weight": 217,
"cookies": false,
"type": "",
"deprecated": false,
@ -6346,7 +6399,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 210,
"weight": 213,
"cookies": false,
"type": "location",
"deprecated": false,
@ -6416,7 +6469,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 209,
"weight": 212,
"cookies": false,
"type": "location",
"deprecated": false,
@ -6614,7 +6667,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 211,
"weight": 214,
"cookies": false,
"type": "location",
"deprecated": false,
@ -6684,7 +6737,7 @@
"x-appwrite": {
"method": "list",
"group": "teams",
"weight": 218,
"weight": 221,
"cookies": false,
"type": "",
"deprecated": false,
@ -6759,7 +6812,7 @@
"x-appwrite": {
"method": "create",
"group": "teams",
"weight": 217,
"weight": 220,
"cookies": false,
"type": "",
"deprecated": false,
@ -6849,7 +6902,7 @@
"x-appwrite": {
"method": "get",
"group": "teams",
"weight": 219,
"weight": 222,
"cookies": false,
"type": "",
"deprecated": false,
@ -6911,7 +6964,7 @@
"x-appwrite": {
"method": "updateName",
"group": "teams",
"weight": 221,
"weight": 224,
"cookies": false,
"type": "",
"deprecated": false,
@ -6986,7 +7039,7 @@
"x-appwrite": {
"method": "delete",
"group": "teams",
"weight": 223,
"weight": 226,
"cookies": false,
"type": "",
"deprecated": false,
@ -7048,7 +7101,7 @@
"x-appwrite": {
"method": "listMemberships",
"group": "memberships",
"weight": 225,
"weight": 228,
"cookies": false,
"type": "",
"deprecated": false,
@ -7131,7 +7184,7 @@
"x-appwrite": {
"method": "createMembership",
"group": "memberships",
"weight": 224,
"weight": 227,
"cookies": false,
"type": "",
"deprecated": false,
@ -7244,7 +7297,7 @@
"x-appwrite": {
"method": "getMembership",
"group": "memberships",
"weight": 226,
"weight": 229,
"cookies": false,
"type": "",
"deprecated": false,
@ -7314,7 +7367,7 @@
"x-appwrite": {
"method": "updateMembership",
"group": "memberships",
"weight": 227,
"weight": 230,
"cookies": false,
"type": "",
"deprecated": false,
@ -7400,7 +7453,7 @@
"x-appwrite": {
"method": "deleteMembership",
"group": "memberships",
"weight": 229,
"weight": 232,
"cookies": false,
"type": "",
"deprecated": false,
@ -7472,7 +7525,7 @@
"x-appwrite": {
"method": "updateMembershipStatus",
"group": "memberships",
"weight": 228,
"weight": 231,
"cookies": false,
"type": "",
"deprecated": false,
@ -7566,7 +7619,7 @@
"x-appwrite": {
"method": "getPrefs",
"group": "teams",
"weight": 220,
"weight": 223,
"cookies": false,
"type": "",
"deprecated": false,
@ -7627,7 +7680,7 @@
"x-appwrite": {
"method": "updatePrefs",
"group": "teams",
"weight": 222,
"weight": 225,
"cookies": false,
"type": "",
"deprecated": false,
@ -7687,85 +7740,67 @@
"tags": [
{
"name": "account",
"description": "The Account service allows you to authenticate and manage a user account.",
"x-globalAttributes": []
"description": "The Account service allows you to authenticate and manage a user account."
},
{
"name": "avatars",
"description": "The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars.",
"x-globalAttributes": []
"description": "The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars."
},
{
"name": "databases",
"description": "The Databases service allows you to create structured collections of documents, query and filter lists of documents",
"x-globalAttributes": [
"databaseId"
]
"description": "The Databases service allows you to create structured collections of documents, query and filter lists of documents"
},
{
"name": "locale",
"description": "The Locale service allows you to customize your app based on your users' location.",
"x-globalAttributes": []
"description": "The Locale service allows you to customize your app based on your users' location."
},
{
"name": "health",
"description": "The Health service allows you to both validate and monitor your Appwrite server's health.",
"x-globalAttributes": []
"description": "The Health service allows you to both validate and monitor your Appwrite server's health."
},
{
"name": "projects",
"description": "The Project service allows you to manage all the projects in your Appwrite server.",
"x-globalAttributes": []
"description": "The Project service allows you to manage all the projects in your Appwrite server."
},
{
"name": "project",
"description": "The Project service allows you to manage all the projects in your Appwrite server.",
"x-globalAttributes": []
"description": "The Project service allows you to manage all the projects in your Appwrite server."
},
{
"name": "storage",
"description": "The Storage service allows you to manage your project files.",
"x-globalAttributes": []
"description": "The Storage service allows you to manage your project files."
},
{
"name": "teams",
"description": "The Teams service allows you to group users of your project and to enable them to share read and write access to your project resources",
"x-globalAttributes": []
"description": "The Teams service allows you to group users of your project and to enable them to share read and write access to your project resources"
},
{
"name": "users",
"description": "The Users service allows you to manage your project users.",
"x-globalAttributes": []
"description": "The Users service allows you to manage your project users."
},
{
"name": "functions",
"description": "The Functions Service allows you view, create and manage your Cloud Functions.",
"x-globalAttributes": []
"description": "The Functions Service allows you view, create and manage your Cloud Functions."
},
{
"name": "proxy",
"description": "The Proxy Service allows you to configure actions for your domains beyond DNS configuration.",
"x-globalAttributes": []
"description": "The Proxy Service allows you to configure actions for your domains beyond DNS configuration."
},
{
"name": "graphql",
"description": "The GraphQL API allows you to query and mutate your Appwrite server using GraphQL.",
"x-globalAttributes": []
"description": "The GraphQL API allows you to query and mutate your Appwrite server using GraphQL."
},
{
"name": "console",
"description": "The Console service allows you to interact with console relevant informations.",
"x-globalAttributes": []
"description": "The Console service allows you to interact with console relevant informations."
},
{
"name": "migrations",
"description": "The Migrations service allows you to migrate third-party data to your Appwrite project.",
"x-globalAttributes": []
"description": "The Migrations service allows you to migrate third-party data to your Appwrite project."
},
{
"name": "messaging",
"description": "The Messaging service allows you to send messages to any provider type (SMTP, push notification, SMS, etc.).",
"x-globalAttributes": []
"description": "The Messaging service allows you to send messages to any provider type (SMTP, push notification, SMS, etc.)."
}
],
"definitions": {

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -719,9 +719,7 @@ App::delete('/v1/account/sessions/:sessionId')
continue;
}
$dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $session) {
return $dbForProject->deleteDocument('sessions', $session->getId());
});
$dbForProject->deleteDocument('sessions', $session->getId());
unset($sessions[$key]);
@ -2001,6 +1999,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) {
@ -2769,7 +2768,7 @@ App::patch('/v1/account/name')
$user->setAttribute('name', $name);
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
$queueForEvents->setParam('userId', $user->getId());
@ -2844,7 +2843,7 @@ App::patch('/v1/account/password')
->setAttribute('hash', Auth::DEFAULT_ALGO)
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS);
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
$queueForEvents->setParam('userId', $user->getId());
@ -2929,7 +2928,7 @@ App::patch('/v1/account/email')
}
try {
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
/**
* @var Document $oldTarget
*/
@ -3015,7 +3014,7 @@ App::patch('/v1/account/phone')
}
try {
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
/**
* @var Document $oldTarget
*/
@ -3065,7 +3064,7 @@ App::patch('/v1/account/prefs')
$user->setAttribute('prefs', $prefs);
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
$queueForEvents->setParam('userId', $user->getId());
@ -3103,7 +3102,7 @@ App::patch('/v1/account/status')
$user->setAttribute('status', false);
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
$queueForEvents
->setParam('userId', $user->getId())
@ -3867,7 +3866,7 @@ App::patch('/v1/account/mfa')
$user->setAttribute('mfa', $mfa);
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
if ($mfa) {
$factors = $session->getAttribute('factors', []);

View file

@ -10,6 +10,7 @@ use Appwrite\Network\Validator\Email;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Parameter;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Attributes;
@ -35,6 +36,7 @@ use Utopia\Database\Exception\Order as OrderException;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Exception\Restricted as RestrictedException;
use Utopia\Database\Exception\Structure as StructureException;
use Utopia\Database\Exception\Timeout as TimeoutException;
use Utopia\Database\Exception\Truncate as TruncateException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
@ -3170,7 +3172,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')
@ -3180,6 +3181,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->label(
'sdk',
// Using multiple methods to abstract the complexity for SDK users
[
new Method(
namespace: 'databases',
@ -3193,90 +3195,158 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
model: Response::MODEL_DOCUMENT,
)
],
contentType: ContentType::JSON
contentType: ContentType::JSON,
parameters: [
new Parameter('databaseId', optional: false),
new Parameter('collectionId', optional: false),
new Parameter('documentId', optional: false),
new Parameter('data', optional: false),
new Parameter('permissions', optional: true),
]
),
new Method(
namespace: 'databases',
group: 'documents',
name: 'createDocuments',
description: '/docs/references/databases/create-documents.md',
auth: [AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_DOCUMENT_LIST,
)
],
contentType: ContentType::JSON,
parameters: [
new Parameter('databaseId', optional: false),
new Parameter('collectionId', optional: false),
new Parameter('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('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)
->param('documents', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of documents data as JSON objects.', true, ['plan'])
->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) {
->action(function (string $databaseId, ?string $documentId, string $collectionId, string|array|null $data, ?array $permissions, ?array $documents, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, StatsUsage $queueForStatsUsage) {
$data = \is_string($data)
? \json_decode($data, true)
: $data;
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
if (empty($data)) {
/**
* Determine which internal path to call, single or bulk
*/
if (empty($data) && empty($documents)) {
// No single or bulk documents provided
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)) {
// Both single and bulk documents provided
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'You can only send one of the following parameters: data, documents');
}
if (!empty($data) && empty($documentId)) {
// Single document provided without document ID
throw new Exception(Exception::DOCUMENT_MISSING_DATA, 'Document ID is required when creating a single document');
}
if (!empty($documents) && !empty($documentId)) {
// Bulk documents provided with document ID
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)) {
// Bulk documents provided with 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));
$isBulk = true;
if (!empty($data)) {
// Single document provided, convert to single item array
// But remember that it was single to respond with a single document
$isBulk = false;
$documents = [$data];
}
$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);
}
$allowedPermissions = [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
];
$hasRelationships = \array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
);
// 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();
}
}
if ($isBulk && $hasRelationships) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk create is not supported for collections with relationship attributes');
}
// 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()) . ')');
$setPermissions = function (Document $document, ?array $permissions) use ($user, $isAPIKey, $isPrivilegedUser, $isBulk) {
$allowedPermissions = [
Database::PERMISSION_READ,
Database::PERMISSION_UPDATE,
Database::PERMISSION_DELETE,
];
// If bulk, we need to validate permissions explicitly per document
if ($isBulk) {
$permissions = $document['$permissions'] ?? null;
if (!empty($permissions)) {
$validator = new Permissions();
if (!$validator->isValid($permissions)) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, $validator->getDescription());
}
}
}
}
$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);
$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();
}
}
}
// 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;
@ -3362,21 +3432,57 @@ 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();
// Determine the source ID depending on whether it's a bulk operation.
$sourceId = $isBulk
? ($document['$id'] ?? ID::unique())
: $documentId;
// If bulk, we need to validate ID explicitly
if ($isBulk) {
$validator = new CustomId();
if (!$validator->isValid($sourceId)) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, $validator->getDescription());
}
}
// Assign a unique ID if needed, otherwise use the provided ID.
$document['$id'] = $sourceId === 'unique()' ? ID::unique() : $sourceId;
$document = new Document($document);
$setPermissions($document, $permissions);
$checkPermissions($collection, $document, Database::PERMISSION_CREATE);
return $document;
}, $documents);
try {
$document = $dbForProject->createDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $document);
$dbForProject->createDocuments(
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
$documents
);
} catch (StructureException $e) {
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
} catch (DuplicateException $e) {
} catch (DuplicateException) {
throw new Exception(Exception::DOCUMENT_ALREADY_EXISTS);
} catch (NotFoundException $e) {
} catch (NotFoundException) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
} catch (AuthorizationException) {
throw new Exception(Exception::USER_UNAUTHORIZED);
} catch (TimeoutException) {
throw new Exception(Exception::DATABASE_TIMEOUT);
}
$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) {
$document->removeAttribute('$collection');
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$collectionId', $collection->getId());
@ -3408,33 +3514,33 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
}
};
$processDocument($collection, $document);
foreach ($documents as $document) {
$processDocument($collection, $document);
}
$queueForStatsUsage
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, max($operations, 1))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), $operations); // per collection
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $operations))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations)); // per collection
$response->addHeader('X-Debug-Operations', $operations);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($document, Response::MODEL_DOCUMENT);
if ($isBulk) {
$response->dynamic(new Document([
'total' => count($documents),
'documents' => $documents
]), Response::MODEL_DOCUMENT_LIST);
$relationships = \array_map(
fn ($document) => $document->getAttribute('key'),
\array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
)
);
return;
}
$queueForEvents
->setParam('databaseId', $databaseId)
->setParam('collectionId', $collection->getId())
->setParam('documentId', $document->getId())
->setContext('collection', $collection)
->setContext('database', $database)
->setPayload($response->getPayload(), sensitive: $relationships);
->setParam('documentId', $documents[0]->getId())
->setEvent('databases.[databaseId].collections.[collectionId].documents.[documentId].create');
$response->dynamic(
$documents[0],
Response::MODEL_DOCUMENT
);
});
App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
@ -3526,7 +3632,6 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
$operations++;
$document->removeAttribute('$collection');
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$collectionId', $collection->getId());
@ -3579,10 +3684,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
}
$queueForStatsUsage
->addMetric(METRIC_DATABASES_OPERATIONS_READS, max($operations, 1))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_READS), $operations);
$response->addHeader('X-Debug-Operations', $operations);
->addMetric(METRIC_DATABASES_OPERATIONS_READS, \max(1, $operations))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_READS), \max(1, $operations));
$select = \array_reduce($queries, function ($result, $query) {
return $result || ($query->getMethod() === Query::TYPE_SELECT);
@ -3721,10 +3824,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
$processDocument($collection, $document);
$queueForStatsUsage
->addMetric(METRIC_DATABASES_OPERATIONS_READS, max($operations, 1))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_READS), $operations);
$response->addHeader('X-Debug-Operations', $operations);
->addMetric(METRIC_DATABASES_OPERATIONS_READS, \max(1, $operations))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_READS), \max(1, $operations));
$response->dynamic($document, Response::MODEL_DOCUMENT);
});
@ -3881,7 +3982,6 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
->inject('queueForEvents')
->inject('queueForStatsUsage')
->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, StatsUsage $queueForStatsUsage) {
$data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array
if (empty($data) && \is_null($permissions)) {
@ -3898,7 +3998,6 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
}
$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);
}
@ -4021,19 +4120,14 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
$setCollection($collection, $newDocument);
$queueForStatsUsage
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, max($operations, 1))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), $operations);
$response->addHeader('X-Debug-Operations', $operations);
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $operations))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations));
try {
$document = $dbForProject->withRequestTimestamp(
$requestTimestamp,
fn () => $dbForProject->updateDocument(
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
$document->getId(),
$newDocument
)
$document = $dbForProject->updateDocument(
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
$document->getId(),
$newDocument
);
} catch (AuthorizationException) {
throw new Exception(Exception::USER_UNAUTHORIZED);
@ -4080,8 +4174,6 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
$processDocument($collection, $document);
$response->dynamic($document, Response::MODEL_DOCUMENT);
$relationships = \array_map(
fn ($document) => $document->getAttribute('key'),
\array_filter(
@ -4097,6 +4189,202 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
->setContext('collection', $collection)
->setContext('database', $database)
->setPayload($response->getPayload(), sensitive: $relationships);
$response->dynamic($document, Response::MODEL_DOCUMENT);
});
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',
group: 'documents',
name: 'updateDocuments',
description: '/docs/references/databases/update-documents.md',
auth: [AuthType::KEY],
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('queueForStatsUsage')
->inject('plan')
->action(function (string $databaseId, string $collectionId, string|array $data, array $queries, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan) {
$data = \is_string($data)
? \json_decode($data, true)
: $data;
if (empty($data)) {
throw new Exception(Exception::DOCUMENT_MISSING_PAYLOAD);
}
$database = $dbForProject->getDocument('databases', $databaseId);
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$hasRelationships = \array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
);
if ($hasRelationships) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk update is not supported for collections with relationship attributes');
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
if ($data['$permissions']) {
$validator = new Permissions();
if (!$validator->isValid($data['$permissions'])) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, $validator->getDescription());
}
}
$documents = [];
try {
$modified = $dbForProject->updateDocuments(
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
new Document($data),
$queries,
onNext: function (Document $document) use ($plan, &$documents) {
if (\count($documents) < ($plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH)) {
$documents[] = $document;
}
},
);
} catch (StructureException $e) {
throw new Exception(Exception::DOCUMENT_INVALID_STRUCTURE, $e->getMessage());
} catch (NotFoundException) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
} catch (AuthorizationException) {
throw new Exception(Exception::USER_UNAUTHORIZED);
} catch (TimeoutException) {
throw new Exception(Exception::DATABASE_TIMEOUT);
}
foreach ($documents as $document) {
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$collectionId', $collection->getId());
}
$queueForStatsUsage
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $modified))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $modified));
$response->dynamic(new Document([
'total' => $modified,
'documents' => $documents
]), Response::MODEL_DOCUMENT_LIST);
});
App::put('/v1/databases/:databaseId/collections/:collectionId/documents')
->desc('Create or update documents')
->groups(['api', 'database'])
->label('scope', 'documents.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'documents.upsert')
->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',
group: 'documents',
name: 'upsertDocuments',
description: '/docs/references/databases/upsert-documents.md',
auth: [AuthType::KEY],
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('documents', [], fn (array $plan) => new ArrayList(new JSON(), $plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of document data as JSON objects. May contain partial documents.', true, ['plan'])
->inject('response')
->inject('dbForProject')
->inject('queueForStatsUsage')
->inject('plan')
->action(function (string $databaseId, string $collectionId, array $documents, Response $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan) {
$database = $dbForProject->getDocument('databases', $databaseId);
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$hasRelationships = \array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
);
if ($hasRelationships) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk upsert is not supported for collections with relationship attributes');
}
foreach ($documents as $key => $document) {
$documents[$key] = new Document($document);
}
$upserted = [];
$modified = $dbForProject->createOrUpdateDocuments(
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
$documents,
onNext: function (Document $document) use ($plan, &$upserted) {
if (\count($upserted) < ($plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH)) {
$upserted[] = $document;
}
},
);
foreach ($upserted as $document) {
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$collectionId', $collection->getId());
}
$queueForStatsUsage
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $modified))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $modified));
$response->dynamic(new Document([
'total' => $modified,
'documents' => $upserted
]), Response::MODEL_DOCUMENT_LIST);
});
App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:documentId')
@ -4157,18 +4445,20 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
}
try {
$dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $database, $collection, $documentId) {
$dbForProject->deleteDocument(
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
$documentId
);
});
$dbForProject->deleteDocument(
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
$documentId
);
} catch (NotFoundException $e) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$operations = 0;
// 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, &$operations) {
$operations++;
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$collectionId', $collection->getId());
@ -4203,10 +4493,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
$processDocument($collection, $document);
$queueForStatsUsage
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, 1)
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), 1); // per collection
$response->addHeader('X-Debug-Operations', 1);
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $operations))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $operations));
$relationships = \array_map(
fn ($document) => $document->getAttribute('key'),
@ -4227,6 +4515,99 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
$response->noContent();
});
App::delete('/v1/databases/:databaseId/collections/:collectionId/documents')
->desc('Delete documents')
->groups(['api', 'database'])
->label('scope', 'documents.write')
->label('resourceType', RESOURCE_TYPE_DATABASES)
->label('audits.event', 'documents.delete')
->label('audits.resource', 'database/{request.databaseId}/collection/{request.collectionId}')
->label('abuse-key', 'ip:{ip},method:{method},url:{url},userId:{userId}')
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->label('sdk', new Method(
namespace: 'databases',
group: 'documents',
name: 'deleteDocuments',
description: '/docs/references/databases/delete-documents.md',
auth: [AuthType::KEY],
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('plan')
->action(function (string $databaseId, string $collectionId, array $queries, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, StatsUsage $queueForStatsUsage, array $plan) {
$database = $dbForProject->getDocument('databases', $databaseId);
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
if ($collection->isEmpty()) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$hasRelationships = \array_filter(
$collection->getAttribute('attributes', []),
fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP
);
if ($hasRelationships) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk delete is not supported for collections with relationship attributes');
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$documents = [];
try {
$modified = $dbForProject->deleteDocuments(
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
$queries,
onNext: function (Document $document) use ($plan, &$documents) {
if (\count($documents) < ($plan['databasesBatchSize'] ?? APP_LIMIT_DATABASE_BATCH)) {
$documents[] = $document;
}
},
);
} catch (NotFoundException) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
} catch (AuthorizationException) {
throw new Exception(Exception::USER_UNAUTHORIZED);
} catch (TimeoutException) {
throw new Exception(Exception::DATABASE_TIMEOUT);
}
foreach ($documents as $document) {
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$collectionId', $collection->getId());
}
$queueForStatsUsage
->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, \max(1, $modified))
->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), \max(1, $modified));
$response->dynamic(new Document([
'total' => $modified,
'documents' => $documents,
]), Response::MODEL_DOCUMENT_LIST);
});
App::get('/v1/databases/usage')
->desc('Get databases usage stats')
->groups(['api', 'database', 'usage'])
@ -4512,7 +4893,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/usage')
$response->dynamic(new Document([
'range' => $range,
'documentsTotal' => $usage[$metrics[0]]['total'],
'documents' => $usage[$metrics[0]]['data'],
'documentsTotal' => $usage[$metrics[0]]['total'],
'documents' => $usage[$metrics[0]]['data'],
]), Response::MODEL_USAGE_COLLECTION);
});

View file

@ -1285,7 +1285,7 @@ App::post('/v1/functions/:functionId/deployments')
],
type: MethodType::UPLOAD,
packaging: true,
requestType: 'multipart/form-data',
requestType: ContentType::MULTIPART,
))
->param('functionId', '', new UID(), 'Function ID.')
->param('entrypoint', null, new Text(1028), 'Entrypoint File.', true)
@ -1899,7 +1899,6 @@ App::post('/v1/functions/:functionId/executions')
)
],
contentType: ContentType::MULTIPART,
requestType: 'application/json',
))
->param('functionId', '', new UID(), 'Function ID.')
->param('body', '', new Text(10485760, 0), 'HTTP body of execution. Default value is empty string.', true)

View file

@ -401,15 +401,15 @@ App::post('/v1/storage/buckets/:bucketId/files')
group: 'files',
name: 'createFile',
description: '/docs/references/storage/create-file.md',
type: MethodType::UPLOAD,
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
requestType: 'multipart/form-data',
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
model: Response::MODEL_FILE,
)
]
],
type: MethodType::UPLOAD,
requestType: ContentType::MULTIPART
))
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
->param('fileId', '', new CustomId(), 'File 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.')

View file

@ -322,9 +322,7 @@ App::put('/v1/teams/:teamId')
->setAttribute('name', $name)
->setAttribute('search', implode(' ', [$teamId, $name]));
$team = $dbForProject->withRequestTimestamp($requestTimestamp, function () use ($dbForProject, $team) {
return $dbForProject->updateDocument('teams', $team->getId(), $team);
});
$team = $dbForProject->updateDocument('teams', $team->getId(), $team);
$queueForEvents->setParam('teamId', $team->getId());

View file

@ -144,7 +144,6 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
)
],
contentType: ContentType::MULTIPART,
requestType: 'application/json',
));
} else {
/** @var Method $method */

View file

@ -92,8 +92,20 @@ $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;
switch ($event) {
case Database::EVENT_DOCUMENT_DELETE:
$value = -1;
break;
case Database::EVENT_DOCUMENTS_DELETE:
$value = -1 * $document->getAttribute('modified', 0);
break;
case Database::EVENT_DOCUMENTS_CREATE:
$value = $document->getAttribute('modified', 0);
break;
case Database::EVENT_DOCUMENTS_UPSERT:
$value = $document->getAttribute('created', 0);
break;
}
switch (true) {
@ -328,6 +340,8 @@ App::init()
*/
$method = $route->getLabel('sdk', false);
// Take the first method if there's more than one,
// namespace can not differ between methods on the same route
if (\is_array($method)) {
$method = $method[0];
}
@ -511,6 +525,9 @@ 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_DOCUMENTS_UPSERT, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener(
$project,
$document,

View file

@ -496,7 +496,7 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
});
// Fetch domains every `DOMAIN_SYNC_TIMER` seconds and update in the memory
$http->on('Task', function () use ($register, $domains) {
$http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
$lastSyncUpdate = null;
$pools = $register->get('pools');
App::setResource('pools', fn () => $pools);

View file

@ -26,6 +26,7 @@ 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_DATABASE_BATCH = 100; // Default maximum 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

View file

@ -30,7 +30,7 @@
}
},
"require": {
"php": ">=8.0.0",
"php": ">=8.3.0",
"ext-curl": "*",
"ext-imagick": "*",
"ext-mbstring": "*",
@ -51,7 +51,7 @@
"utopia-php/cache": "0.13.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "0.67.*",
"utopia-php/database": "0.68.*",
"utopia-php/domains": "0.5.*",
"utopia-php/dsn": "0.2.1",
"utopia-php/framework": "0.33.*",
@ -99,8 +99,8 @@
"php": "8.3"
},
"allow-plugins": {
"php-http/discovery": false,
"tbachert/spi": false
"php-http/discovery": true,
"tbachert/spi": true
}
}
}

42
composer.lock generated
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": "49b9828ae72763d2a398358c28cebdd5",
"content-hash": "a5c4c6b723423bc6cb3a7344ab071b43",
"packages": [
{
"name": "adhocore/jwt",
@ -1179,16 +1179,16 @@
},
{
"name": "open-telemetry/context",
"version": "1.2.0",
"version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/opentelemetry-php/context.git",
"reference": "5f553042b951d3fedf47925852c380159dfca801"
"reference": "1eb2b837ee9362db064a6b65d5ecce15a9f9f020"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/opentelemetry-php/context/zipball/5f553042b951d3fedf47925852c380159dfca801",
"reference": "5f553042b951d3fedf47925852c380159dfca801",
"url": "https://api.github.com/repos/opentelemetry-php/context/zipball/1eb2b837ee9362db064a6b65d5ecce15a9f9f020",
"reference": "1eb2b837ee9362db064a6b65d5ecce15a9f9f020",
"shasum": ""
},
"require": {
@ -1234,7 +1234,7 @@
"issues": "https://github.com/open-telemetry/opentelemetry-php/issues",
"source": "https://github.com/open-telemetry/opentelemetry-php"
},
"time": "2025-05-02T01:57:57+00:00"
"time": "2025-05-07T23:36:50+00:00"
},
{
"name": "open-telemetry/exporter-otlp",
@ -3499,16 +3499,16 @@
},
{
"name": "utopia-php/database",
"version": "0.67.2",
"version": "0.68.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "cd55117beab0025fd0d3f945a5a004125600982c"
"reference": "f9c2b6b4e7a17f0890d3fb6238b03dae6b2670ad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/cd55117beab0025fd0d3f945a5a004125600982c",
"reference": "cd55117beab0025fd0d3f945a5a004125600982c",
"url": "https://api.github.com/repos/utopia-php/database/zipball/f9c2b6b4e7a17f0890d3fb6238b03dae6b2670ad",
"reference": "f9c2b6b4e7a17f0890d3fb6238b03dae6b2670ad",
"shasum": ""
},
"require": {
@ -3549,9 +3549,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/0.67.2"
"source": "https://github.com/utopia-php/database/tree/0.68.0"
},
"time": "2025-05-06T12:11:58+00:00"
"time": "2025-05-08T12:01:20+00:00"
},
{
"name": "utopia-php/domains",
@ -5043,16 +5043,16 @@
},
{
"name": "laravel/pint",
"version": "v1.22.0",
"version": "v1.22.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
"reference": "7ddfaa6523a675fae5c4123ee38fc6bfb8ee4f36"
"reference": "941d1927c5ca420c22710e98420287169c7bcaf7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pint/zipball/7ddfaa6523a675fae5c4123ee38fc6bfb8ee4f36",
"reference": "7ddfaa6523a675fae5c4123ee38fc6bfb8ee4f36",
"url": "https://api.github.com/repos/laravel/pint/zipball/941d1927c5ca420c22710e98420287169c7bcaf7",
"reference": "941d1927c5ca420c22710e98420287169c7bcaf7",
"shasum": ""
},
"require": {
@ -5064,11 +5064,11 @@
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.75.0",
"illuminate/view": "^11.44.2",
"larastan/larastan": "^3.3.1",
"illuminate/view": "^11.44.7",
"larastan/larastan": "^3.4.0",
"laravel-zero/framework": "^11.36.1",
"mockery/mockery": "^1.6.12",
"nunomaduro/termwind": "^2.3",
"nunomaduro/termwind": "^2.3.1",
"pestphp/pest": "^2.36.0"
},
"bin": [
@ -5105,7 +5105,7 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
"time": "2025-04-08T22:11:45+00:00"
"time": "2025-05-08T08:38:12+00:00"
},
{
"name": "matthiasmullie/minify",
@ -8140,7 +8140,7 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=8.0.0",
"php": ">=8.3.0",
"ext-curl": "*",
"ext-imagick": "*",
"ext-mbstring": "*",

View file

@ -0,0 +1,14 @@
import 'package:dart_appwrite/dart_appwrite.dart';
Client client = Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setSession(''); // The user session to authenticate with
Databases databases = Databases(client);
DocumentList result = await databases.createDocuments(
databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>',
documents: [],
);

View file

@ -0,0 +1,14 @@
import 'package:dart_appwrite/dart_appwrite.dart';
Client client = Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setKey('<YOUR_API_KEY>'); // Your secret API key
Databases databases = Databases(client);
await databases.deleteDocuments(
databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>',
queries: [], // (optional)
);

View file

@ -0,0 +1,15 @@
import 'package:dart_appwrite/dart_appwrite.dart';
Client client = Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setKey('<YOUR_API_KEY>'); // Your secret API key
Databases databases = Databases(client);
DocumentList result = await databases.updateDocuments(
databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>',
data: {}, // (optional)
queries: [], // (optional)
);

View file

@ -0,0 +1,14 @@
import 'package:dart_appwrite/dart_appwrite.dart';
Client client = Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setKey('<YOUR_API_KEY>'); // Your secret API key
Databases databases = Databases(client);
DocumentList result = await databases.upsertDocuments(
databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>',
documents: [], // (optional)
);

View file

@ -0,0 +1,14 @@
import { Client, Databases } from "https://deno.land/x/appwrite/mod.ts";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setSession(''); // The user session to authenticate with
const databases = new Databases(client);
const response = await databases.createDocuments(
'<DATABASE_ID>', // databaseId
'<COLLECTION_ID>', // collectionId
[] // documents
);

View file

@ -0,0 +1,14 @@
import { Client, Databases } from "https://deno.land/x/appwrite/mod.ts";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setKey('<YOUR_API_KEY>'); // Your secret API key
const databases = new Databases(client);
const response = await databases.deleteDocuments(
'<DATABASE_ID>', // databaseId
'<COLLECTION_ID>', // collectionId
[] // queries (optional)
);

View file

@ -0,0 +1,15 @@
import { Client, Databases } from "https://deno.land/x/appwrite/mod.ts";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setKey('<YOUR_API_KEY>'); // Your secret API key
const databases = new Databases(client);
const response = await databases.updateDocuments(
'<DATABASE_ID>', // databaseId
'<COLLECTION_ID>', // collectionId
{}, // data (optional)
[] // queries (optional)
);

View file

@ -0,0 +1,14 @@
import { Client, Databases } from "https://deno.land/x/appwrite/mod.ts";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setKey('<YOUR_API_KEY>'); // Your secret API key
const databases = new Databases(client);
const response = await databases.upsertDocuments(
'<DATABASE_ID>', // databaseId
'<COLLECTION_ID>', // collectionId
[] // documents (optional)
);

View file

@ -0,0 +1,16 @@
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;
Client client = new Client()
.SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.SetProject("<YOUR_PROJECT_ID>") // Your project ID
.SetSession(""); // The user session to authenticate with
Databases databases = new Databases(client);
DocumentList result = await databases.CreateDocuments(
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
documents: new List<object>()
);

View file

@ -0,0 +1,16 @@
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;
Client client = new Client()
.SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.SetProject("<YOUR_PROJECT_ID>") // Your project ID
.SetKey("<YOUR_API_KEY>"); // Your secret API key
Databases databases = new Databases(client);
await databases.DeleteDocuments(
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
queries: new List<string>() // optional
);

View file

@ -0,0 +1,17 @@
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;
Client client = new Client()
.SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.SetProject("<YOUR_PROJECT_ID>") // Your project ID
.SetKey("<YOUR_API_KEY>"); // Your secret API key
Databases databases = new Databases(client);
DocumentList result = await databases.UpdateDocuments(
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
data: [object], // optional
queries: new List<string>() // optional
);

View file

@ -0,0 +1,16 @@
using Appwrite;
using Appwrite.Models;
using Appwrite.Services;
Client client = new Client()
.SetEndPoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.SetProject("<YOUR_PROJECT_ID>") // Your project ID
.SetKey("<YOUR_API_KEY>"); // Your secret API key
Databases databases = new Databases(client);
DocumentList result = await databases.UpsertDocuments(
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
documents: new List<object>() // optional
);

View file

@ -0,0 +1,28 @@
package main
import (
"fmt"
"github.com/appwrite/sdk-for-go/client"
"github.com/appwrite/sdk-for-go/databases"
)
func main() {
client := client.NewClient()
client.SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
client.SetProject("<YOUR_PROJECT_ID>") // Your project ID
client.SetSession("") // The user session to authenticate with
service := databases.NewDatabases(client)
response, error := service.CreateDocuments(
"<DATABASE_ID>",
"<COLLECTION_ID>",
[]interface{}{},
)
if error != nil {
panic(error)
}
fmt.Println(response)
}

View file

@ -0,0 +1,28 @@
package main
import (
"fmt"
"github.com/appwrite/sdk-for-go/client"
"github.com/appwrite/sdk-for-go/databases"
)
func main() {
client := client.NewClient()
client.SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
client.SetProject("<YOUR_PROJECT_ID>") // Your project ID
client.SetKey("<YOUR_API_KEY>") // Your secret API key
service := databases.NewDatabases(client)
response, error := service.DeleteDocuments(
"<DATABASE_ID>",
"<COLLECTION_ID>",
databases.WithDeleteDocumentsQueries([]interface{}{}),
)
if error != nil {
panic(error)
}
fmt.Println(response)
}

View file

@ -0,0 +1,29 @@
package main
import (
"fmt"
"github.com/appwrite/sdk-for-go/client"
"github.com/appwrite/sdk-for-go/databases"
)
func main() {
client := client.NewClient()
client.SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
client.SetProject("<YOUR_PROJECT_ID>") // Your project ID
client.SetKey("<YOUR_API_KEY>") // Your secret API key
service := databases.NewDatabases(client)
response, error := service.UpdateDocuments(
"<DATABASE_ID>",
"<COLLECTION_ID>",
databases.WithUpdateDocumentsData(map[string]interface{}{}),
databases.WithUpdateDocumentsQueries([]interface{}{}),
)
if error != nil {
panic(error)
}
fmt.Println(response)
}

View file

@ -0,0 +1,28 @@
package main
import (
"fmt"
"github.com/appwrite/sdk-for-go/client"
"github.com/appwrite/sdk-for-go/databases"
)
func main() {
client := client.NewClient()
client.SetEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
client.SetProject("<YOUR_PROJECT_ID>") // Your project ID
client.SetKey("<YOUR_API_KEY>") // Your secret API key
service := databases.NewDatabases(client)
response, error := service.UpsertDocuments(
"<DATABASE_ID>",
"<COLLECTION_ID>",
databases.WithUpsertDocumentsDocuments([]interface{}{}),
)
if error != nil {
panic(error)
}
fmt.Println(response)
}

View file

@ -0,0 +1,18 @@
mutation {
databasesCreateDocuments(
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
documents: []
) {
total
documents {
_id
_collectionId
_databaseId
_createdAt
_updatedAt
_permissions
data
}
}
}

View file

@ -0,0 +1,18 @@
mutation {
databasesDeleteDocuments(
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
queries: []
) {
total
documents {
_id
_collectionId
_databaseId
_createdAt
_updatedAt
_permissions
data
}
}
}

View file

@ -0,0 +1,19 @@
mutation {
databasesUpdateDocuments(
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
data: "{}",
queries: []
) {
total
documents {
_id
_collectionId
_databaseId
_createdAt
_updatedAt
_permissions
data
}
}
}

View file

@ -0,0 +1,18 @@
mutation {
databasesUpsertDocuments(
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
documents: []
) {
total
documents {
_id
_collectionId
_databaseId
_createdAt
_updatedAt
_permissions
data
}
}
}

View file

@ -0,0 +1,25 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Databases;
Client client = new Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
.setSession(""); // The user session to authenticate with
Databases databases = new Databases(client);
databases.createDocuments(
"<DATABASE_ID>", // databaseId
"<COLLECTION_ID>", // collectionId
listOf(), // documents
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
System.out.println(result);
})
);

View file

@ -0,0 +1,25 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Databases;
Client client = new Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
.setKey("<YOUR_API_KEY>"); // Your secret API key
Databases databases = new Databases(client);
databases.deleteDocuments(
"<DATABASE_ID>", // databaseId
"<COLLECTION_ID>", // collectionId
listOf(), // queries (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
System.out.println(result);
})
);

View file

@ -0,0 +1,26 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Databases;
Client client = new Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
.setKey("<YOUR_API_KEY>"); // Your secret API key
Databases databases = new Databases(client);
databases.updateDocuments(
"<DATABASE_ID>", // databaseId
"<COLLECTION_ID>", // collectionId
mapOf( "a" to "b" ), // data (optional)
listOf(), // queries (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
System.out.println(result);
})
);

View file

@ -0,0 +1,25 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Databases;
Client client = new Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
.setKey("<YOUR_API_KEY>"); // Your secret API key
Databases databases = new Databases(client);
databases.upsertDocuments(
"<DATABASE_ID>", // databaseId
"<COLLECTION_ID>", // collectionId
listOf(), // documents (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
System.out.println(result);
})
);

View file

@ -0,0 +1,16 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Databases
val client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
.setSession("") // The user session to authenticate with
val databases = Databases(client)
val response = databases.createDocuments(
databaseId = "<DATABASE_ID>",
collectionId = "<COLLECTION_ID>",
documents = listOf()
)

View file

@ -0,0 +1,16 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Databases
val client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
.setKey("<YOUR_API_KEY>") // Your secret API key
val databases = Databases(client)
val response = databases.deleteDocuments(
databaseId = "<DATABASE_ID>",
collectionId = "<COLLECTION_ID>",
queries = listOf() // optional
)

View file

@ -0,0 +1,17 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Databases
val client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
.setKey("<YOUR_API_KEY>") // Your secret API key
val databases = Databases(client)
val response = databases.updateDocuments(
databaseId = "<DATABASE_ID>",
collectionId = "<COLLECTION_ID>",
data = mapOf( "a" to "b" ), // optional
queries = listOf() // optional
)

View file

@ -0,0 +1,16 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Databases
val client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
.setKey("<YOUR_API_KEY>") // Your secret API key
val databases = Databases(client)
val response = databases.upsertDocuments(
databaseId = "<DATABASE_ID>",
collectionId = "<COLLECTION_ID>",
documents = listOf() // optional
)

View file

@ -0,0 +1,14 @@
const sdk = require('node-appwrite');
const client = new sdk.Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setSession(''); // The user session to authenticate with
const databases = new sdk.Databases(client);
const result = await databases.createDocuments(
'<DATABASE_ID>', // databaseId
'<COLLECTION_ID>', // collectionId
[] // documents
);

View file

@ -0,0 +1,14 @@
const sdk = require('node-appwrite');
const client = new sdk.Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setKey('<YOUR_API_KEY>'); // Your secret API key
const databases = new sdk.Databases(client);
const result = await databases.deleteDocuments(
'<DATABASE_ID>', // databaseId
'<COLLECTION_ID>', // collectionId
[] // queries (optional)
);

View file

@ -0,0 +1,15 @@
const sdk = require('node-appwrite');
const client = new sdk.Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setKey('<YOUR_API_KEY>'); // Your secret API key
const databases = new sdk.Databases(client);
const result = await databases.updateDocuments(
'<DATABASE_ID>', // databaseId
'<COLLECTION_ID>', // collectionId
{}, // data (optional)
[] // queries (optional)
);

View file

@ -0,0 +1,14 @@
const sdk = require('node-appwrite');
const client = new sdk.Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
.setProject('<YOUR_PROJECT_ID>') // Your project ID
.setKey('<YOUR_API_KEY>'); // Your secret API key
const databases = new sdk.Databases(client);
const result = await databases.upsertDocuments(
'<DATABASE_ID>', // databaseId
'<COLLECTION_ID>', // collectionId
[] // documents (optional)
);

View file

@ -0,0 +1,17 @@
<?php
use Appwrite\Client;
use Appwrite\Services\Databases;
$client = (new Client())
->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
->setProject('<YOUR_PROJECT_ID>') // Your project ID
->setSession(''); // The user session to authenticate with
$databases = new Databases($client);
$result = $databases->createDocuments(
databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>',
documents: []
);

View file

@ -0,0 +1,17 @@
<?php
use Appwrite\Client;
use Appwrite\Services\Databases;
$client = (new Client())
->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
->setProject('<YOUR_PROJECT_ID>') // Your project ID
->setKey('<YOUR_API_KEY>'); // Your secret API key
$databases = new Databases($client);
$result = $databases->deleteDocuments(
databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>',
queries: [] // optional
);

View file

@ -0,0 +1,18 @@
<?php
use Appwrite\Client;
use Appwrite\Services\Databases;
$client = (new Client())
->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
->setProject('<YOUR_PROJECT_ID>') // Your project ID
->setKey('<YOUR_API_KEY>'); // Your secret API key
$databases = new Databases($client);
$result = $databases->updateDocuments(
databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>',
data: [], // optional
queries: [] // optional
);

View file

@ -0,0 +1,17 @@
<?php
use Appwrite\Client;
use Appwrite\Services\Databases;
$client = (new Client())
->setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
->setProject('<YOUR_PROJECT_ID>') // Your project ID
->setKey('<YOUR_API_KEY>'); // Your secret API key
$databases = new Databases($client);
$result = $databases->upsertDocuments(
databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>',
documents: [] // optional
);

View file

@ -0,0 +1,15 @@
from appwrite.client import Client
from appwrite.services.databases import Databases
client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
client.set_project('<YOUR_PROJECT_ID>') # Your project ID
client.set_session('') # The user session to authenticate with
databases = Databases(client)
result = databases.create_documents(
database_id = '<DATABASE_ID>',
collection_id = '<COLLECTION_ID>',
documents = []
)

View file

@ -0,0 +1,15 @@
from appwrite.client import Client
from appwrite.services.databases import Databases
client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
client.set_project('<YOUR_PROJECT_ID>') # Your project ID
client.set_key('<YOUR_API_KEY>') # Your secret API key
databases = Databases(client)
result = databases.delete_documents(
database_id = '<DATABASE_ID>',
collection_id = '<COLLECTION_ID>',
queries = [] # optional
)

View file

@ -0,0 +1,16 @@
from appwrite.client import Client
from appwrite.services.databases import Databases
client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
client.set_project('<YOUR_PROJECT_ID>') # Your project ID
client.set_key('<YOUR_API_KEY>') # Your secret API key
databases = Databases(client)
result = databases.update_documents(
database_id = '<DATABASE_ID>',
collection_id = '<COLLECTION_ID>',
data = {}, # optional
queries = [] # optional
)

View file

@ -0,0 +1,15 @@
from appwrite.client import Client
from appwrite.services.databases import Databases
client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
client.set_project('<YOUR_PROJECT_ID>') # Your project ID
client.set_key('<YOUR_API_KEY>') # Your secret API key
databases = Databases(client)
result = databases.upsert_documents(
database_id = '<DATABASE_ID>',
collection_id = '<COLLECTION_ID>',
documents = [] # optional
)

View file

@ -0,0 +1,12 @@
POST /v1/databases/{databaseId}/collections/{collectionId}/documents HTTP/1.1
Host: cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.6.0
X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Session:
X-Appwrite-Key: <YOUR_API_KEY>
X-Appwrite-JWT: <YOUR_JWT>
{
"documents": []
}

View file

@ -0,0 +1,10 @@
DELETE /v1/databases/{databaseId}/collections/{collectionId}/documents HTTP/1.1
Host: cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.6.0
X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Key: <YOUR_API_KEY>
{
"queries": []
}

View file

@ -0,0 +1,11 @@
PATCH /v1/databases/{databaseId}/collections/{collectionId}/documents HTTP/1.1
Host: cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.6.0
X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Key: <YOUR_API_KEY>
{
"data": {},
"queries": []
}

View file

@ -0,0 +1,10 @@
PUT /v1/databases/{databaseId}/collections/{collectionId}/documents HTTP/1.1
Host: cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.6.0
X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Key: <YOUR_API_KEY>
{
"documents": []
}

View file

@ -0,0 +1,16 @@
require 'appwrite'
include Appwrite
client = Client.new
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
.set_project('<YOUR_PROJECT_ID>') # Your project ID
.set_session('') # The user session to authenticate with
databases = Databases.new(client)
result = databases.create_documents(
database_id: '<DATABASE_ID>',
collection_id: '<COLLECTION_ID>',
documents: []
)

View file

@ -0,0 +1,16 @@
require 'appwrite'
include Appwrite
client = Client.new
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
.set_project('<YOUR_PROJECT_ID>') # Your project ID
.set_key('<YOUR_API_KEY>') # Your secret API key
databases = Databases.new(client)
result = databases.delete_documents(
database_id: '<DATABASE_ID>',
collection_id: '<COLLECTION_ID>',
queries: [] # optional
)

View file

@ -0,0 +1,17 @@
require 'appwrite'
include Appwrite
client = Client.new
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
.set_project('<YOUR_PROJECT_ID>') # Your project ID
.set_key('<YOUR_API_KEY>') # Your secret API key
databases = Databases.new(client)
result = databases.update_documents(
database_id: '<DATABASE_ID>',
collection_id: '<COLLECTION_ID>',
data: {}, # optional
queries: [] # optional
)

View file

@ -0,0 +1,16 @@
require 'appwrite'
include Appwrite
client = Client.new
.set_endpoint('https://<REGION>.cloud.appwrite.io/v1') # Your API Endpoint
.set_project('<YOUR_PROJECT_ID>') # Your project ID
.set_key('<YOUR_API_KEY>') # Your secret API key
databases = Databases.new(client)
result = databases.upsert_documents(
database_id: '<DATABASE_ID>',
collection_id: '<COLLECTION_ID>',
documents: [] # optional
)

View file

@ -0,0 +1,15 @@
import Appwrite
let client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
.setSession("") // The user session to authenticate with
let databases = Databases(client)
let documentList = try await databases.createDocuments(
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
documents: []
)

View file

@ -0,0 +1,15 @@
import Appwrite
let client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
.setKey("<YOUR_API_KEY>") // Your secret API key
let databases = Databases(client)
let documentList = try await databases.deleteDocuments(
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
queries: [] // optional
)

View file

@ -0,0 +1,16 @@
import Appwrite
let client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
.setKey("<YOUR_API_KEY>") // Your secret API key
let databases = Databases(client)
let documentList = try await databases.updateDocuments(
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
data: [:], // optional
queries: [] // optional
)

View file

@ -0,0 +1,15 @@
import Appwrite
let client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
.setProject("<YOUR_PROJECT_ID>") // Your project ID
.setKey("<YOUR_API_KEY>") // Your secret API key
let databases = Databases(client)
let documentList = try await databases.upsertDocuments(
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
documents: [] // optional
)

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 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.

View file

@ -0,0 +1 @@
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.

View file

@ -0,0 +1 @@
Bulk delete documents using queries, if no queries are passed then all documents are deleted.

View file

@ -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.

View file

@ -0,0 +1 @@
Create or update 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.

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

View file

@ -117,13 +117,19 @@ class Mapper
$list = false;
foreach ($route->getParams() as $name => $parameter) {
$methodParameters = $method->getParameters();
$sdkParameters = $method->getParameters();
if (!empty($methodParameters)) {
if (!array_key_exists($name, $methodParameters)) {
if (!empty($sdkParameters)) {
$sdkMethodParameters = [];
foreach ($sdkParameters as $sdkParameter) {
$sdkMethodParameters[$sdkParameter->getName()] = $sdkParameter;
}
if (!\array_key_exists($name, $sdkMethodParameters)) {
continue;
}
$optional = $methodParameters[$name]['optional'];
$optional = $sdkMethodParameters[$name]->getOptional();
} else {
$optional = $parameter['optional'];
}

View file

@ -3,6 +3,7 @@
namespace Appwrite\Platform\Tasks;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Specification\Format\OpenAPI3;
use Appwrite\SDK\Specification\Format\Swagger2;
use Appwrite\SDK\Specification\Specification;
@ -19,7 +20,6 @@ use Utopia\Config\Config;
use Utopia\Database\Adapter\MySQL;
use Utopia\Database\Database;
use Utopia\Platform\Action;
use Utopia\Registry\Registry;
use Utopia\Request as UtopiaRequest;
use Utopia\Response as UtopiaResponse;
use Utopia\System\System;
@ -49,11 +49,10 @@ class Specs extends Action
->desc('Generate Appwrite API specifications')
->param('version', 'latest', new Text(16), 'Spec version', true)
->param('mode', 'normal', new WhiteList(['normal', 'mocks']), 'Spec Mode', true)
->inject('register')
->callback(fn (string $version, string $mode, Registry $register) => $this->action($version, $mode, $register));
->callback($this->action(...));
}
public function action(string $version, string $mode, Registry $register): void
public function action(string $version, string $mode): void
{
$appRoutes = App::getRoutes();
$response = $this->getResponse();
@ -194,7 +193,7 @@ class Specs extends Action
}
foreach ($sdks as $sdk) {
/** @var \Appwrite\SDK\Method $sdks */
/** @var Method $sdk */
$hide = $sdk->isHidden();
if ($hide === true || (\is_array($hide) && \in_array($platform, $hide))) {
@ -262,7 +261,6 @@ class Specs extends Action
$services[] = [
'name' => $service['key'] ?? '',
'description' => $service['subtitle'] ?? '',
'x-globalAttributes' => $service['globalAttributes'] ?? [],
];
}
@ -274,7 +272,15 @@ class Specs extends Action
}
}
$arguments = [new App('UTC'), $services, $routes, $models, $keys[$platform], $authCounts[$platform] ?? 0];
$arguments = [
new App('UTC'),
$services,
$routes,
$models,
$keys[$platform],
$authCounts[$platform] ?? 0
];
foreach (['swagger2', 'open-api3'] as $format) {
$formatInstance = match ($format) {
'swagger2' => new Swagger2(...$arguments),

View file

@ -21,16 +21,14 @@ class Method
* @param string $description
* @param array<AuthType> $auth
* @param array<SDKResponse> $responses
* @param ContentType $responseType
* @param MethodType|null $methodType
* @param ContentType $contentType
* @param MethodType|null $type
* @param bool $deprecated
* @param array|bool $hide
* @param bool $packaging
* @param string $requestType
* @param array $parameters
* @param ContentType $requestType
* @param array<Parameter> $parameters
* @param array $additionalParameters
*
* @throws \Exception
*/
public function __construct(
protected string $namespace,
@ -44,7 +42,7 @@ class Method
protected bool $deprecated = false,
protected array|bool $hide = false,
protected bool $packaging = false,
protected string $requestType = 'application/json',
protected ContentType $requestType = ContentType::JSON,
protected array $parameters = [],
protected array $additionalParameters = []
) {
@ -53,7 +51,6 @@ class Method
$this->validateDesc($description);
foreach ($responses as $response) {
/** @var SDKResponse $response */
$this->validateResponseModel($response->getModel());
$this->validateNoContent($response);
}
@ -191,11 +188,14 @@ class Method
return $this->packaging;
}
public function getRequestType(): string
public function getRequestType(): ContentType
{
return $this->requestType;
}
/**
* @return array<Parameter>
*/
public function getParameters(): array
{
return $this->parameters;
@ -274,7 +274,7 @@ class Method
return $this;
}
public function setRequestType(string $requestType): self
public function setRequestType(ContentType $requestType): self
{
$this->requestType = $requestType;
return $this;

View file

@ -0,0 +1,79 @@
<?php
namespace Appwrite\SDK;
use Utopia\Validator;
class Parameter
{
/**
* @param string $name
* @param string $description
* @param mixed|null $default
* @param Validator|callable|null $validator
* @param bool $optional
*/
public function __construct(
protected string $name,
protected string $description = '',
protected mixed $default = null,
protected mixed $validator = null,
protected bool $optional = false,
) {
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
public function getDescription(): string
{
return $this->description;
}
public function setDescription(string $description): static
{
$this->description = $description;
return $this;
}
public function getDefault(): mixed
{
return $this->default;
}
public function setDefault(mixed $default): static
{
$this->default = $default;
return $this;
}
public function getValidator(): mixed
{
return $this->validator;
}
public function setValidator(mixed $validator): static
{
$this->validator = $validator;
return $this;
}
public function getOptional(): bool
{
return $this->optional;
}
public function setOptional(bool $optional): static
{
$this->optional = $optional;
return $this;
}
}

View file

@ -12,12 +12,12 @@ abstract class Format
protected App $app;
/**
* @var Route[]
* @var array<Route>
*/
protected array $routes;
/**
* @var Model[]
* @var array<Model>
*/
protected array $models;
@ -415,4 +415,24 @@ abstract class Format
}
return $values;
}
protected function getNestedModels(Model $model, array &$usedModels): void
{
foreach ($model->getRules() as $rule) {
if (!in_array($model->getType(), $usedModels)) {
continue;
}
$types = (array)$rule['type'];
foreach ($types as $ruleType) {
if (!in_array($ruleType, ['string', 'integer', 'boolean', 'json', 'float'])) {
$usedModels[] = $ruleType;
foreach ($this->models as $m) {
if ($m->getType() === $ruleType) {
$this->getNestedModels($m, $usedModels);
}
}
}
}
}
}
}

View file

@ -3,7 +3,9 @@
namespace Appwrite\SDK\Specification\Format;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\MethodType;
use Appwrite\SDK\Response;
use Appwrite\SDK\Specification\Format;
use Appwrite\Template\Template;
use Appwrite\Utopia\Response\Model;
@ -22,41 +24,6 @@ class OpenAPI3 extends Format
return 'Open API 3';
}
protected function getNestedModels(Model $model, array &$usedModels): void
{
foreach ($model->getRules() as $rule) {
if (!in_array($model->getType(), $usedModels)) {
continue;
}
if (\is_array($rule['type'])) {
foreach ($rule['type'] as $ruleType) {
if (!in_array($ruleType, ['string', 'integer', 'boolean', 'json', 'float', 'double'])) {
$usedModels[] = $ruleType;
foreach ($this->models as $m) {
if ($m->getType() === $ruleType) {
$this->getNestedModels($m, $usedModels);
continue;
}
}
}
}
} else {
if (!in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float', 'double'])) {
$usedModels[] = $rule['type'];
foreach ($this->models as $m) {
if ($m->getType() === $rule['type']) {
$this->getNestedModels($m, $usedModels);
continue;
}
}
}
}
}
}
public function parse(): array
{
/**
@ -133,22 +100,20 @@ class OpenAPI3 extends Format
}
$additionalMethods = null;
if (is_array($sdk)) {
$mainSdk = array_shift($sdk);
if (\is_array($sdk)) {
$additionalMethods = $sdk;
$sdk = $mainSdk;
$sdk = $sdk[0];
}
/**
* @var \Appwrite\SDK\Method $sdk
* @var Method $sdk
*/
$consumes = [$sdk->getRequestType()];
$consumes = [$sdk->getRequestType()->value];
$method = $sdk->getMethodName() ?? \uniqid();
if (!empty($method) && is_array($method)) {
$method = array_keys($method)[0];
if (!empty($method) && \is_array($method)) {
$method = \array_keys($method)[0];
}
$desc = $sdk->getDescriptionFilePath();
@ -161,10 +126,8 @@ class OpenAPI3 extends Format
case AuthType::SESSION:
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
break;
case AuthType::KEY:
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case AuthType::JWT:
case AuthType::KEY:
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case AuthType::ADMIN:
@ -206,39 +169,47 @@ 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 */
/** @var 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) {
$additionalMethod['parameters'][] = $name;
foreach ($method->getParameters() as $parameter) {
$additionalMethod['parameters'][] = $parameter->getName();
if (!$param['optional']) {
$additionalMethod['required'][] = $name;
if (!$parameter->getOptional()) {
$additionalMethod['required'][] = $parameter->getName();
}
}
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;
}
}
// Handle response models
foreach ($sdk->getResponses() as $response) {
/** @var \Appwrite\SDK\Response $response */
/** @var Response $response */
$model = $response->getModel();
foreach ($this->models as $value) {
@ -306,11 +277,11 @@ class OpenAPI3 extends Format
}
}
if ((!empty($scope))) { // && 'public' != $scope
if ((!empty($scope))) {
$securities = ['Project' => []];
foreach ($sdk->getAuth() as $security) {
/** @var \Appwrite\SDK\AuthType $security */
/** @var AuthType $security */
if (array_key_exists($security->value, $this->keys)) {
$securities[$security->value] = [];
}

View file

@ -3,16 +3,20 @@
namespace Appwrite\SDK\Specification\Format;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\MethodType;
use Appwrite\SDK\Response;
use Appwrite\SDK\Specification\Format;
use Appwrite\Template\Template;
use Appwrite\Utopia\Response\Model;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Route;
use Utopia\Validator;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Nullable;
use Utopia\Validator\Range;
use Utopia\Validator\WhiteList;
class Swagger2 extends Format
{
@ -21,41 +25,6 @@ class Swagger2 extends Format
return 'Swagger 2';
}
protected function getNestedModels(Model $model, array &$usedModels): void
{
foreach ($model->getRules() as $rule) {
if (!in_array($model->getType(), $usedModels)) {
continue;
}
if (\is_array($rule['type'])) {
foreach ($rule['type'] as $ruleType) {
if (!in_array($ruleType, ['string', 'integer', 'boolean', 'json', 'float'])) {
$usedModels[] = $ruleType;
foreach ($this->models as $m) {
if ($m->getType() === $ruleType) {
$this->getNestedModels($m, $usedModels);
continue;
}
}
}
}
} else {
if (!in_array($rule['type'], ['string', 'integer', 'boolean', 'json', 'float'])) {
$usedModels[] = $rule['type'];
foreach ($this->models as $m) {
if ($m->getType() === $rule['type']) {
$this->getNestedModels($m, $usedModels);
continue;
}
}
}
}
}
}
public function parse(): array
{
/*
@ -118,11 +87,12 @@ class Swagger2 extends Format
$usedModels = [];
foreach ($this->routes as $route) {
/** @var \Utopia\Route $route */
/** @var Route $route */
$url = \str_replace('/v1', '', $route->getPath());
$scope = $route->getLabel('scope', '');
/** @var \Appwrite\SDK\Method $sdk */
/** @var Method $sdk */
$sdk = $route->getLabel('sdk', false);
if (empty($sdk)) {
@ -130,16 +100,14 @@ class Swagger2 extends Format
}
$additionalMethods = null;
if (is_array($sdk)) {
$mainSdk = array_shift($sdk);
if (\is_array($sdk)) {
$additionalMethods = $sdk;
$sdk = $mainSdk;
$sdk = $sdk[0];
}
$consumes = [];
if (strtoupper($route->getMethod()) !== 'GET' && strtoupper($route->getMethod()) !== 'HEAD') {
$consumes = [$sdk->getRequestType()];
$consumes = [$sdk->getRequestType()->value];
}
$method = $sdk->getMethodName() ?? \uniqid();
@ -158,10 +126,8 @@ class Swagger2 extends Format
case AuthType::SESSION:
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
break;
case AuthType::KEY:
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case AuthType::JWT:
case AuthType::KEY:
$sdkPlatforms[] = APP_PLATFORM_SERVER;
break;
case AuthType::ADMIN:
@ -208,41 +174,49 @@ 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 */
/** @var 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) {
$additionalMethod['parameters'][] = $name;
foreach ($method->getParameters() as $parameter) {
$additionalMethod['parameters'][] = $parameter->getName();
if (!$param['optional']) {
$additionalMethod['required'][] = $name;
if (!$parameter->getOptional()) {
$additionalMethod['required'][] = $parameter->getName();
}
}
foreach ($method->getResponses() as $response) {
/** @var \Appwrite\SDK\Response $response */
$additionalMethod['responses'][] = [
'code' => $response->getCode(),
'model' => '#/definitions/' . $response->getModel()
];
/** @var Response $response */
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;
}
}
// Handle Responses
foreach ($sdk->getResponses() as $response) {
/** @var \Appwrite\SDK\Response $response */
/** @var Response $response */
$model = $response->getModel();
foreach ($this->models as $value) {
@ -337,7 +311,9 @@ class Swagger2 extends Format
foreach ($parameters as $name => $param) { // Set params
/** @var Validator $validator */
$validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $this->app->getResources($param['injections'])) : $param['validator'];
$validator = (\is_callable($param['validator']))
? ($param['validator'])(...$this->app->getResources($param['injections']))
: $param['validator'];
$node = [
'name' => $name,
@ -482,7 +458,7 @@ class Swagger2 extends Format
$node['type'] = $validator->getType();
break;
case 'Utopia\Validator\WhiteList':
/** @var \Utopia\Validator\WhiteList $validator */
/** @var WhiteList $validator */
$node['type'] = $validator->getType();
$node['x-example'] = $validator->getList()[0];

View file

@ -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'
}
}
};

View file

@ -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'
}
}
};

View file

@ -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
}
};

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}`;
}

Some files were not shown because too many files have changed in this diff Show more