diff --git a/app/config/platforms.php b/app/config/platforms.php index e54fb0a073..167e55651c 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -11,7 +11,7 @@ return [ [ 'key' => 'web', 'name' => 'Web', - 'version' => '16.0.2', + 'version' => '16.1.0', 'url' => 'https://github.com/appwrite/sdk-for-web', 'package' => 'https://www.npmjs.com/package/appwrite', 'enabled' => true, @@ -59,7 +59,7 @@ return [ [ 'key' => 'flutter', 'name' => 'Flutter', - 'version' => '13.0.0', + 'version' => '13.1.1', 'url' => 'https://github.com/appwrite/sdk-for-flutter', 'package' => 'https://pub.dev/packages/appwrite', 'enabled' => true, @@ -77,7 +77,7 @@ return [ [ 'key' => 'apple', 'name' => 'Apple', - 'version' => '7.0.0', + 'version' => '7.1.0', 'url' => 'https://github.com/appwrite/sdk-for-apple', 'package' => 'https://github.com/appwrite/sdk-for-apple', 'enabled' => true, @@ -112,7 +112,7 @@ return [ [ 'key' => 'android', 'name' => 'Android', - 'version' => '6.0.0', + 'version' => '6.1.0', 'url' => 'https://github.com/appwrite/sdk-for-android', 'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android', 'enabled' => true, @@ -134,7 +134,7 @@ return [ [ 'key' => 'react-native', 'name' => 'React Native', - 'version' => '0.5.0', + 'version' => '0.6.0', 'url' => 'https://github.com/appwrite/sdk-for-react-native', 'package' => 'https://npmjs.com/package/react-native-appwrite', 'enabled' => true, diff --git a/app/config/runtimes/specifications.php b/app/config/runtimes/specifications.php index 68880f4d4e..d3625db8a2 100644 --- a/app/config/runtimes/specifications.php +++ b/app/config/runtimes/specifications.php @@ -6,7 +6,7 @@ return [ Specification::S_05VCPU_512MB => [ 'slug' => Specification::S_05VCPU_512MB, 'memory' => 512, - 'cpus' => 1 // TODO: revert this, it's a temporary change to test function performance. + 'cpus' => 0.5 ], Specification::S_1VCPU_512MB => [ 'slug' => Specification::S_1VCPU_512MB, diff --git a/app/config/specs/open-api3-latest-client.json b/app/config/specs/open-api3-latest-client.json index d948a101f2..af7303c985 100644 --- a/app/config/specs/open-api3-latest-client.json +++ b/app/config/specs/open-api3-latest-client.json @@ -1,7 +1,7 @@ { "openapi": "3.0.0", "info": { - "version": "1.6.0", + "version": "1.6.1", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -5766,7 +5766,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 382, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -5851,7 +5851,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 386, + "weight": 380, "cookies": false, "type": "", "deprecated": false, diff --git a/app/config/specs/open-api3-latest-console.json b/app/config/specs/open-api3-latest-console.json index fb01509ecd..6878909f64 100644 --- a/app/config/specs/open-api3-latest-console.json +++ b/app/config/specs/open-api3-latest-console.json @@ -1,7 +1,7 @@ { "openapi": "3.0.0", "info": { - "version": "1.6.0", + "version": "1.6.1", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -9488,7 +9488,8 @@ "bun-1.0", "bun-1.1", "go-1.23", - "static-1" + "static-1", + "flutter-3.24" ], "x-enum-name": null, "x-enum-keys": [] @@ -10145,7 +10146,8 @@ "bun-1.0", "bun-1.1", "go-1.23", - "static-1" + "static-1", + "flutter-3.24" ], "x-enum-name": null, "x-enum-keys": [] @@ -13673,7 +13675,7 @@ }, "x-appwrite": { "method": "listMessages", - "weight": 390, + "weight": 384, "cookies": false, "type": "", "deprecated": false, @@ -13751,7 +13753,7 @@ }, "x-appwrite": { "method": "createEmail", - "weight": 387, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -13897,7 +13899,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 394, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -14045,7 +14047,7 @@ }, "x-appwrite": { "method": "createPush", - "weight": 389, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -14202,7 +14204,7 @@ }, "x-appwrite": { "method": "updatePush", - "weight": 396, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -14361,7 +14363,7 @@ }, "x-appwrite": { "method": "createSms", - "weight": 388, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -14472,7 +14474,7 @@ }, "x-appwrite": { "method": "updateSms", - "weight": 395, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -14586,7 +14588,7 @@ }, "x-appwrite": { "method": "getMessage", - "weight": 393, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -14641,7 +14643,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 397, + "weight": 391, "cookies": false, "type": "", "deprecated": false, @@ -14705,7 +14707,7 @@ }, "x-appwrite": { "method": "listMessageLogs", - "weight": 391, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -14782,7 +14784,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 392, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -14859,7 +14861,7 @@ }, "x-appwrite": { "method": "listProviders", - "weight": 362, + "weight": 356, "cookies": false, "type": "", "deprecated": false, @@ -14937,7 +14939,7 @@ }, "x-appwrite": { "method": "createApnsProvider", - "weight": 361, + "weight": 355, "cookies": false, "type": "", "deprecated": false, @@ -15044,7 +15046,7 @@ }, "x-appwrite": { "method": "updateApnsProvider", - "weight": 374, + "weight": 368, "cookies": false, "type": "", "deprecated": false, @@ -15154,7 +15156,7 @@ }, "x-appwrite": { "method": "createFcmProvider", - "weight": 360, + "weight": 354, "cookies": false, "type": "", "deprecated": false, @@ -15241,7 +15243,7 @@ }, "x-appwrite": { "method": "updateFcmProvider", - "weight": 373, + "weight": 367, "cookies": false, "type": "", "deprecated": false, @@ -15331,7 +15333,7 @@ }, "x-appwrite": { "method": "createMailgunProvider", - "weight": 352, + "weight": 346, "cookies": false, "type": "", "deprecated": false, @@ -15448,7 +15450,7 @@ }, "x-appwrite": { "method": "updateMailgunProvider", - "weight": 365, + "weight": 359, "cookies": false, "type": "", "deprecated": false, @@ -15568,7 +15570,7 @@ }, "x-appwrite": { "method": "createMsg91Provider", - "weight": 355, + "weight": 349, "cookies": false, "type": "", "deprecated": false, @@ -15665,7 +15667,7 @@ }, "x-appwrite": { "method": "updateMsg91Provider", - "weight": 368, + "weight": 362, "cookies": false, "type": "", "deprecated": false, @@ -15765,7 +15767,7 @@ }, "x-appwrite": { "method": "createSendgridProvider", - "weight": 353, + "weight": 347, "cookies": false, "type": "", "deprecated": false, @@ -15872,7 +15874,7 @@ }, "x-appwrite": { "method": "updateSendgridProvider", - "weight": 366, + "weight": 360, "cookies": false, "type": "", "deprecated": false, @@ -15982,7 +15984,7 @@ }, "x-appwrite": { "method": "createSmtpProvider", - "weight": 354, + "weight": 348, "cookies": false, "type": "", "deprecated": false, @@ -16127,7 +16129,7 @@ }, "x-appwrite": { "method": "updateSmtpProvider", - "weight": 367, + "weight": 361, "cookies": false, "type": "", "deprecated": false, @@ -16274,7 +16276,7 @@ }, "x-appwrite": { "method": "createTelesignProvider", - "weight": 356, + "weight": 350, "cookies": false, "type": "", "deprecated": false, @@ -16371,7 +16373,7 @@ }, "x-appwrite": { "method": "updateTelesignProvider", - "weight": 369, + "weight": 363, "cookies": false, "type": "", "deprecated": false, @@ -16471,7 +16473,7 @@ }, "x-appwrite": { "method": "createTextmagicProvider", - "weight": 357, + "weight": 351, "cookies": false, "type": "", "deprecated": false, @@ -16568,7 +16570,7 @@ }, "x-appwrite": { "method": "updateTextmagicProvider", - "weight": 370, + "weight": 364, "cookies": false, "type": "", "deprecated": false, @@ -16668,7 +16670,7 @@ }, "x-appwrite": { "method": "createTwilioProvider", - "weight": 358, + "weight": 352, "cookies": false, "type": "", "deprecated": false, @@ -16765,7 +16767,7 @@ }, "x-appwrite": { "method": "updateTwilioProvider", - "weight": 371, + "weight": 365, "cookies": false, "type": "", "deprecated": false, @@ -16865,7 +16867,7 @@ }, "x-appwrite": { "method": "createVonageProvider", - "weight": 359, + "weight": 353, "cookies": false, "type": "", "deprecated": false, @@ -16962,7 +16964,7 @@ }, "x-appwrite": { "method": "updateVonageProvider", - "weight": 372, + "weight": 366, "cookies": false, "type": "", "deprecated": false, @@ -17062,7 +17064,7 @@ }, "x-appwrite": { "method": "getProvider", - "weight": 364, + "weight": 358, "cookies": false, "type": "", "deprecated": false, @@ -17117,7 +17119,7 @@ }, "x-appwrite": { "method": "deleteProvider", - "weight": 375, + "weight": 369, "cookies": false, "type": "", "deprecated": false, @@ -17181,7 +17183,7 @@ }, "x-appwrite": { "method": "listProviderLogs", - "weight": 363, + "weight": 357, "cookies": false, "type": "", "deprecated": false, @@ -17258,7 +17260,7 @@ }, "x-appwrite": { "method": "listSubscriberLogs", - "weight": 384, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -17335,7 +17337,7 @@ }, "x-appwrite": { "method": "listTopics", - "weight": 377, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -17411,7 +17413,7 @@ }, "x-appwrite": { "method": "createTopic", - "weight": 376, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -17496,7 +17498,7 @@ }, "x-appwrite": { "method": "getTopic", - "weight": 379, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -17558,7 +17560,7 @@ }, "x-appwrite": { "method": "updateTopic", - "weight": 380, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -17637,7 +17639,7 @@ }, "x-appwrite": { "method": "deleteTopic", - "weight": 381, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -17701,7 +17703,7 @@ }, "x-appwrite": { "method": "listTopicLogs", - "weight": 378, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -17778,7 +17780,7 @@ }, "x-appwrite": { "method": "listSubscribers", - "weight": 383, + "weight": 377, "cookies": false, "type": "", "deprecated": false, @@ -17864,7 +17866,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 382, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -17956,7 +17958,7 @@ }, "x-appwrite": { "method": "getSubscriber", - "weight": 385, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -18021,7 +18023,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 386, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -18098,7 +18100,7 @@ }, "x-appwrite": { "method": "list", - "weight": 339, + "weight": 338, "cookies": false, "type": "", "deprecated": false, @@ -18264,7 +18266,7 @@ }, "x-appwrite": { "method": "getAppwriteReport", - "weight": 341, + "weight": 340, "cookies": false, "type": "", "deprecated": false, @@ -18339,7 +18341,7 @@ }, "\/migrations\/firebase": { "post": { - "summary": "Migrate Firebase data (Service Account)", + "summary": "Migrate Firebase data", "operationId": "migrationsCreateFirebaseMigration", "tags": [ "migrations" @@ -18359,7 +18361,7 @@ }, "x-appwrite": { "method": "createFirebaseMigration", - "weight": 336, + "weight": 335, "cookies": false, "type": "", "deprecated": false, @@ -18415,177 +18417,6 @@ } } }, - "\/migrations\/firebase\/deauthorize": { - "get": { - "summary": "Revoke Appwrite's authorization to access Firebase projects", - "operationId": "migrationsDeleteFirebaseAuth", - "tags": [ - "migrations" - ], - "description": "", - "responses": { - "200": { - "description": "File" - } - }, - "x-appwrite": { - "method": "deleteFirebaseAuth", - "weight": 347, - "cookies": false, - "type": "", - "deprecated": false, - "demo": "migrations\/delete-firebase-auth.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master", - "rate-limit": 0, - "rate-time": 3600, - "rate-key": "url:{url},ip:{ip}", - "scope": "migrations.write", - "platforms": [ - "console" - ], - "packaging": false, - "offline-model": "", - "offline-key": "", - "offline-response-key": "$id", - "auth": { - "Project": [] - } - }, - "security": [ - { - "Project": [] - } - ] - } - }, - "\/migrations\/firebase\/oauth": { - "post": { - "summary": "Migrate Firebase data (OAuth)", - "operationId": "migrationsCreateFirebaseOAuthMigration", - "tags": [ - "migrations" - ], - "description": "", - "responses": { - "202": { - "description": "Migration", - "content": { - "application\/json": { - "schema": { - "$ref": "#\/components\/schemas\/migration" - } - } - } - } - }, - "x-appwrite": { - "method": "createFirebaseOAuthMigration", - "weight": 335, - "cookies": false, - "type": "", - "deprecated": false, - "demo": "migrations\/create-firebase-o-auth-migration.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/migrations\/migration-firebase.md", - "rate-limit": 0, - "rate-time": 3600, - "rate-key": "url:{url},ip:{ip}", - "scope": "migrations.write", - "platforms": [ - "console" - ], - "packaging": false, - "offline-model": "", - "offline-key": "", - "offline-response-key": "$id", - "auth": { - "Project": [] - } - }, - "security": [ - { - "Project": [] - } - ], - "requestBody": { - "content": { - "application\/json": { - "schema": { - "type": "object", - "properties": { - "resources": { - "type": "array", - "description": "List of resources to migrate", - "x-example": null, - "items": { - "type": "string" - } - }, - "projectId": { - "type": "string", - "description": "Project ID of the Firebase Project", - "x-example": "" - } - }, - "required": [ - "resources", - "projectId" - ] - } - } - } - } - } - }, - "\/migrations\/firebase\/projects": { - "get": { - "summary": "List Firebase projects", - "operationId": "migrationsListFirebaseProjects", - "tags": [ - "migrations" - ], - "description": "", - "responses": { - "200": { - "description": "Migrations Firebase Projects List", - "content": { - "application\/json": { - "schema": { - "$ref": "#\/components\/schemas\/firebaseProjectList" - } - } - } - } - }, - "x-appwrite": { - "method": "listFirebaseProjects", - "weight": 346, - "cookies": false, - "type": "", - "deprecated": false, - "demo": "migrations\/list-firebase-projects.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master", - "rate-limit": 0, - "rate-time": 3600, - "rate-key": "url:{url},ip:{ip}", - "scope": "migrations.read", - "platforms": [ - "console" - ], - "packaging": false, - "offline-model": "", - "offline-key": "", - "offline-response-key": "$id", - "auth": { - "Project": [] - } - }, - "security": [ - { - "Project": [] - } - ] - } - }, "\/migrations\/firebase\/report": { "get": { "summary": "Generate a report on Firebase data", @@ -18608,7 +18439,7 @@ }, "x-appwrite": { "method": "getFirebaseReport", - "weight": 342, + "weight": 341, "cookies": false, "type": "", "deprecated": false, @@ -18660,80 +18491,6 @@ ] } }, - "\/migrations\/firebase\/report\/oauth": { - "get": { - "summary": "Generate a report on Firebase data using OAuth", - "operationId": "migrationsGetFirebaseReportOAuth", - "tags": [ - "migrations" - ], - "description": "", - "responses": { - "200": { - "description": "Migration Report", - "content": { - "application\/json": { - "schema": { - "$ref": "#\/components\/schemas\/migrationReport" - } - } - } - } - }, - "x-appwrite": { - "method": "getFirebaseReportOAuth", - "weight": 343, - "cookies": false, - "type": "", - "deprecated": false, - "demo": "migrations\/get-firebase-report-o-auth.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/migrations\/migration-firebase-report.md", - "rate-limit": 0, - "rate-time": 3600, - "rate-key": "url:{url},ip:{ip}", - "scope": "migrations.write", - "platforms": [ - "console" - ], - "packaging": false, - "offline-model": "", - "offline-key": "", - "offline-response-key": "$id", - "auth": { - "Project": [] - } - }, - "security": [ - { - "Project": [] - } - ], - "parameters": [ - { - "name": "resources", - "description": "List of resources to migrate", - "required": true, - "schema": { - "type": "array", - "items": { - "type": "string" - } - }, - "in": "query" - }, - { - "name": "projectId", - "description": "Project ID", - "required": true, - "schema": { - "type": "string", - "x-example": "" - }, - "in": "query" - } - ] - } - }, "\/migrations\/nhost": { "post": { "summary": "Migrate NHost data", @@ -18756,7 +18513,7 @@ }, "x-appwrite": { "method": "createNHostMigration", - "weight": 338, + "weight": 337, "cookies": false, "type": "", "deprecated": false, @@ -18869,7 +18626,7 @@ }, "x-appwrite": { "method": "getNHostReport", - "weight": 349, + "weight": 343, "cookies": false, "type": "", "deprecated": false, @@ -19004,7 +18761,7 @@ }, "x-appwrite": { "method": "createSupabaseMigration", - "weight": 337, + "weight": 336, "cookies": false, "type": "", "deprecated": false, @@ -19111,7 +18868,7 @@ }, "x-appwrite": { "method": "getSupabaseReport", - "weight": 348, + "weight": 342, "cookies": false, "type": "", "deprecated": false, @@ -19237,7 +18994,7 @@ }, "x-appwrite": { "method": "get", - "weight": 340, + "weight": 339, "cookies": false, "type": "", "deprecated": false, @@ -19297,7 +19054,7 @@ }, "x-appwrite": { "method": "retry", - "weight": 350, + "weight": 344, "cookies": false, "type": "", "deprecated": false, @@ -19350,7 +19107,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 351, + "weight": 345, "cookies": false, "type": "", "deprecated": false, @@ -32749,30 +32506,6 @@ "migrations" ] }, - "firebaseProjectList": { - "description": "Migrations Firebase Projects List", - "type": "object", - "properties": { - "total": { - "type": "integer", - "description": "Total number of projects documents that matched your query.", - "x-example": 5, - "format": "int32" - }, - "projects": { - "type": "array", - "description": "List of projects.", - "items": { - "$ref": "#\/components\/schemas\/firebaseProject" - }, - "x-example": "" - } - }, - "required": [ - "total", - "projects" - ] - }, "specificationList": { "description": "Specifications List", "type": "object", @@ -35109,7 +34842,7 @@ }, "schedule": { "type": "string", - "description": "Function execution schedult in CRON format.", + "description": "Function execution schedule in CRON format.", "x-example": "5 4 * * *" }, "timeout": { @@ -38707,26 +38440,6 @@ "size", "version" ] - }, - "firebaseProject": { - "description": "MigrationFirebaseProject", - "type": "object", - "properties": { - "projectId": { - "type": "string", - "description": "Project ID.", - "x-example": "my-project" - }, - "displayName": { - "type": "string", - "description": "Project display name.", - "x-example": "My Project" - } - }, - "required": [ - "projectId", - "displayName" - ] } }, "securitySchemes": { diff --git a/app/config/specs/open-api3-latest-server.json b/app/config/specs/open-api3-latest-server.json index 1504322456..8900d6ddf2 100644 --- a/app/config/specs/open-api3-latest-server.json +++ b/app/config/specs/open-api3-latest-server.json @@ -1,7 +1,7 @@ { "openapi": "3.0.0", "info": { - "version": "1.6.0", + "version": "1.6.1", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -8596,7 +8596,8 @@ "bun-1.0", "bun-1.1", "go-1.23", - "static-1" + "static-1", + "flutter-3.24" ], "x-enum-name": null, "x-enum-keys": [] @@ -9019,7 +9020,8 @@ "bun-1.0", "bun-1.1", "go-1.23", - "static-1" + "static-1", + "flutter-3.24" ], "x-enum-name": null, "x-enum-keys": [] @@ -12527,7 +12529,7 @@ }, "x-appwrite": { "method": "listMessages", - "weight": 390, + "weight": 384, "cookies": false, "type": "", "deprecated": false, @@ -12606,7 +12608,7 @@ }, "x-appwrite": { "method": "createEmail", - "weight": 387, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -12753,7 +12755,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 394, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -12902,7 +12904,7 @@ }, "x-appwrite": { "method": "createPush", - "weight": 389, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -13060,7 +13062,7 @@ }, "x-appwrite": { "method": "updatePush", - "weight": 396, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -13220,7 +13222,7 @@ }, "x-appwrite": { "method": "createSms", - "weight": 388, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -13332,7 +13334,7 @@ }, "x-appwrite": { "method": "updateSms", - "weight": 395, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -13447,7 +13449,7 @@ }, "x-appwrite": { "method": "getMessage", - "weight": 393, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -13503,7 +13505,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 397, + "weight": 391, "cookies": false, "type": "", "deprecated": false, @@ -13568,7 +13570,7 @@ }, "x-appwrite": { "method": "listMessageLogs", - "weight": 391, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -13646,7 +13648,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 392, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -13724,7 +13726,7 @@ }, "x-appwrite": { "method": "listProviders", - "weight": 362, + "weight": 356, "cookies": false, "type": "", "deprecated": false, @@ -13803,7 +13805,7 @@ }, "x-appwrite": { "method": "createApnsProvider", - "weight": 361, + "weight": 355, "cookies": false, "type": "", "deprecated": false, @@ -13911,7 +13913,7 @@ }, "x-appwrite": { "method": "updateApnsProvider", - "weight": 374, + "weight": 368, "cookies": false, "type": "", "deprecated": false, @@ -14022,7 +14024,7 @@ }, "x-appwrite": { "method": "createFcmProvider", - "weight": 360, + "weight": 354, "cookies": false, "type": "", "deprecated": false, @@ -14110,7 +14112,7 @@ }, "x-appwrite": { "method": "updateFcmProvider", - "weight": 373, + "weight": 367, "cookies": false, "type": "", "deprecated": false, @@ -14201,7 +14203,7 @@ }, "x-appwrite": { "method": "createMailgunProvider", - "weight": 352, + "weight": 346, "cookies": false, "type": "", "deprecated": false, @@ -14319,7 +14321,7 @@ }, "x-appwrite": { "method": "updateMailgunProvider", - "weight": 365, + "weight": 359, "cookies": false, "type": "", "deprecated": false, @@ -14440,7 +14442,7 @@ }, "x-appwrite": { "method": "createMsg91Provider", - "weight": 355, + "weight": 349, "cookies": false, "type": "", "deprecated": false, @@ -14538,7 +14540,7 @@ }, "x-appwrite": { "method": "updateMsg91Provider", - "weight": 368, + "weight": 362, "cookies": false, "type": "", "deprecated": false, @@ -14639,7 +14641,7 @@ }, "x-appwrite": { "method": "createSendgridProvider", - "weight": 353, + "weight": 347, "cookies": false, "type": "", "deprecated": false, @@ -14747,7 +14749,7 @@ }, "x-appwrite": { "method": "updateSendgridProvider", - "weight": 366, + "weight": 360, "cookies": false, "type": "", "deprecated": false, @@ -14858,7 +14860,7 @@ }, "x-appwrite": { "method": "createSmtpProvider", - "weight": 354, + "weight": 348, "cookies": false, "type": "", "deprecated": false, @@ -15004,7 +15006,7 @@ }, "x-appwrite": { "method": "updateSmtpProvider", - "weight": 367, + "weight": 361, "cookies": false, "type": "", "deprecated": false, @@ -15152,7 +15154,7 @@ }, "x-appwrite": { "method": "createTelesignProvider", - "weight": 356, + "weight": 350, "cookies": false, "type": "", "deprecated": false, @@ -15250,7 +15252,7 @@ }, "x-appwrite": { "method": "updateTelesignProvider", - "weight": 369, + "weight": 363, "cookies": false, "type": "", "deprecated": false, @@ -15351,7 +15353,7 @@ }, "x-appwrite": { "method": "createTextmagicProvider", - "weight": 357, + "weight": 351, "cookies": false, "type": "", "deprecated": false, @@ -15449,7 +15451,7 @@ }, "x-appwrite": { "method": "updateTextmagicProvider", - "weight": 370, + "weight": 364, "cookies": false, "type": "", "deprecated": false, @@ -15550,7 +15552,7 @@ }, "x-appwrite": { "method": "createTwilioProvider", - "weight": 358, + "weight": 352, "cookies": false, "type": "", "deprecated": false, @@ -15648,7 +15650,7 @@ }, "x-appwrite": { "method": "updateTwilioProvider", - "weight": 371, + "weight": 365, "cookies": false, "type": "", "deprecated": false, @@ -15749,7 +15751,7 @@ }, "x-appwrite": { "method": "createVonageProvider", - "weight": 359, + "weight": 353, "cookies": false, "type": "", "deprecated": false, @@ -15847,7 +15849,7 @@ }, "x-appwrite": { "method": "updateVonageProvider", - "weight": 372, + "weight": 366, "cookies": false, "type": "", "deprecated": false, @@ -15948,7 +15950,7 @@ }, "x-appwrite": { "method": "getProvider", - "weight": 364, + "weight": 358, "cookies": false, "type": "", "deprecated": false, @@ -16004,7 +16006,7 @@ }, "x-appwrite": { "method": "deleteProvider", - "weight": 375, + "weight": 369, "cookies": false, "type": "", "deprecated": false, @@ -16069,7 +16071,7 @@ }, "x-appwrite": { "method": "listProviderLogs", - "weight": 363, + "weight": 357, "cookies": false, "type": "", "deprecated": false, @@ -16147,7 +16149,7 @@ }, "x-appwrite": { "method": "listSubscriberLogs", - "weight": 384, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -16225,7 +16227,7 @@ }, "x-appwrite": { "method": "listTopics", - "weight": 377, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -16302,7 +16304,7 @@ }, "x-appwrite": { "method": "createTopic", - "weight": 376, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -16388,7 +16390,7 @@ }, "x-appwrite": { "method": "getTopic", - "weight": 379, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -16451,7 +16453,7 @@ }, "x-appwrite": { "method": "updateTopic", - "weight": 380, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -16531,7 +16533,7 @@ }, "x-appwrite": { "method": "deleteTopic", - "weight": 381, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -16596,7 +16598,7 @@ }, "x-appwrite": { "method": "listTopicLogs", - "weight": 378, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -16674,7 +16676,7 @@ }, "x-appwrite": { "method": "listSubscribers", - "weight": 383, + "weight": 377, "cookies": false, "type": "", "deprecated": false, @@ -16761,7 +16763,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 382, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -16855,7 +16857,7 @@ }, "x-appwrite": { "method": "getSubscriber", - "weight": 385, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -16921,7 +16923,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 386, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -25746,7 +25748,7 @@ }, "schedule": { "type": "string", - "description": "Function execution schedult in CRON format.", + "description": "Function execution schedule in CRON format.", "x-example": "5 4 * * *" }, "timeout": { diff --git a/app/config/specs/swagger2-latest-client.json b/app/config/specs/swagger2-latest-client.json index 3ac70ffe28..77025ec042 100644 --- a/app/config/specs/swagger2-latest-client.json +++ b/app/config/specs/swagger2-latest-client.json @@ -1,7 +1,7 @@ { "swagger": "2.0", "info": { - "version": "1.6.0", + "version": "1.6.1", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -5981,7 +5981,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 382, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -6070,7 +6070,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 386, + "weight": 380, "cookies": false, "type": "", "deprecated": false, diff --git a/app/config/specs/swagger2-latest-console.json b/app/config/specs/swagger2-latest-console.json index 25c0c289b3..0ef937050c 100644 --- a/app/config/specs/swagger2-latest-console.json +++ b/app/config/specs/swagger2-latest-console.json @@ -1,7 +1,7 @@ { "swagger": "2.0", "info": { - "version": "1.6.0", + "version": "1.6.1", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -9610,7 +9610,8 @@ "bun-1.0", "bun-1.1", "go-1.23", - "static-1" + "static-1", + "flutter-3.24" ], "x-enum-name": null, "x-enum-keys": [] @@ -10286,7 +10287,8 @@ "bun-1.0", "bun-1.1", "go-1.23", - "static-1" + "static-1", + "flutter-3.24" ], "x-enum-name": null, "x-enum-keys": [] @@ -13890,7 +13892,7 @@ }, "x-appwrite": { "method": "listMessages", - "weight": 390, + "weight": 384, "cookies": false, "type": "", "deprecated": false, @@ -13967,7 +13969,7 @@ }, "x-appwrite": { "method": "createEmail", - "weight": 387, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -14127,7 +14129,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 394, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -14284,7 +14286,7 @@ }, "x-appwrite": { "method": "createPush", - "weight": 389, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -14459,7 +14461,7 @@ }, "x-appwrite": { "method": "updatePush", - "weight": 396, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -14631,7 +14633,7 @@ }, "x-appwrite": { "method": "createSms", - "weight": 388, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -14751,7 +14753,7 @@ }, "x-appwrite": { "method": "updateSms", - "weight": 395, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -14869,7 +14871,7 @@ }, "x-appwrite": { "method": "getMessage", - "weight": 393, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -14928,7 +14930,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 397, + "weight": 391, "cookies": false, "type": "", "deprecated": false, @@ -14992,7 +14994,7 @@ }, "x-appwrite": { "method": "listMessageLogs", - "weight": 391, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -15068,7 +15070,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 392, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -15144,7 +15146,7 @@ }, "x-appwrite": { "method": "listProviders", - "weight": 362, + "weight": 356, "cookies": false, "type": "", "deprecated": false, @@ -15221,7 +15223,7 @@ }, "x-appwrite": { "method": "createApnsProvider", - "weight": 361, + "weight": 355, "cookies": false, "type": "", "deprecated": false, @@ -15338,7 +15340,7 @@ }, "x-appwrite": { "method": "updateApnsProvider", - "weight": 374, + "weight": 368, "cookies": false, "type": "", "deprecated": false, @@ -15453,7 +15455,7 @@ }, "x-appwrite": { "method": "createFcmProvider", - "weight": 360, + "weight": 354, "cookies": false, "type": "", "deprecated": false, @@ -15546,7 +15548,7 @@ }, "x-appwrite": { "method": "updateFcmProvider", - "weight": 373, + "weight": 367, "cookies": false, "type": "", "deprecated": false, @@ -15637,7 +15639,7 @@ }, "x-appwrite": { "method": "createMailgunProvider", - "weight": 352, + "weight": 346, "cookies": false, "type": "", "deprecated": false, @@ -15766,7 +15768,7 @@ }, "x-appwrite": { "method": "updateMailgunProvider", - "weight": 365, + "weight": 359, "cookies": false, "type": "", "deprecated": false, @@ -15893,7 +15895,7 @@ }, "x-appwrite": { "method": "createMsg91Provider", - "weight": 355, + "weight": 349, "cookies": false, "type": "", "deprecated": false, @@ -15998,7 +16000,7 @@ }, "x-appwrite": { "method": "updateMsg91Provider", - "weight": 368, + "weight": 362, "cookies": false, "type": "", "deprecated": false, @@ -16101,7 +16103,7 @@ }, "x-appwrite": { "method": "createSendgridProvider", - "weight": 353, + "weight": 347, "cookies": false, "type": "", "deprecated": false, @@ -16218,7 +16220,7 @@ }, "x-appwrite": { "method": "updateSendgridProvider", - "weight": 366, + "weight": 360, "cookies": false, "type": "", "deprecated": false, @@ -16333,7 +16335,7 @@ }, "x-appwrite": { "method": "createSmtpProvider", - "weight": 354, + "weight": 348, "cookies": false, "type": "", "deprecated": false, @@ -16494,7 +16496,7 @@ }, "x-appwrite": { "method": "updateSmtpProvider", - "weight": 367, + "weight": 361, "cookies": false, "type": "", "deprecated": false, @@ -16652,7 +16654,7 @@ }, "x-appwrite": { "method": "createTelesignProvider", - "weight": 356, + "weight": 350, "cookies": false, "type": "", "deprecated": false, @@ -16757,7 +16759,7 @@ }, "x-appwrite": { "method": "updateTelesignProvider", - "weight": 369, + "weight": 363, "cookies": false, "type": "", "deprecated": false, @@ -16860,7 +16862,7 @@ }, "x-appwrite": { "method": "createTextmagicProvider", - "weight": 357, + "weight": 351, "cookies": false, "type": "", "deprecated": false, @@ -16965,7 +16967,7 @@ }, "x-appwrite": { "method": "updateTextmagicProvider", - "weight": 370, + "weight": 364, "cookies": false, "type": "", "deprecated": false, @@ -17068,7 +17070,7 @@ }, "x-appwrite": { "method": "createTwilioProvider", - "weight": 358, + "weight": 352, "cookies": false, "type": "", "deprecated": false, @@ -17173,7 +17175,7 @@ }, "x-appwrite": { "method": "updateTwilioProvider", - "weight": 371, + "weight": 365, "cookies": false, "type": "", "deprecated": false, @@ -17276,7 +17278,7 @@ }, "x-appwrite": { "method": "createVonageProvider", - "weight": 359, + "weight": 353, "cookies": false, "type": "", "deprecated": false, @@ -17381,7 +17383,7 @@ }, "x-appwrite": { "method": "updateVonageProvider", - "weight": 372, + "weight": 366, "cookies": false, "type": "", "deprecated": false, @@ -17484,7 +17486,7 @@ }, "x-appwrite": { "method": "getProvider", - "weight": 364, + "weight": 358, "cookies": false, "type": "", "deprecated": false, @@ -17543,7 +17545,7 @@ }, "x-appwrite": { "method": "deleteProvider", - "weight": 375, + "weight": 369, "cookies": false, "type": "", "deprecated": false, @@ -17607,7 +17609,7 @@ }, "x-appwrite": { "method": "listProviderLogs", - "weight": 363, + "weight": 357, "cookies": false, "type": "", "deprecated": false, @@ -17683,7 +17685,7 @@ }, "x-appwrite": { "method": "listSubscriberLogs", - "weight": 384, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -17759,7 +17761,7 @@ }, "x-appwrite": { "method": "listTopics", - "weight": 377, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -17834,7 +17836,7 @@ }, "x-appwrite": { "method": "createTopic", - "weight": 376, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -17926,7 +17928,7 @@ }, "x-appwrite": { "method": "getTopic", - "weight": 379, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -17988,7 +17990,7 @@ }, "x-appwrite": { "method": "updateTopic", - "weight": 380, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -18071,7 +18073,7 @@ }, "x-appwrite": { "method": "deleteTopic", - "weight": 381, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -18135,7 +18137,7 @@ }, "x-appwrite": { "method": "listTopicLogs", - "weight": 378, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -18211,7 +18213,7 @@ }, "x-appwrite": { "method": "listSubscribers", - "weight": 383, + "weight": 377, "cookies": false, "type": "", "deprecated": false, @@ -18294,7 +18296,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 382, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -18386,7 +18388,7 @@ }, "x-appwrite": { "method": "getSubscriber", - "weight": 385, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -18453,7 +18455,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 386, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -18528,7 +18530,7 @@ }, "x-appwrite": { "method": "list", - "weight": 339, + "weight": 338, "cookies": false, "type": "", "deprecated": false, @@ -18699,7 +18701,7 @@ }, "x-appwrite": { "method": "getAppwriteReport", - "weight": 341, + "weight": 340, "cookies": false, "type": "", "deprecated": false, @@ -18767,7 +18769,7 @@ }, "\/migrations\/firebase": { "post": { - "summary": "Migrate Firebase data (Service Account)", + "summary": "Migrate Firebase data", "operationId": "migrationsCreateFirebaseMigration", "consumes": [ "application\/json" @@ -18789,7 +18791,7 @@ }, "x-appwrite": { "method": "createFirebaseMigration", - "weight": 336, + "weight": 335, "cookies": false, "type": "", "deprecated": false, @@ -18847,192 +18849,6 @@ ] } }, - "\/migrations\/firebase\/deauthorize": { - "get": { - "summary": "Revoke Appwrite's authorization to access Firebase projects", - "operationId": "migrationsDeleteFirebaseAuth", - "consumes": [ - "application\/json" - ], - "produces": [ - "application\/json" - ], - "tags": [ - "migrations" - ], - "description": "", - "responses": { - "200": { - "description": "File", - "schema": { - "type": "file" - } - } - }, - "x-appwrite": { - "method": "deleteFirebaseAuth", - "weight": 347, - "cookies": false, - "type": "", - "deprecated": false, - "demo": "migrations\/delete-firebase-auth.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master", - "rate-limit": 0, - "rate-time": 3600, - "rate-key": "url:{url},ip:{ip}", - "scope": "migrations.write", - "platforms": [ - "console" - ], - "packaging": false, - "offline-model": "", - "offline-key": "", - "offline-response-key": "$id", - "auth": { - "Project": [] - } - }, - "security": [ - { - "Project": [] - } - ] - } - }, - "\/migrations\/firebase\/oauth": { - "post": { - "summary": "Migrate Firebase data (OAuth)", - "operationId": "migrationsCreateFirebaseOAuthMigration", - "consumes": [ - "application\/json" - ], - "produces": [ - "application\/json" - ], - "tags": [ - "migrations" - ], - "description": "", - "responses": { - "202": { - "description": "Migration", - "schema": { - "$ref": "#\/definitions\/migration" - } - } - }, - "x-appwrite": { - "method": "createFirebaseOAuthMigration", - "weight": 335, - "cookies": false, - "type": "", - "deprecated": false, - "demo": "migrations\/create-firebase-o-auth-migration.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/migrations\/migration-firebase.md", - "rate-limit": 0, - "rate-time": 3600, - "rate-key": "url:{url},ip:{ip}", - "scope": "migrations.write", - "platforms": [ - "console" - ], - "packaging": false, - "offline-model": "", - "offline-key": "", - "offline-response-key": "$id", - "auth": { - "Project": [] - } - }, - "security": [ - { - "Project": [] - } - ], - "parameters": [ - { - "name": "payload", - "in": "body", - "schema": { - "type": "object", - "properties": { - "resources": { - "type": "array", - "description": "List of resources to migrate", - "default": null, - "x-example": null, - "items": { - "type": "string" - } - }, - "projectId": { - "type": "string", - "description": "Project ID of the Firebase Project", - "default": null, - "x-example": "" - } - }, - "required": [ - "resources", - "projectId" - ] - } - } - ] - } - }, - "\/migrations\/firebase\/projects": { - "get": { - "summary": "List Firebase projects", - "operationId": "migrationsListFirebaseProjects", - "consumes": [ - "application\/json" - ], - "produces": [ - "application\/json" - ], - "tags": [ - "migrations" - ], - "description": "", - "responses": { - "200": { - "description": "Migrations Firebase Projects List", - "schema": { - "$ref": "#\/definitions\/firebaseProjectList" - } - } - }, - "x-appwrite": { - "method": "listFirebaseProjects", - "weight": 346, - "cookies": false, - "type": "", - "deprecated": false, - "demo": "migrations\/list-firebase-projects.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master", - "rate-limit": 0, - "rate-time": 3600, - "rate-key": "url:{url},ip:{ip}", - "scope": "migrations.read", - "platforms": [ - "console" - ], - "packaging": false, - "offline-model": "", - "offline-key": "", - "offline-response-key": "$id", - "auth": { - "Project": [] - } - }, - "security": [ - { - "Project": [] - } - ] - } - }, "\/migrations\/firebase\/report": { "get": { "summary": "Generate a report on Firebase data", @@ -19057,7 +18873,7 @@ }, "x-appwrite": { "method": "getFirebaseReport", - "weight": 342, + "weight": 341, "cookies": false, "type": "", "deprecated": false, @@ -19106,79 +18922,6 @@ ] } }, - "\/migrations\/firebase\/report\/oauth": { - "get": { - "summary": "Generate a report on Firebase data using OAuth", - "operationId": "migrationsGetFirebaseReportOAuth", - "consumes": [ - "application\/json" - ], - "produces": [ - "application\/json" - ], - "tags": [ - "migrations" - ], - "description": "", - "responses": { - "200": { - "description": "Migration Report", - "schema": { - "$ref": "#\/definitions\/migrationReport" - } - } - }, - "x-appwrite": { - "method": "getFirebaseReportOAuth", - "weight": 343, - "cookies": false, - "type": "", - "deprecated": false, - "demo": "migrations\/get-firebase-report-o-auth.md", - "edit": "https:\/\/github.com\/appwrite\/appwrite\/edit\/master\/docs\/references\/migrations\/migration-firebase-report.md", - "rate-limit": 0, - "rate-time": 3600, - "rate-key": "url:{url},ip:{ip}", - "scope": "migrations.write", - "platforms": [ - "console" - ], - "packaging": false, - "offline-model": "", - "offline-key": "", - "offline-response-key": "$id", - "auth": { - "Project": [] - } - }, - "security": [ - { - "Project": [] - } - ], - "parameters": [ - { - "name": "resources", - "description": "List of resources to migrate", - "required": true, - "type": "array", - "collectionFormat": "multi", - "items": { - "type": "string" - }, - "in": "query" - }, - { - "name": "projectId", - "description": "Project ID", - "required": true, - "type": "string", - "x-example": "", - "in": "query" - } - ] - } - }, "\/migrations\/nhost": { "post": { "summary": "Migrate NHost data", @@ -19203,7 +18946,7 @@ }, "x-appwrite": { "method": "createNHostMigration", - "weight": 338, + "weight": 337, "cookies": false, "type": "", "deprecated": false, @@ -19326,7 +19069,7 @@ }, "x-appwrite": { "method": "getNHostReport", - "weight": 349, + "weight": 343, "cookies": false, "type": "", "deprecated": false, @@ -19448,7 +19191,7 @@ }, "x-appwrite": { "method": "createSupabaseMigration", - "weight": 337, + "weight": 336, "cookies": false, "type": "", "deprecated": false, @@ -19564,7 +19307,7 @@ }, "x-appwrite": { "method": "getSupabaseReport", - "weight": 348, + "weight": 342, "cookies": false, "type": "", "deprecated": false, @@ -19679,7 +19422,7 @@ }, "x-appwrite": { "method": "get", - "weight": 340, + "weight": 339, "cookies": false, "type": "", "deprecated": false, @@ -19739,7 +19482,7 @@ }, "x-appwrite": { "method": "retry", - "weight": 350, + "weight": 344, "cookies": false, "type": "", "deprecated": false, @@ -19794,7 +19537,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 351, + "weight": 345, "cookies": false, "type": "", "deprecated": false, @@ -33250,31 +32993,6 @@ "migrations" ] }, - "firebaseProjectList": { - "description": "Migrations Firebase Projects List", - "type": "object", - "properties": { - "total": { - "type": "integer", - "description": "Total number of projects documents that matched your query.", - "x-example": 5, - "format": "int32" - }, - "projects": { - "type": "array", - "description": "List of projects.", - "items": { - "type": "object", - "$ref": "#\/definitions\/firebaseProject" - }, - "x-example": "" - } - }, - "required": [ - "total", - "projects" - ] - }, "specificationList": { "description": "Specifications List", "type": "object", @@ -35618,7 +35336,7 @@ }, "schedule": { "type": "string", - "description": "Function execution schedult in CRON format.", + "description": "Function execution schedule in CRON format.", "x-example": "5 4 * * *" }, "timeout": { @@ -39274,26 +38992,6 @@ "size", "version" ] - }, - "firebaseProject": { - "description": "MigrationFirebaseProject", - "type": "object", - "properties": { - "projectId": { - "type": "string", - "description": "Project ID.", - "x-example": "my-project" - }, - "displayName": { - "type": "string", - "description": "Project display name.", - "x-example": "My Project" - } - }, - "required": [ - "projectId", - "displayName" - ] } }, "externalDocs": { diff --git a/app/config/specs/swagger2-latest-server.json b/app/config/specs/swagger2-latest-server.json index 68bcf268fb..f5b1f24a22 100644 --- a/app/config/specs/swagger2-latest-server.json +++ b/app/config/specs/swagger2-latest-server.json @@ -1,7 +1,7 @@ { "swagger": "2.0", "info": { - "version": "1.6.0", + "version": "1.6.1", "title": "Appwrite", "description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)", "termsOfService": "https:\/\/appwrite.io\/policy\/terms", @@ -8720,7 +8720,8 @@ "bun-1.0", "bun-1.1", "go-1.23", - "static-1" + "static-1", + "flutter-3.24" ], "x-enum-name": null, "x-enum-keys": [] @@ -9166,7 +9167,8 @@ "bun-1.0", "bun-1.1", "go-1.23", - "static-1" + "static-1", + "flutter-3.24" ], "x-enum-name": null, "x-enum-keys": [] @@ -12752,7 +12754,7 @@ }, "x-appwrite": { "method": "listMessages", - "weight": 390, + "weight": 384, "cookies": false, "type": "", "deprecated": false, @@ -12830,7 +12832,7 @@ }, "x-appwrite": { "method": "createEmail", - "weight": 387, + "weight": 381, "cookies": false, "type": "", "deprecated": false, @@ -12991,7 +12993,7 @@ }, "x-appwrite": { "method": "updateEmail", - "weight": 394, + "weight": 388, "cookies": false, "type": "", "deprecated": false, @@ -13149,7 +13151,7 @@ }, "x-appwrite": { "method": "createPush", - "weight": 389, + "weight": 383, "cookies": false, "type": "", "deprecated": false, @@ -13325,7 +13327,7 @@ }, "x-appwrite": { "method": "updatePush", - "weight": 396, + "weight": 390, "cookies": false, "type": "", "deprecated": false, @@ -13498,7 +13500,7 @@ }, "x-appwrite": { "method": "createSms", - "weight": 388, + "weight": 382, "cookies": false, "type": "", "deprecated": false, @@ -13619,7 +13621,7 @@ }, "x-appwrite": { "method": "updateSms", - "weight": 395, + "weight": 389, "cookies": false, "type": "", "deprecated": false, @@ -13738,7 +13740,7 @@ }, "x-appwrite": { "method": "getMessage", - "weight": 393, + "weight": 387, "cookies": false, "type": "", "deprecated": false, @@ -13798,7 +13800,7 @@ }, "x-appwrite": { "method": "delete", - "weight": 397, + "weight": 391, "cookies": false, "type": "", "deprecated": false, @@ -13863,7 +13865,7 @@ }, "x-appwrite": { "method": "listMessageLogs", - "weight": 391, + "weight": 385, "cookies": false, "type": "", "deprecated": false, @@ -13940,7 +13942,7 @@ }, "x-appwrite": { "method": "listTargets", - "weight": 392, + "weight": 386, "cookies": false, "type": "", "deprecated": false, @@ -14017,7 +14019,7 @@ }, "x-appwrite": { "method": "listProviders", - "weight": 362, + "weight": 356, "cookies": false, "type": "", "deprecated": false, @@ -14095,7 +14097,7 @@ }, "x-appwrite": { "method": "createApnsProvider", - "weight": 361, + "weight": 355, "cookies": false, "type": "", "deprecated": false, @@ -14213,7 +14215,7 @@ }, "x-appwrite": { "method": "updateApnsProvider", - "weight": 374, + "weight": 368, "cookies": false, "type": "", "deprecated": false, @@ -14329,7 +14331,7 @@ }, "x-appwrite": { "method": "createFcmProvider", - "weight": 360, + "weight": 354, "cookies": false, "type": "", "deprecated": false, @@ -14423,7 +14425,7 @@ }, "x-appwrite": { "method": "updateFcmProvider", - "weight": 373, + "weight": 367, "cookies": false, "type": "", "deprecated": false, @@ -14515,7 +14517,7 @@ }, "x-appwrite": { "method": "createMailgunProvider", - "weight": 352, + "weight": 346, "cookies": false, "type": "", "deprecated": false, @@ -14645,7 +14647,7 @@ }, "x-appwrite": { "method": "updateMailgunProvider", - "weight": 365, + "weight": 359, "cookies": false, "type": "", "deprecated": false, @@ -14773,7 +14775,7 @@ }, "x-appwrite": { "method": "createMsg91Provider", - "weight": 355, + "weight": 349, "cookies": false, "type": "", "deprecated": false, @@ -14879,7 +14881,7 @@ }, "x-appwrite": { "method": "updateMsg91Provider", - "weight": 368, + "weight": 362, "cookies": false, "type": "", "deprecated": false, @@ -14983,7 +14985,7 @@ }, "x-appwrite": { "method": "createSendgridProvider", - "weight": 353, + "weight": 347, "cookies": false, "type": "", "deprecated": false, @@ -15101,7 +15103,7 @@ }, "x-appwrite": { "method": "updateSendgridProvider", - "weight": 366, + "weight": 360, "cookies": false, "type": "", "deprecated": false, @@ -15217,7 +15219,7 @@ }, "x-appwrite": { "method": "createSmtpProvider", - "weight": 354, + "weight": 348, "cookies": false, "type": "", "deprecated": false, @@ -15379,7 +15381,7 @@ }, "x-appwrite": { "method": "updateSmtpProvider", - "weight": 367, + "weight": 361, "cookies": false, "type": "", "deprecated": false, @@ -15538,7 +15540,7 @@ }, "x-appwrite": { "method": "createTelesignProvider", - "weight": 356, + "weight": 350, "cookies": false, "type": "", "deprecated": false, @@ -15644,7 +15646,7 @@ }, "x-appwrite": { "method": "updateTelesignProvider", - "weight": 369, + "weight": 363, "cookies": false, "type": "", "deprecated": false, @@ -15748,7 +15750,7 @@ }, "x-appwrite": { "method": "createTextmagicProvider", - "weight": 357, + "weight": 351, "cookies": false, "type": "", "deprecated": false, @@ -15854,7 +15856,7 @@ }, "x-appwrite": { "method": "updateTextmagicProvider", - "weight": 370, + "weight": 364, "cookies": false, "type": "", "deprecated": false, @@ -15958,7 +15960,7 @@ }, "x-appwrite": { "method": "createTwilioProvider", - "weight": 358, + "weight": 352, "cookies": false, "type": "", "deprecated": false, @@ -16064,7 +16066,7 @@ }, "x-appwrite": { "method": "updateTwilioProvider", - "weight": 371, + "weight": 365, "cookies": false, "type": "", "deprecated": false, @@ -16168,7 +16170,7 @@ }, "x-appwrite": { "method": "createVonageProvider", - "weight": 359, + "weight": 353, "cookies": false, "type": "", "deprecated": false, @@ -16274,7 +16276,7 @@ }, "x-appwrite": { "method": "updateVonageProvider", - "weight": 372, + "weight": 366, "cookies": false, "type": "", "deprecated": false, @@ -16378,7 +16380,7 @@ }, "x-appwrite": { "method": "getProvider", - "weight": 364, + "weight": 358, "cookies": false, "type": "", "deprecated": false, @@ -16438,7 +16440,7 @@ }, "x-appwrite": { "method": "deleteProvider", - "weight": 375, + "weight": 369, "cookies": false, "type": "", "deprecated": false, @@ -16503,7 +16505,7 @@ }, "x-appwrite": { "method": "listProviderLogs", - "weight": 363, + "weight": 357, "cookies": false, "type": "", "deprecated": false, @@ -16580,7 +16582,7 @@ }, "x-appwrite": { "method": "listSubscriberLogs", - "weight": 384, + "weight": 378, "cookies": false, "type": "", "deprecated": false, @@ -16657,7 +16659,7 @@ }, "x-appwrite": { "method": "listTopics", - "weight": 377, + "weight": 371, "cookies": false, "type": "", "deprecated": false, @@ -16733,7 +16735,7 @@ }, "x-appwrite": { "method": "createTopic", - "weight": 376, + "weight": 370, "cookies": false, "type": "", "deprecated": false, @@ -16826,7 +16828,7 @@ }, "x-appwrite": { "method": "getTopic", - "weight": 379, + "weight": 373, "cookies": false, "type": "", "deprecated": false, @@ -16889,7 +16891,7 @@ }, "x-appwrite": { "method": "updateTopic", - "weight": 380, + "weight": 374, "cookies": false, "type": "", "deprecated": false, @@ -16973,7 +16975,7 @@ }, "x-appwrite": { "method": "deleteTopic", - "weight": 381, + "weight": 375, "cookies": false, "type": "", "deprecated": false, @@ -17038,7 +17040,7 @@ }, "x-appwrite": { "method": "listTopicLogs", - "weight": 378, + "weight": 372, "cookies": false, "type": "", "deprecated": false, @@ -17115,7 +17117,7 @@ }, "x-appwrite": { "method": "listSubscribers", - "weight": 383, + "weight": 377, "cookies": false, "type": "", "deprecated": false, @@ -17199,7 +17201,7 @@ }, "x-appwrite": { "method": "createSubscriber", - "weight": 382, + "weight": 376, "cookies": false, "type": "", "deprecated": false, @@ -17293,7 +17295,7 @@ }, "x-appwrite": { "method": "getSubscriber", - "weight": 385, + "weight": 379, "cookies": false, "type": "", "deprecated": false, @@ -17361,7 +17363,7 @@ }, "x-appwrite": { "method": "deleteSubscriber", - "weight": 386, + "weight": 380, "cookies": false, "type": "", "deprecated": false, @@ -26233,7 +26235,7 @@ }, "schedule": { "type": "string", - "description": "Function execution schedult in CRON format.", + "description": "Function execution schedule in CRON format.", "x-example": "5 4 * * *" }, "timeout": { diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index aad072c50a..347b4ebbef 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -2944,7 +2944,11 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') $data['$permissions'] = $permissions; $document = new Document($data); - $checkPermissions = function (Document $collection, Document $document, string $permission) use (&$checkPermissions, $dbForProject, $database) { + $operations = 0; + + $checkPermissions = function (Document $collection, Document $document, string $permission) use (&$checkPermissions, $dbForProject, $database, &$operations) { + $operations++; + $documentSecurity = $collection->getAttribute('documentSecurity', false); $validator = new Authorization($permission); @@ -3036,6 +3040,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') throw new Exception(Exception::COLLECTION_NOT_FOUND); } + // Add $collectionId and $databaseId for all documents $processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) { $document->setAttribute('$databaseId', $database->getId()); @@ -3071,6 +3076,13 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') $processDocument($collection, $document); + $queueForUsage + ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, $operations) + ->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), $operations) + ->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection + + $response->addHeader('X-Debug-Operations', $operations); + $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($document, Response::MODEL_DOCUMENT); @@ -3090,10 +3102,6 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents') ->setContext('collection', $collection) ->setContext('database', $database) ->setPayload($response->getPayload(), sensitive: $relationships); - - - $queueForUsage - ->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection }); App::get('/v1/databases/:databaseId/collections/:collectionId/documents') @@ -3116,7 +3124,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') ->inject('response') ->inject('dbForProject') ->inject('mode') - ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, string $mode) { + ->inject('queueForUsage') + ->action(function (string $databaseId, string $collectionId, array $queries, Response $response, Database $dbForProject, string $mode, Usage $queueForUsage) { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); $isAPIKey = Auth::isAppUser(Authorization::getRoles()); $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); @@ -3152,7 +3161,6 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription()); } - $documentId = $cursor->getValue(); $cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId)); @@ -3167,12 +3175,16 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') $documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries); $total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries, APP_LIMIT_COUNT); + $operations = 0; + // Add $collectionId and $databaseId for all documents - $processDocument = (function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database): bool { + $processDocument = (function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database, &$operations): bool { if ($document->isEmpty()) { return false; } + $operations++; + $document->removeAttribute('$collection'); $document->setAttribute('$databaseId', $database->getId()); $document->setAttribute('$collectionId', $collection->getId()); @@ -3186,8 +3198,13 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') $related = $document->getAttribute($relationship->getAttribute('key')); if (empty($related)) { + if (\in_array(\gettype($related), ['array', 'object'])) { + $operations++; + } + continue; } + if (!\is_array($related)) { $relations = [$related]; } else { @@ -3195,6 +3212,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') } $relatedCollectionId = $relationship->getAttribute('relatedCollection'); + // todo: Use local cache for this getDocument $relatedCollection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)); foreach ($relations as $index => $doc) { @@ -3219,6 +3237,13 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents') $processDocument($collection, $document); } + $queueForUsage + ->addMetric(METRIC_DATABASES_OPERATIONS_READS, $operations) + ->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_READS), $operations) + ; + + $response->addHeader('X-Debug-Operations', $operations); + $select = \array_reduce($queries, function ($result, $query) { return $result || ($query->getMethod() === Query::TYPE_SELECT); }, false); @@ -3274,9 +3299,9 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen ->inject('response') ->inject('dbForProject') ->inject('mode') - ->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, string $mode) { + ->inject('queueForUsage') + ->action(function (string $databaseId, string $collectionId, string $documentId, array $queries, Response $response, Database $dbForProject, string $mode, Usage $queueForUsage) { $database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId)); - $isAPIKey = Auth::isAppUser(Authorization::getRoles()); $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles()); @@ -3303,12 +3328,16 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen throw new Exception(Exception::DOCUMENT_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) { if ($document->isEmpty()) { return; } + $operations++; + $document->setAttribute('$databaseId', $database->getId()); $document->setAttribute('$collectionId', $collection->getId()); @@ -3321,8 +3350,13 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen $related = $document->getAttribute($relationship->getAttribute('key')); if (empty($related)) { + if (\in_array(\gettype($related), ['array', 'object'])) { + $operations++; + } + continue; } + if (!\is_array($related)) { $related = [$related]; } @@ -3342,6 +3376,13 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen $processDocument($collection, $document); + $queueForUsage + ->addMetric(METRIC_DATABASES_OPERATIONS_READS, $operations) + ->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_READS), $operations) + ; + + $response->addHeader('X-Debug-Operations', $operations); + $response->dynamic($document, Response::MODEL_DOCUMENT); }); @@ -3444,6 +3485,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen $output[$i]['countryName'] = $locale->getText('locale.country.unknown'); } } + $response->dynamic(new Document([ 'total' => $audit->countLogsByResource($resource), 'logs' => $output, @@ -3481,7 +3523,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum ->inject('dbForProject') ->inject('queueForEvents') ->inject('mode') - ->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, string $mode) { + ->inject('queueForUsage') + ->action(function (string $databaseId, string $collectionId, string $documentId, string|array $data, ?array $permissions, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Usage $queueForUsage) { $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array @@ -3548,7 +3591,12 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum $data['$permissions'] = $permissions; $newDocument = new Document($data); - $setCollection = (function (Document $collection, Document $document) use (&$setCollection, $dbForProject, $database) { + $operations = 0; + + $setCollection = (function (Document $collection, Document $document) use (&$setCollection, $dbForProject, $database, &$operations) { + + $operations++; + $relationships = \array_filter( $collection->getAttribute('attributes', []), fn ($attribute) => $attribute->getAttribute('type') === Database::VAR_RELATIONSHIP @@ -3616,6 +3664,13 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum $setCollection($collection, $newDocument); + $queueForUsage + ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, $operations) + ->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), $operations) + ; + + $response->addHeader('X-Debug-Operations', $operations); + try { $document = $dbForProject->withRequestTimestamp( $requestTimestamp, @@ -3787,6 +3842,13 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu $processDocument($collection, $document); + $queueForUsage + ->addMetric(METRIC_DATABASES_OPERATIONS_WRITES, 1) + ->addMetric(str_replace('{databaseInternalId}', $database->getInternalId(), METRIC_DATABASE_ID_OPERATIONS_WRITES), 1) + ->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection + + $response->addHeader('X-Debug-Operations', 1); + $relationships = \array_map( fn ($document) => $document->getAttribute('key'), \array_filter( @@ -3803,9 +3865,6 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu ->setContext('database', $database) ->setPayload($response->output($document, Response::MODEL_DOCUMENT), sensitive: $relationships); - $queueForUsage - ->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$database->getInternalId(), $collection->getInternalId()], METRIC_DATABASE_ID_COLLECTION_ID_STORAGE), 1); // per collection - $response->noContent(); }); diff --git a/app/controllers/api/migrations.php b/app/controllers/api/migrations.php index 3abd1db016..29e46505f5 100644 --- a/app/controllers/api/migrations.php +++ b/app/controllers/api/migrations.php @@ -1,17 +1,12 @@ dynamic($migration, Response::MODEL_MIGRATION); }); -App::post('/v1/migrations/firebase/oauth') - ->groups(['api', 'migrations']) - ->desc('Migrate Firebase data (OAuth)') - ->label('scope', 'migrations.write') - ->label('event', 'migrations.[migrationId].create') - ->label('audits.event', 'migration.create') - ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) - ->label('sdk.namespace', 'migrations') - ->label('sdk.method', 'createFirebaseOAuthMigration') - ->label('sdk.description', '/docs/references/migrations/migration-firebase.md') - ->label('sdk.response.code', Response::STATUS_CODE_ACCEPTED) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_MIGRATION) - ->param('resources', [], new ArrayList(new WhiteList(Firebase::getSupportedResources())), 'List of resources to migrate') - ->param('projectId', '', new Text(65536), 'Project ID of the Firebase Project') - ->inject('response') - ->inject('dbForProject') - ->inject('dbForPlatform') - ->inject('project') - ->inject('user') - ->inject('queueForEvents') - ->inject('queueForMigrations') - ->inject('request') - ->action(function (array $resources, string $projectId, Response $response, Database $dbForProject, Database $dbForPlatform, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations, Request $request) { - $firebase = new OAuth2Firebase( - System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', ''), - System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', ''), - $request->getProtocol() . '://' . $request->getHostname() . '/v1/migrations/firebase/redirect' - ); - - $identity = $dbForPlatform->findOne('identities', [ - Query::equal('provider', ['firebase']), - Query::equal('userInternalId', [$user->getInternalId()]), - ]); - if ($identity->isEmpty()) { - throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); - } - - $accessToken = $identity->getAttribute('providerAccessToken'); - $refreshToken = $identity->getAttribute('providerRefreshToken'); - $accessTokenExpiry = $identity->getAttribute('providerAccessTokenExpiry'); - - $isExpired = new \DateTime($accessTokenExpiry) < new \DateTime('now'); - if ($isExpired) { - $firebase->refreshTokens($refreshToken); - - $accessToken = $firebase->getAccessToken(''); - $refreshToken = $firebase->getRefreshToken(''); - - $verificationId = $firebase->getUserID($accessToken); - - if (empty($verificationId)) { - throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Another request is currently refreshing OAuth token. Please try again.'); - } - - $identity = $identity - ->setAttribute('providerAccessToken', $accessToken) - ->setAttribute('providerRefreshToken', $refreshToken) - ->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$firebase->getAccessTokenExpiry(''))); - - $dbForPlatform->updateDocument('identities', $identity->getId(), $identity); - } - - if ($identity->getAttribute('secrets')) { - $serviceAccount = $identity->getAttribute('secrets'); - } else { - $firebase->cleanupServiceAccounts($accessToken, $projectId); - $serviceAccount = $firebase->createServiceAccount($accessToken, $projectId); - $identity = $identity - ->setAttribute('secrets', json_encode($serviceAccount)); - - $dbForPlatform->updateDocument('identities', $identity->getId(), $identity); - } - - $migration = $dbForProject->createDocument('migrations', new Document([ - '$id' => ID::unique(), - 'status' => 'pending', - 'stage' => 'init', - 'source' => Firebase::getName(), - 'destination' => Appwrite::getName(), - 'credentials' => [ - 'serviceAccount' => json_encode($serviceAccount), - ], - 'resources' => $resources, - 'statusCounters' => '{}', - 'resourceData' => '{}', - 'errors' => [] - ])); - - $queueForEvents->setParam('migrationId', $migration->getId()); - - // Trigger Transfer - $queueForMigrations - ->setMigration($migration) - ->setProject($project) - ->setUser($user) - ->trigger(); - - $response - ->setStatusCode(Response::STATUS_CODE_ACCEPTED) - ->dynamic($migration, Response::MODEL_MIGRATION); - }); - App::post('/v1/migrations/firebase') ->groups(['api', 'migrations']) - ->desc('Migrate Firebase data (Service Account)') + ->desc('Migrate Firebase data') ->label('scope', 'migrations.write') ->label('event', 'migrations.[migrationId].create') ->label('audits.event', 'migration.create') @@ -547,368 +437,6 @@ App::get('/v1/migrations/firebase/report') ->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT); }); -App::get('/v1/migrations/firebase/report/oauth') - ->groups(['api', 'migrations']) - ->desc('Generate a report on Firebase data using OAuth') - ->label('scope', 'migrations.write') - ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) - ->label('sdk.namespace', 'migrations') - ->label('sdk.method', 'getFirebaseReportOAuth') - ->label('sdk.description', '/docs/references/migrations/migration-firebase-report.md') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_MIGRATION_REPORT) - ->param('resources', [], new ArrayList(new WhiteList(Firebase::getSupportedResources())), 'List of resources to migrate') - ->param('projectId', '', new Text(65536), 'Project ID') - ->inject('response') - ->inject('request') - ->inject('user') - ->inject('dbForPlatform') - ->action(function (array $resources, string $projectId, Response $response, Request $request, Document $user, Database $dbForPlatform) { - $firebase = new OAuth2Firebase( - System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', ''), - System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', ''), - $request->getProtocol() . '://' . $request->getHostname() . '/v1/migrations/firebase/redirect' - ); - - $identity = $dbForPlatform->findOne('identities', [ - Query::equal('provider', ['firebase']), - Query::equal('userInternalId', [$user->getInternalId()]), - ]); - - if ($identity->isEmpty()) { - throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); - } - - $accessToken = $identity->getAttribute('providerAccessToken'); - $refreshToken = $identity->getAttribute('providerRefreshToken'); - $accessTokenExpiry = $identity->getAttribute('providerAccessTokenExpiry'); - - if (empty($accessToken) || empty($refreshToken) || empty($accessTokenExpiry)) { - throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); - } - - if (System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', '') === '' || System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', '') === '') { - throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); - } - - $isExpired = new \DateTime($accessTokenExpiry) < new \DateTime('now'); - if ($isExpired) { - $firebase->refreshTokens($refreshToken); - - $accessToken = $firebase->getAccessToken(''); - $refreshToken = $firebase->getRefreshToken(''); - - $verificationId = $firebase->getUserID($accessToken); - - if (empty($verificationId)) { - throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Another request is currently refreshing OAuth token. Please try again.'); - } - - $identity = $identity - ->setAttribute('providerAccessToken', $accessToken) - ->setAttribute('providerRefreshToken', $refreshToken) - ->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$firebase->getAccessTokenExpiry(''))); - - $dbForPlatform->updateDocument('identities', $identity->getId(), $identity); - } - - // Get Service Account - if ($identity->getAttribute('secrets')) { - $serviceAccount = $identity->getAttribute('secrets'); - } else { - $firebase->cleanupServiceAccounts($accessToken, $projectId); - $serviceAccount = $firebase->createServiceAccount($accessToken, $projectId); - $identity = $identity - ->setAttribute('secrets', json_encode($serviceAccount)); - - $dbForPlatform->updateDocument('identities', $identity->getId(), $identity); - } - - $firebase = new Firebase($serviceAccount); - - try { - $report = $firebase->report($resources); - } catch (\Throwable $e) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Source Error: ' . $e->getMessage()); - } - - $response - ->setStatusCode(Response::STATUS_CODE_OK) - ->dynamic(new Document($report), Response::MODEL_MIGRATION_REPORT); - }); - -App::get('/v1/migrations/firebase/connect') - ->desc('Authorize with Firebase') - ->groups(['api', 'migrations']) - ->label('scope', 'migrations.write') - ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) - ->label('sdk.namespace', 'migrations') - ->label('sdk.method', 'createFirebaseAuth') - ->label('sdk.description', '') - ->label('sdk.response.code', Response::STATUS_CODE_MOVED_PERMANENTLY) - ->label('sdk.response.type', Response::CONTENT_TYPE_HTML) - ->label('sdk.methodType', 'webAuth') - ->label('sdk.hide', true) - ->param('redirect', '', fn ($clients) => new Host($clients), 'URL to redirect back to your Firebase authorization. Only console hostnames are allowed.', true, ['clients']) - ->param('projectId', '', new UID(), 'Project ID') - ->inject('response') - ->inject('request') - ->inject('user') - ->inject('dbForPlatform') - ->action(function (string $redirect, string $projectId, Response $response, Request $request, Document $user, Database $dbForPlatform) { - $state = \json_encode([ - 'projectId' => $projectId, - 'redirect' => $redirect, - ]); - - $prefs = $user->getAttribute('prefs', []); - $prefs['migrationState'] = $state; - $user->setAttribute('prefs', $prefs); - $dbForPlatform->updateDocument('users', $user->getId(), $user); - - $oauth2 = new OAuth2Firebase( - System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', ''), - System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', ''), - $request->getProtocol() . '://' . $request->getHostname() . '/v1/migrations/firebase/redirect' - ); - $url = $oauth2->getLoginURL(); - - $response - ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') - ->addHeader('Pragma', 'no-cache') - ->redirect($url); - }); - -App::get('/v1/migrations/firebase/redirect') - ->desc('Capture and receive data on Firebase authorization') - ->groups(['api', 'migrations']) - ->label('scope', 'public') - ->label('error', __DIR__ . '/../../views/general/error.phtml') - ->param('code', '', new Text(2048), 'OAuth2 code. This is a temporary code that the will be later exchanged for an access token.', true) - ->inject('user') - ->inject('project') - ->inject('request') - ->inject('response') - ->inject('dbForPlatform') - ->action(function (string $code, Document $user, Document $project, Request $request, Response $response, Database $dbForPlatform) { - $state = $user['prefs']['migrationState'] ?? '{}'; - $prefs['migrationState'] = ''; - $user->setAttribute('prefs', $prefs); - $dbForPlatform->updateDocument('users', $user->getId(), $user); - - if (empty($state)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Installation requests from organisation members for the Appwrite Google App are currently unsupported.'); - } - - $state = \json_decode($state, true); - $redirect = $state['redirect'] ?? ''; - $projectId = $state['projectId'] ?? ''; - - $project = $dbForPlatform->getDocument('projects', $projectId); - - if (empty($redirect)) { - $redirect = $request->getProtocol() . '://' . $request->getHostname() . '/console/project-$projectId/settings/migrations'; - } - - if ($project->isEmpty()) { - $response - ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') - ->addHeader('Pragma', 'no-cache') - ->redirect($redirect); - - return; - } - - // OAuth Authroization - if (!empty($code)) { - $oauth2 = new OAuth2Firebase( - System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', ''), - System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', ''), - $request->getProtocol() . '://' . $request->getHostname() . '/v1/migrations/firebase/redirect' - ); - - $accessToken = $oauth2->getAccessToken($code); - $refreshToken = $oauth2->getRefreshToken($code); - $accessTokenExpiry = $oauth2->getAccessTokenExpiry($code); - $email = $oauth2->getUserEmail($accessToken); - $oauth2ID = $oauth2->getUserID($accessToken); - - if (empty($accessToken)) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to get access token.'); - } - - if (empty($refreshToken)) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to get refresh token.'); - } - - if (empty($accessTokenExpiry)) { - throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to get access token expiry.'); - } - - // Makes sure this email is not already used in another identity - $identity = $dbForPlatform->findOne('identities', [ - Query::equal('providerEmail', [$email]), - ]); - - if (!$identity->isEmpty()) { - if ($identity->getAttribute('userInternalId', '') !== $user->getInternalId()) { - throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS); - } - } - - if (!$identity->isEmpty()) { - $identity = $identity - ->setAttribute('providerAccessToken', $accessToken) - ->setAttribute('providerRefreshToken', $refreshToken) - ->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$accessTokenExpiry)); - - $dbForPlatform->updateDocument('identities', $identity->getId(), $identity); - } else { - $identity = $dbForPlatform->createDocument('identities', new Document([ - '$id' => ID::unique(), - '$permissions' => [ - Permission::read(Role::any()), - Permission::update(Role::user($user->getId())), - Permission::delete(Role::user($user->getId())), - ], - 'userInternalId' => $user->getInternalId(), - 'userId' => $user->getId(), - 'provider' => 'firebase', - 'providerUid' => $oauth2ID, - 'providerEmail' => $email, - 'providerAccessToken' => $accessToken, - 'providerRefreshToken' => $refreshToken, - 'providerAccessTokenExpiry' => DateTime::addSeconds(new \DateTime(), (int)$accessTokenExpiry), - ])); - } - } else { - throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Missing OAuth2 code.'); - } - - $response - ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') - ->addHeader('Pragma', 'no-cache') - ->redirect($redirect); - }); - -App::get('/v1/migrations/firebase/projects') - ->desc('List Firebase projects') - ->groups(['api', 'migrations']) - ->label('scope', 'migrations.read') - ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) - ->label('sdk.namespace', 'migrations') - ->label('sdk.method', 'listFirebaseProjects') - ->label('sdk.description', '') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_MIGRATION_FIREBASE_PROJECT_LIST) - ->inject('user') - ->inject('response') - ->inject('project') - ->inject('dbForPlatform') - ->inject('request') - ->action(function (Document $user, Response $response, Document $project, Database $dbForPlatform, Request $request) { - $firebase = new OAuth2Firebase( - System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', ''), - System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', ''), - $request->getProtocol() . '://' . $request->getHostname() . '/v1/migrations/firebase/redirect' - ); - - $identity = $dbForPlatform->findOne('identities', [ - Query::equal('provider', ['firebase']), - Query::equal('userInternalId', [$user->getInternalId()]), - ]); - - if ($identity->isEmpty()) { - throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); - } - - $accessToken = $identity->getAttribute('providerAccessToken'); - $refreshToken = $identity->getAttribute('providerRefreshToken'); - $accessTokenExpiry = $identity->getAttribute('providerAccessTokenExpiry'); - - if (empty($accessToken) || empty($refreshToken) || empty($accessTokenExpiry)) { - throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); - } - - if (System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_ID', '') === '' || System::getEnv('_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET', '') === '') { - throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); - } - - try { - $isExpired = new \DateTime($accessTokenExpiry) < new \DateTime('now'); - if ($isExpired) { - try { - $firebase->refreshTokens($refreshToken); - } catch (\Throwable $e) { - throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); - } - - $accessToken = $firebase->getAccessToken(''); - $refreshToken = $firebase->getRefreshToken(''); - - $verificationId = $firebase->getUserID($accessToken); - - if (empty($verificationId)) { - throw new Exception(Exception::GENERAL_RATE_LIMIT_EXCEEDED, 'Another request is currently refreshing OAuth token. Please try again.'); - } - - $identity = $identity - ->setAttribute('providerAccessToken', $accessToken) - ->setAttribute('providerRefreshToken', $refreshToken) - ->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$firebase->getAccessTokenExpiry(''))); - - $dbForPlatform->updateDocument('identities', $identity->getId(), $identity); - } - - $projects = $firebase->getProjects($accessToken); - - $output = []; - foreach ($projects as $project) { - $output[] = [ - 'displayName' => $project['displayName'], - 'projectId' => $project['projectId'], - ]; - } - } catch (\Throwable $e) { - throw new Exception(Exception::USER_IDENTITY_NOT_FOUND); - } - - $response->dynamic(new Document([ - 'projects' => $output, - 'total' => count($output), - ]), Response::MODEL_MIGRATION_FIREBASE_PROJECT_LIST); - }); - -App::get('/v1/migrations/firebase/deauthorize') - ->desc('Revoke Appwrite\'s authorization to access Firebase projects') - ->groups(['api', 'migrations']) - ->label('scope', 'migrations.write') - ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) - ->label('sdk.namespace', 'migrations') - ->label('sdk.method', 'deleteFirebaseAuth') - ->label('sdk.description', '') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->inject('user') - ->inject('response') - ->inject('dbForPlatform') - ->action(function (Document $user, Response $response, Database $dbForPlatform) { - $identity = $dbForPlatform->findOne('identities', [ - Query::equal('provider', ['firebase']), - Query::equal('userInternalId', [$user->getInternalId()]), - ]); - - if ($identity->isEmpty()) { - throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN, 'Not authenticated with Firebase'); //TODO: Replace with USER_IDENTITY_NOT_FOUND - } - - $dbForPlatform->deleteDocument('identities', $identity->getId()); - - $response->noContent(); - }); - App::get('/v1/migrations/supabase/report') ->groups(['api', 'migrations']) ->desc('Generate a report on Supabase Data') diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 7d4361a192..e92b55f140 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -6,6 +6,7 @@ use Appwrite\Auth\Auth; use Appwrite\ClamAV\Network; use Appwrite\Event\Delete; use Appwrite\Event\Event; +use Appwrite\Event\Usage; use Appwrite\Extend\Exception; use Appwrite\OpenSSL\OpenSSL; use Appwrite\Utopia\Database\Validator\CustomId; @@ -886,7 +887,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') ->inject('mode') ->inject('deviceForFiles') ->inject('deviceForLocal') - ->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, string $mode, Device $deviceForFiles, Device $deviceForLocal) { + ->inject('queueForUsage') + ->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, Request $request, Response $response, Document $project, Database $dbForProject, string $mode, Device $deviceForFiles, Device $deviceForLocal, Usage $queueForUsage) { if (!\extension_loaded('imagick')) { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing'); @@ -1014,6 +1016,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview') $contentType = (\array_key_exists($output, $outputs)) ? $outputs[$output] : $outputs['jpg']; + $queueForUsage + ->addMetric(METRIC_FILES_TRANSFORMATIONS, 1) + ->addMetric(str_replace('{bucketInternalId}', $bucket->getInternalId(), METRIC_BUCKET_ID_FILES_TRANSFORMATIONS), 1) + ; + $response ->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days ->setContentType($contentType) diff --git a/app/controllers/general.php b/app/controllers/general.php index 4cf9192078..631c240464 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -21,6 +21,7 @@ use Appwrite\Utopia\Response\Filters\V18 as ResponseV18; use Appwrite\Utopia\View; use Executor\Executor; use MaxMind\Db\Reader; +use Swoole\Database\DetectsLostConnections; use Swoole\Http\Request as SwooleRequest; use Utopia\App; use Utopia\CLI\Console; @@ -28,6 +29,7 @@ use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; +use Utopia\Database\Exception as DatabaseException; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; @@ -38,6 +40,7 @@ use Utopia\Logger\Adapter\Sentry; use Utopia\Logger\Log; use Utopia\Logger\Log\User; use Utopia\Logger\Logger; +use Utopia\Pools\Connection; use Utopia\System\System; use Utopia\Validator\Hostname; use Utopia\Validator\Text; @@ -746,7 +749,16 @@ App::error() ->inject('logger') ->inject('log') ->inject('queueForUsage') - ->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, Usage $queueForUsage) { + ->inject('connectionForProject') + ->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, Usage $queueForUsage, Connection $connectionForProject) { + if ( + ($error instanceof PDOException || $error instanceof DatabaseException) + && DetectsLostConnections::causedByLostConnection($error) + ) { + // Mark connection as unhealthy so it will be recycled on next reclaim. + $connectionForProject->setHealthy(false); + } + $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); $route = $utopia->getRoute(); $class = \get_class($error); @@ -836,7 +848,7 @@ App::error() $adapter = new Sentry($projectId, $key, $host); $logger = new Logger($adapter); - $logger->setSample(0.04); + $logger->setSample(0.01); $publish = true; } else { throw new \Exception('Invalid experimental logging provider'); diff --git a/app/http.php b/app/http.php index a5c0b6baf8..5afb0b103d 100644 --- a/app/http.php +++ b/app/http.php @@ -5,6 +5,7 @@ require_once __DIR__ . '/../vendor/autoload.php'; use Appwrite\Utopia\Request; use Appwrite\Utopia\Response; use Swoole\Constant; +use Swoole\Database\DetectsLostConnections; use Swoole\Http\Request as SwooleRequest; use Swoole\Http\Response as SwooleResponse; use Swoole\Http\Server; @@ -17,6 +18,7 @@ use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; +use Utopia\Database\Exception as DatabaseException; use Utopia\Database\Exception\Duplicate; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; @@ -42,7 +44,7 @@ $http = new Server( ); $payloadSize = 12 * (1024 * 1024); // 12MB - adding slight buffer for headers and other data that might be sent with the payload - update later with valid testing -$totalWorkers = swoole_cpu_num() * intval(System::getEnv('_APP_WORKER_PER_CORE', 6)); +$totalWorkers = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6)); $http ->set([ @@ -362,6 +364,15 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool $app->run($request, $response); } catch (\Throwable $th) { + if ( + ($th instanceof PDOException || $th instanceof DatabaseException) + && DetectsLostConnections::causedByLostConnection($th) + ) { + // Mark connection as unhealthy so it will be recycled on next reclaim. + $connectionForProject = $app->getResource('connectionForProject'); + $connectionForProject->setHealthy(false); + } + $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); $logger = $app->getResource("logger"); diff --git a/app/init.php b/app/init.php index ace69303e3..efe797175a 100644 --- a/app/init.php +++ b/app/init.php @@ -74,6 +74,7 @@ use Utopia\Logger\Adapter\Raygun; use Utopia\Logger\Adapter\Sentry; use Utopia\Logger\Log; use Utopia\Logger\Logger; +use Utopia\Pools\Connection as PoolConnection; use Utopia\Pools\Group; use Utopia\Pools\Pool; use Utopia\Queue; @@ -259,9 +260,15 @@ const METRIC_DOCUMENTS = 'documents'; const METRIC_DATABASE_ID_DOCUMENTS = '{databaseInternalId}.documents'; const METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS = '{databaseInternalId}.{collectionInternalId}.documents'; const METRIC_DATABASE_ID_COLLECTION_ID_STORAGE = '{databaseInternalId}.{collectionInternalId}.databases.storage'; +const METRIC_DATABASES_OPERATIONS_READS = 'databases.operations.reads'; +const METRIC_DATABASE_ID_OPERATIONS_READS = '{databaseInternalId}.databases.operations.reads'; +const METRIC_DATABASES_OPERATIONS_WRITES = 'databases.operations.writes'; +const METRIC_DATABASE_ID_OPERATIONS_WRITES = '{databaseInternalId}.databases.operations.writes'; const METRIC_BUCKETS = 'buckets'; const METRIC_FILES = 'files'; const METRIC_FILES_STORAGE = 'files.storage'; +const METRIC_FILES_TRANSFORMATIONS = 'files.transformations'; +const METRIC_BUCKET_ID_FILES_TRANSFORMATIONS = '{bucketInternalId}.files.transformations'; const METRIC_BUCKET_ID_FILES = '{bucketInternalId}.files'; const METRIC_BUCKET_ID_FILES_STORAGE = '{bucketInternalId}.files.storage'; const METRIC_FUNCTIONS = 'functions'; @@ -895,7 +902,7 @@ $register->set('pools', function () { $multiprocessing = System::getEnv('_APP_SERVER_MULTIPROCESS', 'disabled') === 'enabled'; if ($multiprocessing) { - $workerCount = swoole_cpu_num() * intval(System::getEnv('_APP_WORKER_PER_CORE', 6)); + $workerCount = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6)); } else { $workerCount = 1; } @@ -1413,7 +1420,26 @@ App::setResource('console', function () { ]); }, []); -App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project) { +App::setResource('connectionForProject', function (Group $pools, Document $project) { + if ($project->isEmpty() || $project->getId() === 'console') { + return $pools + ->get('console') + ->pop(); + } + + try { + $dsn = new DSN($project->getAttribute('database')); + } catch (\InvalidArgumentException) { + // TODO: Temporary until all projects are using shared tables + $dsn = new DSN('mysql://' . $project->getAttribute('database')); + } + + return $pools + ->get($dsn->getHost()) + ->pop(); +}, ['pools', 'project']); + +App::setResource('dbForProject', function (Group $pools, PoolConnection $connectionForProject, Database $dbForPlatform, Cache $cache, Document $project) { if ($project->isEmpty() || $project->getId() === 'console') { return $dbForPlatform; } @@ -1425,12 +1451,7 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform $dsn = new DSN('mysql://' . $project->getAttribute('database')); } - $dbAdapter = $pools - ->get($dsn->getHost()) - ->pop() - ->getResource(); - - $database = new Database($dbAdapter, $cache); + $database = new Database($connectionForProject->getResource(), $cache); $database ->setMetadata('host', \gethostname()) @@ -1453,7 +1474,7 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform } return $database; -}, ['pools', 'dbForPlatform', 'cache', 'project']); +}, ['pools', 'connectionForProject', 'dbForPlatform', 'cache', 'project']); App::setResource('dbForPlatform', function (Group $pools, Cache $cache) { $dbAdapter = $pools diff --git a/app/realtime.php b/app/realtime.php index 4f87e4dea1..86f9c85fdd 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -193,7 +193,7 @@ $stats->create(); $containerId = uniqid(); $statsDocument = null; -$workerNumber = swoole_cpu_num() * intval(System::getEnv('_APP_WORKER_PER_CORE', 6)); +$workerNumber = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6)); $adapter = new Adapter\Swoole(port: System::getEnv('PORT', 80)); $adapter diff --git a/app/worker.php b/app/worker.php index 6eb1363e9b..62882b32b1 100644 --- a/app/worker.php +++ b/app/worker.php @@ -16,6 +16,7 @@ use Appwrite\Event\Migration; use Appwrite\Event\Usage; use Appwrite\Event\UsageDump; use Appwrite\Platform\Appwrite; +use Swoole\Database\DetectsLostConnections; use Swoole\Runtime; use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis; use Utopia\Cache\Adapter\Sharding; @@ -25,11 +26,13 @@ use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\DateTime; use Utopia\Database\Document; +use Utopia\Database\Exception as DatabaseException; use Utopia\Database\Validator\Authorization; use Utopia\DSN\DSN; use Utopia\Logger\Log; use Utopia\Logger\Logger; use Utopia\Platform\Service; +use Utopia\Pools\Connection as PoolConnection; use Utopia\Pools\Group; use Utopia\Queue\Connection; use Utopia\Queue\Message; @@ -66,13 +69,13 @@ Server::setResource('project', function (Message $message, Database $dbForPlatfo return $dbForPlatform->getDocument('projects', $project->getId()); }, ['message', 'dbForPlatform']); -Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForPlatform) { +Server::setResource('connectionForProject', function (Group $pools, Document $project) { if ($project->isEmpty() || $project->getId() === 'console') { - return $dbForPlatform; + return $pools + ->get('console') + ->pop(); } - $pools = $register->get('pools'); - try { $dsn = new DSN($project->getAttribute('database')); } catch (\InvalidArgumentException) { @@ -80,12 +83,17 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register, $dsn = new DSN('mysql://' . $project->getAttribute('database')); } - $adapter = $pools + return $pools ->get($dsn->getHost()) - ->pop() - ->getResource(); + ->pop(); +}, ['pools', 'project']); - $database = new Database($adapter, $cache); +Server::setResource('dbForProject', function (PoolConnection $connectionForProject, Cache $cache, Registry $register, Message $message, Document $project, Database $dbForPlatform) { + if ($project->isEmpty() || $project->getId() === 'console') { + return $dbForPlatform; + } + + $database = new Database($connectionForProject->getResource(), $cache); try { $dsn = new DSN($project->getAttribute('database')); @@ -109,12 +117,12 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register, } return $database; -}, ['cache', 'register', 'message', 'project', 'dbForPlatform']); +}, ['connectionForProject', 'cache', 'register', 'message', 'project', 'dbForPlatform']); -Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) { +Server::setResource('getProjectDB', function (Group $pools, PoolConnection $connectionForProject, Database $dbForPlatform, $cache) { $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools - return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases): Database { + return function (Document $project) use ($pools, $connectionForProject, $dbForPlatform, $cache, &$databases): Database { if ($project->isEmpty() || $project->getId() === 'console') { return $dbForPlatform; } @@ -146,12 +154,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf return $database; } - $dbAdapter = $pools - ->get($dsn->getHost()) - ->pop() - ->getResource(); - - $database = new Database($dbAdapter, $cache); + $database = new Database($connectionForProject->getResource(), $cache); $databases[$dsn->getHost()] = $database; @@ -171,7 +174,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf return $database; }; -}, ['pools', 'dbForPlatform', 'cache']); +}, ['pools', 'connectionForProject', 'dbForPlatform', 'cache']); Server::setResource('abuseRetention', function () { return time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400); @@ -410,7 +413,16 @@ $worker ->inject('log') ->inject('pools') ->inject('project') - ->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project) use ($queueName) { + ->inject('connectionForProject') + ->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project, PoolConnection $connectionForProject) use ($queueName) { + if ( + ($error instanceof PDOException || $error instanceof DatabaseException) + && DetectsLostConnections::causedByLostConnection($error) + ) { + // Mark connection as unhealthy, it will be recycled on next reclaim. + $connectionForProject->setHealthy(false); + } + $pools->reclaim(); $version = System::getEnv('_APP_VERSION', 'UNKNOWN'); diff --git a/composer.json b/composer.json index 2879993d3f..2612403c1a 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,7 @@ "ext-sockets": "*", "appwrite/php-runtimes": "0.16.*", "appwrite/php-clamav": "2.0.*", - "utopia-php/abuse": "0.45.*", + "utopia-php/abuse": "0.46.*", "utopia-php/analytics": "0.10.*", "utopia-php/audit": "0.43.*", "utopia-php/cache": "0.11.*", @@ -63,7 +63,7 @@ "utopia-php/migration": "0.6.*", "utopia-php/orchestration": "0.9.*", "utopia-php/platform": "0.7.*", - "utopia-php/pools": "0.5.*", + "utopia-php/pools": "0.6.*", "utopia-php/preloader": "0.2.*", "utopia-php/queue": "0.7.*", "utopia-php/registry": "0.5.*", @@ -84,7 +84,7 @@ }, "require-dev": { "ext-fileinfo": "*", - "appwrite/sdk-generator": "0.39.*", + "appwrite/sdk-generator": "0.39.28", "phpunit/phpunit": "9.5.20", "swoole/ide-helper": "5.1.2", "textalk/websocket": "1.5.7", diff --git a/composer.lock b/composer.lock index 63a7559b37..a0c89acd1a 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "db3b63ba1be2561ee4a112e5b9c6bd53", + "content-hash": "7b5b5926b452186543903eb539f59c2d", "packages": [ { "name": "adhocore/jwt", @@ -2403,12 +2403,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -2453,16 +2453,16 @@ }, { "name": "symfony/http-client", - "version": "v7.2.1", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "ff4df2b68d1c67abb9fef146e6540ea16b58d99e" + "reference": "339ba21476eb184290361542f732ad12c97591ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/ff4df2b68d1c67abb9fef146e6540ea16b58d99e", - "reference": "ff4df2b68d1c67abb9fef146e6540ea16b58d99e", + "url": "https://api.github.com/repos/symfony/http-client/zipball/339ba21476eb184290361542f732ad12c97591ec", + "reference": "339ba21476eb184290361542f732ad12c97591ec", "shasum": "" }, "require": { @@ -2528,7 +2528,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.2.1" + "source": "https://github.com/symfony/http-client/tree/v7.2.2" }, "funding": [ { @@ -2544,7 +2544,7 @@ "type": "tidelift" } ], - "time": "2024-12-07T08:50:44+00:00" + "time": "2024-12-30T18:35:15+00:00" }, { "name": "symfony/http-client-contracts", @@ -2884,12 +2884,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -3136,16 +3136,16 @@ }, { "name": "utopia-php/abuse", - "version": "0.45.0", + "version": "0.46.0", "source": { "type": "git", "url": "https://github.com/utopia-php/abuse.git", - "reference": "54a58355df1eb26c9be7834738debe6754e5581d" + "reference": "bef426a9a28b3e71b08216bcbe310455f588c11b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/abuse/zipball/54a58355df1eb26c9be7834738debe6754e5581d", - "reference": "54a58355df1eb26c9be7834738debe6754e5581d", + "url": "https://api.github.com/repos/utopia-php/abuse/zipball/bef426a9a28b3e71b08216bcbe310455f588c11b", + "reference": "bef426a9a28b3e71b08216bcbe310455f588c11b", "shasum": "" }, "require": { @@ -3181,9 +3181,9 @@ ], "support": { "issues": "https://github.com/utopia-php/abuse/issues", - "source": "https://github.com/utopia-php/abuse/tree/0.45.0" + "source": "https://github.com/utopia-php/abuse/tree/0.46.0" }, - "time": "2024-12-20T16:18:52+00:00" + "time": "2024-12-27T17:46:08+00:00" }, { "name": "utopia-php/analytics", @@ -3929,16 +3929,16 @@ }, { "name": "utopia-php/migration", - "version": "0.6.13", + "version": "0.6.14", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "68d9b0a9477755afcda607e7e8109785cae17a13" + "reference": "59a19f09ded0ccab4c8cca35b1242c01e2b9cfd2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/68d9b0a9477755afcda607e7e8109785cae17a13", - "reference": "68d9b0a9477755afcda607e7e8109785cae17a13", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/59a19f09ded0ccab4c8cca35b1242c01e2b9cfd2", + "reference": "59a19f09ded0ccab4c8cca35b1242c01e2b9cfd2", "shasum": "" }, "require": { @@ -3979,9 +3979,9 @@ ], "support": { "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/0.6.13" + "source": "https://github.com/utopia-php/migration/tree/0.6.14" }, - "time": "2024-11-26T13:57:53+00:00" + "time": "2025-01-08T01:07:25+00:00" }, { "name": "utopia-php/mongo", @@ -4145,25 +4145,25 @@ }, { "name": "utopia-php/pools", - "version": "0.5.0", + "version": "0.6.0", "source": { "type": "git", "url": "https://github.com/utopia-php/pools.git", - "reference": "6f716a213a08db95eda1b5dddfa90983c1834817" + "reference": "59414ab7b57728edfde6d5ccc5a2583b7967ac18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/pools/zipball/6f716a213a08db95eda1b5dddfa90983c1834817", - "reference": "6f716a213a08db95eda1b5dddfa90983c1834817", + "url": "https://api.github.com/repos/utopia-php/pools/zipball/59414ab7b57728edfde6d5ccc5a2583b7967ac18", + "reference": "59414ab7b57728edfde6d5ccc5a2583b7967ac18", "shasum": "" }, "require": { - "php": ">=8.0" + "php": ">=8.3" }, "require-dev": { - "laravel/pint": "1.2.*", - "phpstan/phpstan": "1.8.*", - "phpunit/phpunit": "^9.3" + "laravel/pint": "1.*", + "phpstan/phpstan": "1.*", + "phpunit/phpunit": "11.*" }, "type": "library", "autoload": { @@ -4190,9 +4190,9 @@ ], "support": { "issues": "https://github.com/utopia-php/pools/issues", - "source": "https://github.com/utopia-php/pools/tree/0.5.0" + "source": "https://github.com/utopia-php/pools/tree/0.6.0" }, - "time": "2024-04-19T11:11:54+00:00" + "time": "2025-01-08T07:58:42+00:00" }, { "name": "utopia-php/preloader", @@ -4807,16 +4807,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.39.27", + "version": "0.39.28", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "27d8ecde30e40cbfe1124cc0430c406d3e144849" + "reference": "6ff467858fe418e364460da905139216570a5d5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/27d8ecde30e40cbfe1124cc0430c406d3e144849", - "reference": "27d8ecde30e40cbfe1124cc0430c406d3e144849", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/6ff467858fe418e364460da905139216570a5d5e", + "reference": "6ff467858fe418e364460da905139216570a5d5e", "shasum": "" }, "require": { @@ -4852,9 +4852,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/0.39.27" + "source": "https://github.com/appwrite/sdk-generator/tree/0.39.28" }, - "time": "2024-12-16T11:32:02+00:00" + "time": "2024-12-30T11:17:25+00:00" }, { "name": "doctrine/annotations", @@ -5126,16 +5126,16 @@ }, { "name": "laravel/pint", - "version": "v1.18.3", + "version": "v1.19.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "cef51821608239040ab841ad6e1c6ae502ae3026" + "reference": "8169513746e1bac70c85d6ea1524d9225d4886f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/cef51821608239040ab841ad6e1c6ae502ae3026", - "reference": "cef51821608239040ab841ad6e1c6ae502ae3026", + "url": "https://api.github.com/repos/laravel/pint/zipball/8169513746e1bac70c85d6ea1524d9225d4886f0", + "reference": "8169513746e1bac70c85d6ea1524d9225d4886f0", "shasum": "" }, "require": { @@ -5146,10 +5146,10 @@ "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.65.0", - "illuminate/view": "^10.48.24", - "larastan/larastan": "^2.9.11", - "laravel-zero/framework": "^10.4.0", + "friendsofphp/php-cs-fixer": "^3.66.0", + "illuminate/view": "^10.48.25", + "larastan/larastan": "^2.9.12", + "laravel-zero/framework": "^10.48.25", "mockery/mockery": "^1.6.12", "nunomaduro/termwind": "^1.17.0", "pestphp/pest": "^2.36.0" @@ -5188,7 +5188,7 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2024-11-26T15:34:00+00:00" + "time": "2024-12-30T16:20:10+00:00" }, { "name": "matthiasmullie/minify", @@ -5376,16 +5376,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.3.1", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" + "reference": "447a020a1f875a434d62f2a401f53b82a396e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", - "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", + "reference": "447a020a1f875a434d62f2a401f53b82a396e494", "shasum": "" }, "require": { @@ -5428,9 +5428,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" }, - "time": "2024-10-08T18:51:32+00:00" + "time": "2024-12-30T11:07:19+00:00" }, { "name": "phar-io/manifest", @@ -7735,16 +7735,16 @@ }, { "name": "symfony/finder", - "version": "v7.2.0", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49" + "reference": "87a71856f2f56e4100373e92529eed3171695cfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/6de263e5868b9a137602dd1e33e4d48bfae99c49", - "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49", + "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb", + "reference": "87a71856f2f56e4100373e92529eed3171695cfb", "shasum": "" }, "require": { @@ -7779,7 +7779,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.2.0" + "source": "https://github.com/symfony/finder/tree/v7.2.2" }, "funding": [ { @@ -7795,7 +7795,7 @@ "type": "tidelift" } ], - "time": "2024-10-23T06:56:12+00:00" + "time": "2024-12-30T19:00:17+00:00" }, { "name": "symfony/options-resolver", diff --git a/docs/examples/1.6.x/client-graphql/examples/account/create-push-target.md b/docs/examples/1.6.x/client-graphql/examples/account/create-push-target.md index 8a0fad387c..63802a782e 100644 --- a/docs/examples/1.6.x/client-graphql/examples/account/create-push-target.md +++ b/docs/examples/1.6.x/client-graphql/examples/account/create-push-target.md @@ -12,5 +12,6 @@ mutation { providerId providerType identifier + expired } } diff --git a/docs/examples/1.6.x/client-graphql/examples/account/create.md b/docs/examples/1.6.x/client-graphql/examples/account/create.md index 3f8e3c3cf7..0d39394a3d 100644 --- a/docs/examples/1.6.x/client-graphql/examples/account/create.md +++ b/docs/examples/1.6.x/client-graphql/examples/account/create.md @@ -33,6 +33,7 @@ mutation { providerId providerType identifier + expired } accessedAt } diff --git a/docs/examples/1.6.x/client-graphql/examples/account/get.md b/docs/examples/1.6.x/client-graphql/examples/account/get.md index e4db8f0e41..f4f07c187f 100644 --- a/docs/examples/1.6.x/client-graphql/examples/account/get.md +++ b/docs/examples/1.6.x/client-graphql/examples/account/get.md @@ -28,6 +28,7 @@ query { providerId providerType identifier + expired } accessedAt } diff --git a/docs/examples/1.6.x/client-graphql/examples/account/update-email.md b/docs/examples/1.6.x/client-graphql/examples/account/update-email.md index b207bad4bb..c879e24a43 100644 --- a/docs/examples/1.6.x/client-graphql/examples/account/update-email.md +++ b/docs/examples/1.6.x/client-graphql/examples/account/update-email.md @@ -31,6 +31,7 @@ mutation { providerId providerType identifier + expired } accessedAt } diff --git a/docs/examples/1.6.x/client-graphql/examples/account/update-m-f-a.md b/docs/examples/1.6.x/client-graphql/examples/account/update-m-f-a.md index d2cd3d6ae5..787c2e0860 100644 --- a/docs/examples/1.6.x/client-graphql/examples/account/update-m-f-a.md +++ b/docs/examples/1.6.x/client-graphql/examples/account/update-m-f-a.md @@ -30,6 +30,7 @@ mutation { providerId providerType identifier + expired } accessedAt } diff --git a/docs/examples/1.6.x/client-graphql/examples/account/update-mfa-authenticator.md b/docs/examples/1.6.x/client-graphql/examples/account/update-mfa-authenticator.md index c74062c7d4..9cfe9150be 100644 --- a/docs/examples/1.6.x/client-graphql/examples/account/update-mfa-authenticator.md +++ b/docs/examples/1.6.x/client-graphql/examples/account/update-mfa-authenticator.md @@ -31,6 +31,7 @@ mutation { providerId providerType identifier + expired } accessedAt } diff --git a/docs/examples/1.6.x/client-graphql/examples/account/update-name.md b/docs/examples/1.6.x/client-graphql/examples/account/update-name.md index 850b5760a0..8ba2c99d9c 100644 --- a/docs/examples/1.6.x/client-graphql/examples/account/update-name.md +++ b/docs/examples/1.6.x/client-graphql/examples/account/update-name.md @@ -30,6 +30,7 @@ mutation { providerId providerType identifier + expired } accessedAt } diff --git a/docs/examples/1.6.x/client-graphql/examples/account/update-password.md b/docs/examples/1.6.x/client-graphql/examples/account/update-password.md index 5904da0842..f3619a10d2 100644 --- a/docs/examples/1.6.x/client-graphql/examples/account/update-password.md +++ b/docs/examples/1.6.x/client-graphql/examples/account/update-password.md @@ -31,6 +31,7 @@ mutation { providerId providerType identifier + expired } accessedAt } diff --git a/docs/examples/1.6.x/client-graphql/examples/account/update-phone.md b/docs/examples/1.6.x/client-graphql/examples/account/update-phone.md index 408a203300..adecb71168 100644 --- a/docs/examples/1.6.x/client-graphql/examples/account/update-phone.md +++ b/docs/examples/1.6.x/client-graphql/examples/account/update-phone.md @@ -31,6 +31,7 @@ mutation { providerId providerType identifier + expired } accessedAt } diff --git a/docs/examples/1.6.x/client-graphql/examples/account/update-prefs.md b/docs/examples/1.6.x/client-graphql/examples/account/update-prefs.md index 40db7b43db..57280247e4 100644 --- a/docs/examples/1.6.x/client-graphql/examples/account/update-prefs.md +++ b/docs/examples/1.6.x/client-graphql/examples/account/update-prefs.md @@ -30,6 +30,7 @@ mutation { providerId providerType identifier + expired } accessedAt } diff --git a/docs/examples/1.6.x/client-graphql/examples/account/update-push-target.md b/docs/examples/1.6.x/client-graphql/examples/account/update-push-target.md index 059702dc97..3c402cdf12 100644 --- a/docs/examples/1.6.x/client-graphql/examples/account/update-push-target.md +++ b/docs/examples/1.6.x/client-graphql/examples/account/update-push-target.md @@ -11,5 +11,6 @@ mutation { providerId providerType identifier + expired } } diff --git a/docs/examples/1.6.x/client-graphql/examples/account/update-status.md b/docs/examples/1.6.x/client-graphql/examples/account/update-status.md index aca8c098e7..c17f556842 100644 --- a/docs/examples/1.6.x/client-graphql/examples/account/update-status.md +++ b/docs/examples/1.6.x/client-graphql/examples/account/update-status.md @@ -28,6 +28,7 @@ mutation { providerId providerType identifier + expired } accessedAt } diff --git a/docs/examples/1.6.x/client-graphql/examples/messaging/create-subscriber.md b/docs/examples/1.6.x/client-graphql/examples/messaging/create-subscriber.md index b2712ebb48..bab53612b7 100644 --- a/docs/examples/1.6.x/client-graphql/examples/messaging/create-subscriber.md +++ b/docs/examples/1.6.x/client-graphql/examples/messaging/create-subscriber.md @@ -17,6 +17,7 @@ mutation { providerId providerType identifier + expired } userId userName diff --git a/src/Appwrite/Auth/OAuth2/Firebase.php b/src/Appwrite/Auth/OAuth2/Firebase.php deleted file mode 100644 index 0e2859e32c..0000000000 --- a/src/Appwrite/Auth/OAuth2/Firebase.php +++ /dev/null @@ -1,389 +0,0 @@ - 'offline', - 'client_id' => $this->appID, - 'redirect_uri' => $this->callback, - 'scope' => \implode(' ', $this->getScopes()), - 'state' => \json_encode($this->state), - 'response_type' => 'code', - 'prompt' => 'consent', - ]); - } - - /** - * @param string $code - * - * @return array - */ - protected function getTokens(string $code): array - { - if (empty($this->tokens)) { - $response = $this->request( - 'POST', - 'https://oauth2.googleapis.com/token', - [], - \http_build_query([ - 'client_id' => $this->appID, - 'redirect_uri' => $this->callback, - 'client_secret' => $this->appSecret, - 'code' => $code, - 'grant_type' => 'authorization_code' - ]) - ); - - $this->tokens = \json_decode($response, true); - } - - return $this->tokens; - } - - /** - * @param string $refreshToken - * - * @return array - */ - public function refreshTokens(string $refreshToken): array - { - $response = $this->request( - 'POST', - 'https://oauth2.googleapis.com/token', - [], - \http_build_query([ - 'client_id' => $this->appID, - 'client_secret' => $this->appSecret, - 'grant_type' => 'refresh_token', - 'refresh_token' => $refreshToken - ]) - ); - - $output = []; - \parse_str($response, $output); - $this->tokens = $output; - - if (empty($this->tokens['refresh_token'])) { - $this->tokens['refresh_token'] = $refreshToken; - } - - return $this->tokens; - } - - - /** - * @param string $accessToken - * - * @return string - */ - public function getUserID(string $accessToken): string - { - $user = $this->getUser($accessToken); - - return $user['id'] ?? ''; - } - - /** - * @param string $accessToken - * - * @return string - */ - public function getUserEmail(string $accessToken): string - { - $user = $this->getUser($accessToken); - - return $user['email'] ?? ''; - } - - - /** - * Check if the OAuth email is verified - * - * @link https://docs.github.com/en/rest/users/emails#list-email-addresses-for-the-authenticated-user - * - * @param string $accessToken - * - * @return bool - */ - public function isEmailVerified(string $accessToken): bool - { - $user = $this->getUser($accessToken); - - if ($user['verified'] ?? false) { - return true; - } - - return false; - } - - /** - * @param string $accessToken - * - * @return string - */ - public function getUserName(string $accessToken): string - { - $user = $this->getUser($accessToken); - - return $user['name'] ?? ''; - } - - /** - * @param string $accessToken - * - * @return array - */ - protected function getUser(string $accessToken) - { - if (empty($this->user)) { - $response = $this->request( - 'GET', - 'https://www.googleapis.com/oauth2/v1/userinfo?access_token=' . \urlencode($accessToken), - [], - ); - - $this->user = \json_decode($response, true); - } - - return $this->user; - } - - public function getProjects(string $accessToken): array - { - $projects = $this->request('GET', 'https://firebase.googleapis.com/v1beta1/projects', ['Authorization: Bearer ' . \urlencode($accessToken)]); - - $projects = \json_decode($projects, true); - - return $projects['results']; - } - - /* - Be careful with the setIAMPolicy method, it will overwrite all existing policies - **/ - public function assignIAMRole(string $accessToken, string $email, string $projectId, array $role) - { - // Get IAM Roles - $iamRoles = $this->request('POST', 'https://cloudresourcemanager.googleapis.com/v1/projects/' . $projectId . ':getIamPolicy', [ - 'Authorization: Bearer ' . \urlencode($accessToken), - 'Content-Type: application/json' - ]); - - $iamRoles = \json_decode($iamRoles, true); - - $iamRoles['bindings'][] = [ - 'role' => $role['name'], - 'members' => [ - 'serviceAccount:' . $email - ] - ]; - - // Set IAM Roles - $this->request('POST', 'https://cloudresourcemanager.googleapis.com/v1/projects/' . $projectId . ':setIamPolicy', [ - 'Authorization: Bearer ' . \urlencode($accessToken), - 'Content-Type: application/json' - ], \json_encode([ - 'policy' => $iamRoles - ])); - } - - private function generateRandomString($length = 10): string - { - $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; - $charactersLength = strlen($characters); - $randomString = ''; - for ($i = 0; $i < $length; $i++) { - $randomString .= $characters[random_int(0, $charactersLength - 1)]; - } - return $randomString; - } - - private function createCustomRole(string $accessToken, string $projectId): array - { - // Check if role already exists - try { - $role = $this->request('GET', 'https://iam.googleapis.com/v1/projects/' . $projectId . '/roles/appwriteMigrations', [ - 'Content-Type: application/json', - 'Authorization: Bearer ' . \urlencode($accessToken), - ]); - - $role = \json_decode($role, true); - - return $role; - } catch (\Throwable $e) { - if ($e->getCode() !== 404) { - throw $e; - } - } - - // Create role if doesn't exist or isn't correct - $role = $this->request( - 'POST', - 'https://iam.googleapis.com/v1/projects/' . $projectId . '/roles/', - [ - 'Content-Type: application/json', - 'Authorization: Bearer ' . \urlencode($accessToken), - ], - \json_encode( - [ - 'roleId' => 'appwriteMigrations', - 'role' => [ - 'title' => 'Appwrite Migrations', - 'description' => 'A helper role for Appwrite Migrations', - 'includedPermissions' => $this->iamPermissions, - 'stage' => 'GA' - ] - ] - ) - ); - - return json_decode($role, true); - } - - public function createServiceAccount(string $accessToken, string $projectId): array - { - // Create Service Account - $uid = $this->generateRandomString(); - - $response = $this->request( - 'POST', - 'https://iam.googleapis.com/v1/projects/' . $projectId . '/serviceAccounts', - [ - 'Authorization: Bearer ' . \urlencode($accessToken), - 'Content-Type: application/json' - ], - \json_encode([ - 'accountId' => 'appwrite-' . $uid, - 'serviceAccount' => [ - 'displayName' => 'Appwrite Migrations ' . $uid - ] - ]) - ); - - $response = json_decode($response, true); - - // Create and assign IAM Roles - $role = $this->createCustomRole($accessToken, $projectId); - - \sleep(1); // Wait for IAM to propagate changes. - - $this->assignIAMRole($accessToken, $response['email'], $projectId, $role); - - // Create Service Account Key - $responseKey = $this->request( - 'POST', - 'https://iam.googleapis.com/v1/projects/' . $projectId . '/serviceAccounts/' . $response['email'] . '/keys', - [ - 'Authorization: Bearer ' . \urlencode($accessToken), - 'Content-Type: application/json' - ] - ); - - $responseKey = json_decode($responseKey, true); - - return json_decode(base64_decode($responseKey['privateKeyData']), true); - } - - public function cleanupServiceAccounts(string $accessToken, string $projectId) - { - // List Service Accounts - $response = $this->request( - 'GET', - 'https://iam.googleapis.com/v1/projects/' . $projectId . '/serviceAccounts', - [ - 'Authorization: Bearer ' . \urlencode($accessToken), - 'Content-Type: application/json' - ] - ); - - $response = json_decode($response, true); - - if (empty($response['accounts'])) { - return false; - } - - foreach ($response['accounts'] as $account) { - if (strpos($account['email'], 'appwrite-') !== false) { - $this->request( - 'DELETE', - 'https://iam.googleapis.com/v1/projects/' . $projectId . '/serviceAccounts/' . $account['email'], - [ - 'Authorization: Bearer ' . \urlencode($accessToken), - 'Content-Type: application/json' - ] - ); - } - } - - return true; - } -} diff --git a/src/Appwrite/Platform/Tasks/Maintenance.php b/src/Appwrite/Platform/Tasks/Maintenance.php index 55f4f25843..c789cbdaac 100644 --- a/src/Appwrite/Platform/Tasks/Maintenance.php +++ b/src/Appwrite/Platform/Tasks/Maintenance.php @@ -66,7 +66,6 @@ class Maintenance extends Action { $this->notifyDeleteTargets($queueForDeletes); $this->notifyDeleteExecutionLogs($queueForDeletes); - $this->notifyDeleteAbuseLogs($queueForDeletes); $this->notifyDeleteAuditLogs($queueForDeletes); $this->notifyDeleteUsageStats($usageStatsRetentionHourly, $queueForDeletes); $this->notifyDeleteExpiredSessions($queueForDeletes); @@ -106,13 +105,6 @@ class Maintenance extends Action ->trigger(); } - private function notifyDeleteAbuseLogs(Delete $queueForDeletes): void - { - $queueForDeletes - ->setType(DELETE_TYPE_ABUSE) - ->trigger(); - } - private function notifyDeleteAuditLogs(Delete $queueForDeletes): void { $queueForDeletes diff --git a/src/Appwrite/Platform/Workers/Databases.php b/src/Appwrite/Platform/Workers/Databases.php index 9c11dd4090..c16fba36c1 100644 --- a/src/Appwrite/Platform/Workers/Databases.php +++ b/src/Appwrite/Platform/Workers/Databases.php @@ -284,8 +284,6 @@ class Databases extends Action $dbForProject->deleteDocument('attributes', $relatedAttribute->getId()); } - throw $e; - } catch (\Throwable $e) { Console::error($e->getMessage());