mirror of
https://github.com/appwrite/appwrite
synced 2026-05-22 16:38:32 +00:00
commit
0e6d3279d7
22 changed files with 3450 additions and 2266 deletions
|
|
@ -57,6 +57,21 @@
|
|||
"emails.recovery.thanks": "Thanks,",
|
||||
"emails.recovery.buttonText": "Reset password",
|
||||
"emails.recovery.signature": "{{project}} team",
|
||||
"emails.csvExport.success.subject": "Your CSV export is ready",
|
||||
"emails.csvExport.success.preview": "Your data export has been completed successfully.",
|
||||
"emails.csvExport.success.hello": "Hello {{user}},",
|
||||
"emails.csvExport.success.body": "Your CSV export is ready for download. Click the link below to download your data export.",
|
||||
"emails.csvExport.success.footer": "This download link will expire in 1 hour.",
|
||||
"emails.csvExport.success.thanks": "Thanks,",
|
||||
"emails.csvExport.success.buttonText": "Download CSV",
|
||||
"emails.csvExport.success.signature": "{{project}} team",
|
||||
"emails.csvExport.failure.subject": "Your CSV export failed - file too large",
|
||||
"emails.csvExport.failure.preview": "Your data export failed because the file size exceeds your plan limit.",
|
||||
"emails.csvExport.failure.hello": "Hello {{user}},",
|
||||
"emails.csvExport.failure.body": "Your CSV export could not be completed because the export file size ({{size}}MB) exceeds your plan limit. Please consider upgrading your plan or exporting a smaller dataset.",
|
||||
"emails.csvExport.failure.footer": "If you have any questions, please contact our support team.",
|
||||
"emails.csvExport.failure.thanks": "Thanks,",
|
||||
"emails.csvExport.failure.signature": "{{project}} team",
|
||||
"emails.invitation.subject": "Invitation to {{team}} Team at {{project}}",
|
||||
"emails.invitation.preview": "{{owner}} invited you to join {{team}} at {{project}}",
|
||||
"emails.invitation.hello": "Hello {{user}},",
|
||||
|
|
|
|||
|
|
@ -4934,7 +4934,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 378,
|
||||
"weight": 379,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-transactions.md",
|
||||
|
|
@ -4999,7 +4999,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 374,
|
||||
"weight": 375,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-transaction.md",
|
||||
|
|
@ -5067,7 +5067,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 375,
|
||||
"weight": 376,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-transaction.md",
|
||||
|
|
@ -5129,7 +5129,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 376,
|
||||
"weight": 377,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-transaction.md",
|
||||
|
|
@ -5205,7 +5205,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 377,
|
||||
"weight": 378,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-transaction.md",
|
||||
|
|
@ -5269,7 +5269,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 379,
|
||||
"weight": 380,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-operations.md",
|
||||
|
|
@ -5352,7 +5352,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listDocuments",
|
||||
"group": "documents",
|
||||
"weight": 337,
|
||||
"weight": 338,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-documents.md",
|
||||
|
|
@ -5451,7 +5451,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createDocument",
|
||||
"group": "documents",
|
||||
"weight": 329,
|
||||
"weight": 330,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-document.md",
|
||||
|
|
@ -5607,7 +5607,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getDocument",
|
||||
"group": "documents",
|
||||
"weight": 330,
|
||||
"weight": 331,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-document.md",
|
||||
|
|
@ -5716,7 +5716,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "upsertDocument",
|
||||
"group": "documents",
|
||||
"weight": 333,
|
||||
"weight": 334,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/upsert-document.md",
|
||||
|
|
@ -5870,7 +5870,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "updateDocument",
|
||||
"group": "documents",
|
||||
"weight": 331,
|
||||
"weight": 332,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-document.md",
|
||||
|
|
@ -5978,7 +5978,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteDocument",
|
||||
"group": "documents",
|
||||
"weight": 335,
|
||||
"weight": 336,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-document.md",
|
||||
|
|
@ -6082,7 +6082,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "decrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 340,
|
||||
"weight": 341,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/decrement-document-attribute.md",
|
||||
|
|
@ -6206,7 +6206,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "incrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 339,
|
||||
"weight": 340,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/increment-document-attribute.md",
|
||||
|
|
@ -6330,7 +6330,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listExecutions",
|
||||
"group": "executions",
|
||||
"weight": 470,
|
||||
"weight": 471,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/list-executions.md",
|
||||
|
|
@ -6405,7 +6405,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createExecution",
|
||||
"group": "executions",
|
||||
"weight": 468,
|
||||
"weight": 469,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/create-execution.md",
|
||||
|
|
@ -6521,7 +6521,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getExecution",
|
||||
"group": "executions",
|
||||
"weight": 469,
|
||||
"weight": 470,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/get-execution.md",
|
||||
|
|
@ -7115,7 +7115,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 298,
|
||||
"weight": 299,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/create-subscriber.md",
|
||||
|
|
@ -7198,7 +7198,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 302,
|
||||
"weight": 303,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/delete-subscriber.md",
|
||||
|
|
@ -8076,7 +8076,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 443,
|
||||
"weight": 444,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-transactions.md",
|
||||
|
|
@ -8144,7 +8144,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 439,
|
||||
"weight": 440,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-transaction.md",
|
||||
|
|
@ -8215,7 +8215,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 440,
|
||||
"weight": 441,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-transaction.md",
|
||||
|
|
@ -8280,7 +8280,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 441,
|
||||
"weight": 442,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-transaction.md",
|
||||
|
|
@ -8359,7 +8359,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 442,
|
||||
"weight": 443,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-transaction.md",
|
||||
|
|
@ -8426,7 +8426,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 444,
|
||||
"weight": 445,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-operations.md",
|
||||
|
|
@ -8512,7 +8512,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listRows",
|
||||
"group": "rows",
|
||||
"weight": 435,
|
||||
"weight": 436,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-rows.md",
|
||||
|
|
@ -8610,7 +8610,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createRow",
|
||||
"group": "rows",
|
||||
"weight": 427,
|
||||
"weight": 428,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-row.md",
|
||||
|
|
@ -8761,7 +8761,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getRow",
|
||||
"group": "rows",
|
||||
"weight": 428,
|
||||
"weight": 429,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-row.md",
|
||||
|
|
@ -8869,7 +8869,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "upsertRow",
|
||||
"group": "rows",
|
||||
"weight": 431,
|
||||
"weight": 432,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/upsert-row.md",
|
||||
|
|
@ -9014,7 +9014,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "updateRow",
|
||||
"group": "rows",
|
||||
"weight": 429,
|
||||
"weight": 430,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-row.md",
|
||||
|
|
@ -9121,7 +9121,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteRow",
|
||||
"group": "rows",
|
||||
"weight": 433,
|
||||
"weight": 434,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-row.md",
|
||||
|
|
@ -9224,7 +9224,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "decrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 438,
|
||||
"weight": 439,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/decrement-row-column.md",
|
||||
|
|
@ -9347,7 +9347,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "incrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 437,
|
||||
"weight": 438,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/increment-row-column.md",
|
||||
|
|
@ -12514,13 +12514,14 @@
|
|||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "The status of the function execution. Possible values can be: `waiting`, `processing`, `completed`, or `failed`.",
|
||||
"description": "The status of the function execution. Possible values can be: `waiting`, `processing`, `completed`, `failed`, or `scheduled`.",
|
||||
"x-example": "processing",
|
||||
"enum": [
|
||||
"waiting",
|
||||
"processing",
|
||||
"completed",
|
||||
"failed"
|
||||
"failed",
|
||||
"scheduled"
|
||||
]
|
||||
},
|
||||
"requestMethod": {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -4934,7 +4934,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 378,
|
||||
"weight": 379,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-transactions.md",
|
||||
|
|
@ -4999,7 +4999,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 374,
|
||||
"weight": 375,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-transaction.md",
|
||||
|
|
@ -5067,7 +5067,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 375,
|
||||
"weight": 376,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-transaction.md",
|
||||
|
|
@ -5129,7 +5129,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 376,
|
||||
"weight": 377,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-transaction.md",
|
||||
|
|
@ -5205,7 +5205,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 377,
|
||||
"weight": 378,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-transaction.md",
|
||||
|
|
@ -5269,7 +5269,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 379,
|
||||
"weight": 380,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-operations.md",
|
||||
|
|
@ -5352,7 +5352,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listDocuments",
|
||||
"group": "documents",
|
||||
"weight": 337,
|
||||
"weight": 338,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-documents.md",
|
||||
|
|
@ -5451,7 +5451,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createDocument",
|
||||
"group": "documents",
|
||||
"weight": 329,
|
||||
"weight": 330,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-document.md",
|
||||
|
|
@ -5607,7 +5607,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getDocument",
|
||||
"group": "documents",
|
||||
"weight": 330,
|
||||
"weight": 331,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-document.md",
|
||||
|
|
@ -5716,7 +5716,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "upsertDocument",
|
||||
"group": "documents",
|
||||
"weight": 333,
|
||||
"weight": 334,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/upsert-document.md",
|
||||
|
|
@ -5870,7 +5870,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "updateDocument",
|
||||
"group": "documents",
|
||||
"weight": 331,
|
||||
"weight": 332,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-document.md",
|
||||
|
|
@ -5978,7 +5978,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteDocument",
|
||||
"group": "documents",
|
||||
"weight": 335,
|
||||
"weight": 336,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-document.md",
|
||||
|
|
@ -6082,7 +6082,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "decrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 340,
|
||||
"weight": 341,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/decrement-document-attribute.md",
|
||||
|
|
@ -6206,7 +6206,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "incrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 339,
|
||||
"weight": 340,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/increment-document-attribute.md",
|
||||
|
|
@ -6330,7 +6330,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listExecutions",
|
||||
"group": "executions",
|
||||
"weight": 470,
|
||||
"weight": 471,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/list-executions.md",
|
||||
|
|
@ -6405,7 +6405,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createExecution",
|
||||
"group": "executions",
|
||||
"weight": 468,
|
||||
"weight": 469,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/create-execution.md",
|
||||
|
|
@ -6521,7 +6521,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getExecution",
|
||||
"group": "executions",
|
||||
"weight": 469,
|
||||
"weight": 470,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/get-execution.md",
|
||||
|
|
@ -7115,7 +7115,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 298,
|
||||
"weight": 299,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/create-subscriber.md",
|
||||
|
|
@ -7198,7 +7198,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 302,
|
||||
"weight": 303,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/delete-subscriber.md",
|
||||
|
|
@ -8076,7 +8076,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 443,
|
||||
"weight": 444,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-transactions.md",
|
||||
|
|
@ -8144,7 +8144,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 439,
|
||||
"weight": 440,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-transaction.md",
|
||||
|
|
@ -8215,7 +8215,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 440,
|
||||
"weight": 441,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-transaction.md",
|
||||
|
|
@ -8280,7 +8280,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 441,
|
||||
"weight": 442,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-transaction.md",
|
||||
|
|
@ -8359,7 +8359,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 442,
|
||||
"weight": 443,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-transaction.md",
|
||||
|
|
@ -8426,7 +8426,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 444,
|
||||
"weight": 445,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-operations.md",
|
||||
|
|
@ -8512,7 +8512,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listRows",
|
||||
"group": "rows",
|
||||
"weight": 435,
|
||||
"weight": 436,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-rows.md",
|
||||
|
|
@ -8610,7 +8610,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createRow",
|
||||
"group": "rows",
|
||||
"weight": 427,
|
||||
"weight": 428,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-row.md",
|
||||
|
|
@ -8761,7 +8761,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getRow",
|
||||
"group": "rows",
|
||||
"weight": 428,
|
||||
"weight": 429,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-row.md",
|
||||
|
|
@ -8869,7 +8869,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "upsertRow",
|
||||
"group": "rows",
|
||||
"weight": 431,
|
||||
"weight": 432,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/upsert-row.md",
|
||||
|
|
@ -9014,7 +9014,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "updateRow",
|
||||
"group": "rows",
|
||||
"weight": 429,
|
||||
"weight": 430,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-row.md",
|
||||
|
|
@ -9121,7 +9121,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteRow",
|
||||
"group": "rows",
|
||||
"weight": 433,
|
||||
"weight": 434,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-row.md",
|
||||
|
|
@ -9224,7 +9224,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "decrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 438,
|
||||
"weight": 439,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/decrement-row-column.md",
|
||||
|
|
@ -9347,7 +9347,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "incrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 437,
|
||||
"weight": 438,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/increment-row-column.md",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -5076,7 +5076,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 378,
|
||||
"weight": 379,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-transactions.md",
|
||||
|
|
@ -5141,7 +5141,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 374,
|
||||
"weight": 375,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-transaction.md",
|
||||
|
|
@ -5209,7 +5209,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 375,
|
||||
"weight": 376,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-transaction.md",
|
||||
|
|
@ -5270,7 +5270,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 376,
|
||||
"weight": 377,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-transaction.md",
|
||||
|
|
@ -5347,7 +5347,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 377,
|
||||
"weight": 378,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-transaction.md",
|
||||
|
|
@ -5410,7 +5410,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 379,
|
||||
"weight": 380,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-operations.md",
|
||||
|
|
@ -5489,7 +5489,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listDocuments",
|
||||
"group": "documents",
|
||||
"weight": 337,
|
||||
"weight": 338,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-documents.md",
|
||||
|
|
@ -5582,7 +5582,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createDocument",
|
||||
"group": "documents",
|
||||
"weight": 329,
|
||||
"weight": 330,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-document.md",
|
||||
|
|
@ -5736,7 +5736,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getDocument",
|
||||
"group": "documents",
|
||||
"weight": 330,
|
||||
"weight": 331,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-document.md",
|
||||
|
|
@ -5837,7 +5837,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "upsertDocument",
|
||||
"group": "documents",
|
||||
"weight": 333,
|
||||
"weight": 334,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/upsert-document.md",
|
||||
|
|
@ -5987,7 +5987,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "updateDocument",
|
||||
"group": "documents",
|
||||
"weight": 331,
|
||||
"weight": 332,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-document.md",
|
||||
|
|
@ -6093,7 +6093,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteDocument",
|
||||
"group": "documents",
|
||||
"weight": 335,
|
||||
"weight": 336,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-document.md",
|
||||
|
|
@ -6191,7 +6191,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "decrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 340,
|
||||
"weight": 341,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/decrement-document-attribute.md",
|
||||
|
|
@ -6309,7 +6309,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "incrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 339,
|
||||
"weight": 340,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/increment-document-attribute.md",
|
||||
|
|
@ -6425,7 +6425,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listExecutions",
|
||||
"group": "executions",
|
||||
"weight": 470,
|
||||
"weight": 471,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/list-executions.md",
|
||||
|
|
@ -6498,7 +6498,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createExecution",
|
||||
"group": "executions",
|
||||
"weight": 468,
|
||||
"weight": 469,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/create-execution.md",
|
||||
|
|
@ -6615,7 +6615,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getExecution",
|
||||
"group": "executions",
|
||||
"weight": 469,
|
||||
"weight": 470,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/get-execution.md",
|
||||
|
|
@ -7240,7 +7240,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 298,
|
||||
"weight": 299,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/create-subscriber.md",
|
||||
|
|
@ -7324,7 +7324,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 302,
|
||||
"weight": 303,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/delete-subscriber.md",
|
||||
|
|
@ -8153,7 +8153,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 443,
|
||||
"weight": 444,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-transactions.md",
|
||||
|
|
@ -8221,7 +8221,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 439,
|
||||
"weight": 440,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-transaction.md",
|
||||
|
|
@ -8292,7 +8292,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 440,
|
||||
"weight": 441,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-transaction.md",
|
||||
|
|
@ -8356,7 +8356,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 441,
|
||||
"weight": 442,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-transaction.md",
|
||||
|
|
@ -8436,7 +8436,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 442,
|
||||
"weight": 443,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-transaction.md",
|
||||
|
|
@ -8502,7 +8502,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 444,
|
||||
"weight": 445,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-operations.md",
|
||||
|
|
@ -8584,7 +8584,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listRows",
|
||||
"group": "rows",
|
||||
"weight": 435,
|
||||
"weight": 436,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-rows.md",
|
||||
|
|
@ -8676,7 +8676,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createRow",
|
||||
"group": "rows",
|
||||
"weight": 427,
|
||||
"weight": 428,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-row.md",
|
||||
|
|
@ -8825,7 +8825,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getRow",
|
||||
"group": "rows",
|
||||
"weight": 428,
|
||||
"weight": 429,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-row.md",
|
||||
|
|
@ -8925,7 +8925,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "upsertRow",
|
||||
"group": "rows",
|
||||
"weight": 431,
|
||||
"weight": 432,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/upsert-row.md",
|
||||
|
|
@ -9066,7 +9066,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "updateRow",
|
||||
"group": "rows",
|
||||
"weight": 429,
|
||||
"weight": 430,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-row.md",
|
||||
|
|
@ -9171,7 +9171,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteRow",
|
||||
"group": "rows",
|
||||
"weight": 433,
|
||||
"weight": 434,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-row.md",
|
||||
|
|
@ -9268,7 +9268,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "decrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 438,
|
||||
"weight": 439,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/decrement-row-column.md",
|
||||
|
|
@ -9385,7 +9385,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "incrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 437,
|
||||
"weight": 438,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/increment-row-column.md",
|
||||
|
|
@ -12504,13 +12504,14 @@
|
|||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"description": "The status of the function execution. Possible values can be: `waiting`, `processing`, `completed`, or `failed`.",
|
||||
"description": "The status of the function execution. Possible values can be: `waiting`, `processing`, `completed`, `failed`, or `scheduled`.",
|
||||
"x-example": "processing",
|
||||
"enum": [
|
||||
"waiting",
|
||||
"processing",
|
||||
"completed",
|
||||
"failed"
|
||||
"failed",
|
||||
"scheduled"
|
||||
]
|
||||
},
|
||||
"requestMethod": {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -5076,7 +5076,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 378,
|
||||
"weight": 379,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-transactions.md",
|
||||
|
|
@ -5141,7 +5141,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 374,
|
||||
"weight": 375,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-transaction.md",
|
||||
|
|
@ -5209,7 +5209,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 375,
|
||||
"weight": 376,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-transaction.md",
|
||||
|
|
@ -5270,7 +5270,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 376,
|
||||
"weight": 377,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-transaction.md",
|
||||
|
|
@ -5347,7 +5347,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 377,
|
||||
"weight": 378,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-transaction.md",
|
||||
|
|
@ -5410,7 +5410,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 379,
|
||||
"weight": 380,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-operations.md",
|
||||
|
|
@ -5489,7 +5489,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listDocuments",
|
||||
"group": "documents",
|
||||
"weight": 337,
|
||||
"weight": 338,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-documents.md",
|
||||
|
|
@ -5582,7 +5582,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createDocument",
|
||||
"group": "documents",
|
||||
"weight": 329,
|
||||
"weight": 330,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-document.md",
|
||||
|
|
@ -5736,7 +5736,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getDocument",
|
||||
"group": "documents",
|
||||
"weight": 330,
|
||||
"weight": 331,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-document.md",
|
||||
|
|
@ -5837,7 +5837,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "upsertDocument",
|
||||
"group": "documents",
|
||||
"weight": 333,
|
||||
"weight": 334,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/upsert-document.md",
|
||||
|
|
@ -5987,7 +5987,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "updateDocument",
|
||||
"group": "documents",
|
||||
"weight": 331,
|
||||
"weight": 332,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-document.md",
|
||||
|
|
@ -6093,7 +6093,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteDocument",
|
||||
"group": "documents",
|
||||
"weight": 335,
|
||||
"weight": 336,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-document.md",
|
||||
|
|
@ -6191,7 +6191,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "decrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 340,
|
||||
"weight": 341,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/decrement-document-attribute.md",
|
||||
|
|
@ -6309,7 +6309,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "incrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 339,
|
||||
"weight": 340,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/increment-document-attribute.md",
|
||||
|
|
@ -6425,7 +6425,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listExecutions",
|
||||
"group": "executions",
|
||||
"weight": 470,
|
||||
"weight": 471,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/list-executions.md",
|
||||
|
|
@ -6498,7 +6498,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createExecution",
|
||||
"group": "executions",
|
||||
"weight": 468,
|
||||
"weight": 469,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/create-execution.md",
|
||||
|
|
@ -6615,7 +6615,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getExecution",
|
||||
"group": "executions",
|
||||
"weight": 469,
|
||||
"weight": 470,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/get-execution.md",
|
||||
|
|
@ -7240,7 +7240,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 298,
|
||||
"weight": 299,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/create-subscriber.md",
|
||||
|
|
@ -7324,7 +7324,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 302,
|
||||
"weight": 303,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/delete-subscriber.md",
|
||||
|
|
@ -8153,7 +8153,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 443,
|
||||
"weight": 444,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-transactions.md",
|
||||
|
|
@ -8221,7 +8221,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 439,
|
||||
"weight": 440,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-transaction.md",
|
||||
|
|
@ -8292,7 +8292,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 440,
|
||||
"weight": 441,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-transaction.md",
|
||||
|
|
@ -8356,7 +8356,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 441,
|
||||
"weight": 442,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-transaction.md",
|
||||
|
|
@ -8436,7 +8436,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 442,
|
||||
"weight": 443,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-transaction.md",
|
||||
|
|
@ -8502,7 +8502,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 444,
|
||||
"weight": 445,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-operations.md",
|
||||
|
|
@ -8584,7 +8584,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "listRows",
|
||||
"group": "rows",
|
||||
"weight": 435,
|
||||
"weight": 436,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-rows.md",
|
||||
|
|
@ -8676,7 +8676,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "createRow",
|
||||
"group": "rows",
|
||||
"weight": 427,
|
||||
"weight": 428,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-row.md",
|
||||
|
|
@ -8825,7 +8825,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "getRow",
|
||||
"group": "rows",
|
||||
"weight": 428,
|
||||
"weight": 429,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-row.md",
|
||||
|
|
@ -8925,7 +8925,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "upsertRow",
|
||||
"group": "rows",
|
||||
"weight": 431,
|
||||
"weight": 432,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/upsert-row.md",
|
||||
|
|
@ -9066,7 +9066,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "updateRow",
|
||||
"group": "rows",
|
||||
"weight": 429,
|
||||
"weight": 430,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-row.md",
|
||||
|
|
@ -9171,7 +9171,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "deleteRow",
|
||||
"group": "rows",
|
||||
"weight": 433,
|
||||
"weight": 434,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-row.md",
|
||||
|
|
@ -9268,7 +9268,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "decrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 438,
|
||||
"weight": 439,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/decrement-row-column.md",
|
||||
|
|
@ -9385,7 +9385,7 @@
|
|||
"x-appwrite": {
|
||||
"method": "incrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 437,
|
||||
"weight": 438,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/increment-row-column.md",
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,5 @@
|
|||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Migration;
|
||||
use Appwrite\Extend\Exception;
|
||||
|
|
@ -20,6 +19,7 @@ use Utopia\Database\Exception\Query as QueryException;
|
|||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Queries\Documents;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Migration\Resource;
|
||||
|
|
@ -307,7 +307,8 @@ App::post('/v1/migrations/nhost')
|
|||
->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
App::post('/v1/migrations/csv')
|
||||
App::post('/v1/migrations/csv/imports')
|
||||
->alias('/v1/migrations/csv')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Import documents from a CSV')
|
||||
->label('scope', 'migrations.write')
|
||||
|
|
@ -316,8 +317,8 @@ App::post('/v1/migrations/csv')
|
|||
->label('sdk', new Method(
|
||||
namespace: 'migrations',
|
||||
group: null,
|
||||
name: 'createCsvMigration',
|
||||
description: '/docs/references/migrations/migration-csv.md',
|
||||
name: 'createCSVImport',
|
||||
description: '/docs/references/migrations/migration-csv-import.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
|
|
@ -335,15 +336,23 @@ App::post('/v1/migrations/csv')
|
|||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('deviceForFiles')
|
||||
->inject('deviceForImports')
|
||||
->inject('deviceForMigrations')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (string $bucketId, string $fileId, string $resourceId, bool $internalFile, Response $response, Database $dbForProject, Database $dbForPlatform, Document $project, Device $deviceForFiles, Device $deviceForImports, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
if ($internalFile && !$isPrivilegedUser) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
->action(function (
|
||||
string $bucketId,
|
||||
string $fileId,
|
||||
string $resourceId,
|
||||
bool $internalFile,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
Database $dbForPlatform,
|
||||
Document $project,
|
||||
Device $deviceForFiles,
|
||||
Device $deviceForMigrations,
|
||||
Event $queueForEvents,
|
||||
Migration $queueForMigrations
|
||||
) {
|
||||
$bucket = Authorization::skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) {
|
||||
if ($internalFile) {
|
||||
return $dbForPlatform->getDocument('buckets', 'default');
|
||||
|
|
@ -351,7 +360,7 @@ App::post('/v1/migrations/csv')
|
|||
return $dbForProject->getDocument('buckets', $bucketId);
|
||||
});
|
||||
|
||||
if ($bucket->isEmpty() || (!$isAPIKey && !$isPrivilegedUser)) {
|
||||
if ($bucket->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
|
|
@ -365,18 +374,17 @@ App::post('/v1/migrations/csv')
|
|||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
|
||||
}
|
||||
|
||||
// no encryption, compression on files above 20MB.
|
||||
// No encryption or compression on files above 20MB.
|
||||
$hasEncryption = !empty($file->getAttribute('openSSLCipher'));
|
||||
$compression = $file->getAttribute('algorithm', Compression::NONE);
|
||||
$hasCompression = $compression !== Compression::NONE;
|
||||
|
||||
$migrationId = ID::unique();
|
||||
$newPath = $deviceForImports->getPath($migrationId . '_' . $fileId . '.csv');
|
||||
$newPath = $deviceForMigrations->getPath($migrationId . '_' . $fileId . '.csv');
|
||||
|
||||
if ($hasEncryption || $hasCompression) {
|
||||
$source = $deviceForFiles->read($path);
|
||||
|
||||
// 1. decrypt
|
||||
if ($hasEncryption) {
|
||||
$source = OpenSSL::decrypt(
|
||||
$source,
|
||||
|
|
@ -388,7 +396,6 @@ App::post('/v1/migrations/csv')
|
|||
);
|
||||
}
|
||||
|
||||
// 2. decompress
|
||||
if ($hasCompression) {
|
||||
switch ($compression) {
|
||||
case Compression::ZSTD:
|
||||
|
|
@ -400,15 +407,15 @@ App::post('/v1/migrations/csv')
|
|||
}
|
||||
}
|
||||
|
||||
// manual write after decryption and/or decompression
|
||||
if (! $deviceForImports->write($newPath, $source, 'text/csv')) {
|
||||
throw new \Exception("Unable to copy file");
|
||||
// Manual write after decryption and/or decompression
|
||||
if (!$deviceForMigrations->write($newPath, $source, 'text/csv')) {
|
||||
throw new \Exception('Unable to copy file');
|
||||
}
|
||||
} elseif (! $deviceForFiles->transfer($path, $newPath, $deviceForImports)) {
|
||||
throw new \Exception("Unable to copy file");
|
||||
} elseif (!$deviceForFiles->transfer($path, $newPath, $deviceForMigrations)) {
|
||||
throw new \Exception('Unable to copy file');
|
||||
}
|
||||
|
||||
$fileSize = $deviceForImports->getFileSize($newPath);
|
||||
$fileSize = $deviceForMigrations->getFileSize($newPath);
|
||||
$resources = Transfer::extractServices([Transfer::GROUP_DATABASES]);
|
||||
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
|
|
@ -441,6 +448,136 @@ App::post('/v1/migrations/csv')
|
|||
->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
App::post('/v1/migrations/csv/exports')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('Export documents to CSV')
|
||||
->label('scope', 'migrations.write')
|
||||
->label('event', 'migrations.[migrationId].create')
|
||||
->label('audits.event', 'migration.create')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'migrations',
|
||||
group: null,
|
||||
name: 'createCSVExport',
|
||||
description: '/docs/references/migrations/migration-csv-export.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_ACCEPTED,
|
||||
model: Response::MODEL_MIGRATION,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database to export.')
|
||||
->param('bucketId', '', new UID(), 'Storage bucket unique ID where the exported CSV will be stored.')
|
||||
->param('filename', '', new Text(255), 'The name of the file to be created for the export, excluding the .csv extension.')
|
||||
->param('columns', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of attributes to export. If empty, all attributes will be exported. You can use the `*` wildcard to export all attributes from the collection.', true)
|
||||
->param('queries', [], new ArrayList(new Text(0)), 'Array of query strings generated using the Query class provided by the SDK to filter documents to export. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
|
||||
->param('delimiter', ',', new Text(1), 'The character that separates each column value. Default is comma.', true)
|
||||
->param('enclosure', '"', new Text(1), 'The character that encloses each column value. Default is double quotes.', true)
|
||||
->param('escape', '"', new Text(1), 'The escape character for the enclosure character. Default is double quotes.', true)
|
||||
->param('header', true, new Boolean(), 'Whether to include the header row with column names. Default is true.', true)
|
||||
->param('notify', true, new Boolean(), 'Set to true to receive an email when the export is complete. Default is true.', true)
|
||||
->inject('user')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('project')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (
|
||||
string $resourceId,
|
||||
string $bucketId,
|
||||
string $filename,
|
||||
array $columns,
|
||||
array $queries,
|
||||
string $delimiter,
|
||||
string $enclosure,
|
||||
string $escape,
|
||||
bool $header,
|
||||
bool $notify,
|
||||
Document $user,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
Document $project,
|
||||
Event $queueForEvents,
|
||||
Migration $queueForMigrations
|
||||
) {
|
||||
try {
|
||||
$parsedQueries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
if ($bucket->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
[$databaseId, $collectionId] = \explode(':', $resourceId, 2);
|
||||
if (empty($databaseId)) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
if (empty($collectionId)) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$validator = new Documents(
|
||||
attributes: $collection->getAttribute('attributes', []),
|
||||
indexes: $collection->getAttribute('indexes', []),
|
||||
idAttributeType: $dbForProject->getAdapter()->getIdAttributeType(),
|
||||
);
|
||||
|
||||
if (!$validator->isValid($parsedQueries)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$migration = $dbForProject->createDocument('migrations', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'status' => 'pending',
|
||||
'stage' => 'init',
|
||||
'source' => Appwrite::getName(),
|
||||
'destination' => CSV::getName(),
|
||||
'resources' => Transfer::extractServices([Transfer::GROUP_DATABASES]),
|
||||
'resourceId' => $resourceId,
|
||||
'resourceType' => Resource::TYPE_DATABASE,
|
||||
'statusCounters' => '{}',
|
||||
'resourceData' => '{}',
|
||||
'errors' => [],
|
||||
'options' => [
|
||||
'bucketId' => $bucketId,
|
||||
'filename' => $filename,
|
||||
'columns' => $columns,
|
||||
'queries' => $queries,
|
||||
'delimiter' => $delimiter,
|
||||
'enclosure' => $enclosure,
|
||||
'escape' => $escape,
|
||||
'header' => $header,
|
||||
'notify' => $notify,
|
||||
'userInternalId' => $user->getSequence(),
|
||||
],
|
||||
]));
|
||||
|
||||
$queueForEvents->setParam('migrationId', $migration->getId());
|
||||
|
||||
$queueForMigrations
|
||||
->setMigration($migration)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
|
||||
->dynamic($migration, Response::MODEL_MIGRATION);
|
||||
});
|
||||
|
||||
App::get('/v1/migrations')
|
||||
->groups(['api', 'migrations'])
|
||||
->desc('List migrations')
|
||||
|
|
|
|||
|
|
@ -555,7 +555,7 @@ App::setResource('deviceForFiles', function ($project, Telemetry $telemetry) {
|
|||
App::setResource('deviceForSites', function ($project, Telemetry $telemetry) {
|
||||
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_SITES . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
App::setResource('deviceForImports', function ($project, Telemetry $telemetry) {
|
||||
App::setResource('deviceForMigrations', function ($project, Telemetry $telemetry) {
|
||||
return new Device\Telemetry($telemetry, getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
App::setResource('deviceForFunctions', function ($project, Telemetry $telemetry) {
|
||||
|
|
|
|||
|
|
@ -349,7 +349,7 @@ Server::setResource('deviceForSites', function (Document $project, Telemetry $te
|
|||
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_SITES . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
|
||||
Server::setResource('deviceForImports', function (Document $project, Telemetry $telemetry) {
|
||||
Server::setResource('deviceForMigrations', function (Document $project, Telemetry $telemetry) {
|
||||
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
|
||||
|
|
|
|||
|
|
@ -698,6 +698,7 @@ services:
|
|||
- appwrite
|
||||
volumes:
|
||||
- appwrite-imports:/storage/imports:rw
|
||||
- appwrite-uploads:/storage/uploads:rw
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
- ./tests:/usr/src/code/tests
|
||||
|
|
|
|||
1
docs/references/migrations/migration-csv-export.md
Normal file
1
docs/references/migrations/migration-csv-export.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
Export documents to a CSV file from your Appwrite database. This endpoint allows you to export documents to a CSV file stored in an Appwrite Storage bucket.
|
||||
|
|
@ -3,8 +3,10 @@
|
|||
namespace Appwrite\Platform\Workers;
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Exception;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Template\Template;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
|
|
@ -13,10 +15,15 @@ use Utopia\Database\Exception\Authorization;
|
|||
use Utopia\Database\Exception\Conflict;
|
||||
use Utopia\Database\Exception\Restricted;
|
||||
use Utopia\Database\Exception\Structure;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Migration\Destination;
|
||||
use Utopia\Migration\Destinations\Appwrite as DestinationAppwrite;
|
||||
use Utopia\Migration\Destinations\CSV as DestinationCSV;
|
||||
use Utopia\Migration\Exception as MigrationException;
|
||||
use Utopia\Migration\Source;
|
||||
use Utopia\Migration\Sources\Appwrite;
|
||||
use Utopia\Migration\Sources\Appwrite as SourceAppwrite;
|
||||
use Utopia\Migration\Sources\CSV;
|
||||
use Utopia\Migration\Sources\Firebase;
|
||||
|
|
@ -25,6 +32,7 @@ use Utopia\Migration\Sources\Supabase;
|
|||
use Utopia\Migration\Transfer;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Message;
|
||||
use Utopia\Storage\Compression\Compression;
|
||||
use Utopia\Storage\Device;
|
||||
use Utopia\System\System;
|
||||
|
||||
|
|
@ -34,13 +42,14 @@ class Migrations extends Action
|
|||
|
||||
protected Database $dbForPlatform;
|
||||
|
||||
protected Device $deviceForImports;
|
||||
protected Device $deviceForMigrations;
|
||||
protected Device $deviceForFiles;
|
||||
|
||||
protected Document $project;
|
||||
|
||||
protected array $plan;
|
||||
|
||||
/**
|
||||
* Cached for performance.
|
||||
*
|
||||
* @var array<string, int>
|
||||
*/
|
||||
protected array $sourceReport = [];
|
||||
|
|
@ -68,23 +77,38 @@ class Migrations extends Action
|
|||
->inject('dbForPlatform')
|
||||
->inject('logError')
|
||||
->inject('queueForRealtime')
|
||||
->inject('deviceForImports')
|
||||
->inject('deviceForMigrations')
|
||||
->inject('deviceForFiles')
|
||||
->inject('queueForMails')
|
||||
->inject('plan')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function action(Message $message, Document $project, Database $dbForProject, Database $dbForPlatform, callable $logError, Realtime $queueForRealtime, Device $deviceForImports): void
|
||||
{
|
||||
public function action(
|
||||
Message $message,
|
||||
Document $project,
|
||||
Database $dbForProject,
|
||||
Database $dbForPlatform,
|
||||
callable $logError,
|
||||
Realtime $queueForRealtime,
|
||||
Device $deviceForMigrations,
|
||||
Device $deviceForFiles,
|
||||
Mail $queueForMails,
|
||||
array $plan,
|
||||
): void {
|
||||
$payload = $message->getPayload() ?? [];
|
||||
$this->deviceForImports = $deviceForImports;
|
||||
$this->deviceForMigrations = $deviceForMigrations;
|
||||
$this->deviceForFiles = $deviceForFiles;
|
||||
$this->plan = $plan;
|
||||
|
||||
if (empty($payload)) {
|
||||
throw new Exception('Missing payload');
|
||||
}
|
||||
|
||||
$events = $payload['events'] ?? [];
|
||||
$events = $payload['events'] ?? [];
|
||||
$migration = new Document($payload['migration'] ?? []);
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
|
|
@ -96,14 +120,11 @@ class Migrations extends Action
|
|||
$this->project = $project;
|
||||
$this->logError = $logError;
|
||||
|
||||
/**
|
||||
* Handle Event execution.
|
||||
*/
|
||||
if (! empty($events)) {
|
||||
if (!empty($events)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->processMigration($migration, $queueForRealtime);
|
||||
$this->processMigration($migration, $queueForRealtime, $queueForMails);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -112,9 +133,19 @@ class Migrations extends Action
|
|||
protected function processSource(Document $migration): Source
|
||||
{
|
||||
$source = $migration->getAttribute('source');
|
||||
$destination = $migration->getAttribute('destination');
|
||||
$resourceId = $migration->getAttribute('resourceId');
|
||||
$credentials = $migration->getAttribute('credentials');
|
||||
$migrationOptions = $migration->getAttribute('options');
|
||||
$dataSource = Appwrite::SOURCE_API;
|
||||
$database = null;
|
||||
$queries = [];
|
||||
|
||||
if ($source === Appwrite::getName() && $destination === DestinationCSV::getName()) {
|
||||
$dataSource = Appwrite::SOURCE_DATABASE;
|
||||
$database = $this->dbForProject;
|
||||
$queries = Query::parseQueries($migrationOptions['queries']);
|
||||
}
|
||||
|
||||
$migrationSource = match ($source) {
|
||||
Firebase::getName() => new Firebase(
|
||||
|
|
@ -142,11 +173,14 @@ class Migrations extends Action
|
|||
$credentials['projectId'],
|
||||
$credentials['endpoint'] === 'http://localhost/v1' ? 'http://appwrite/v1' : $credentials['endpoint'],
|
||||
$credentials['apiKey'],
|
||||
$dataSource,
|
||||
$database,
|
||||
$queries,
|
||||
),
|
||||
CSV::getName() => new CSV(
|
||||
$resourceId,
|
||||
$migrationOptions['path'],
|
||||
$this->deviceForImports,
|
||||
$this->deviceForMigrations,
|
||||
$this->dbForProject
|
||||
),
|
||||
default => throw new \Exception('Invalid source type'),
|
||||
|
|
@ -163,6 +197,7 @@ class Migrations extends Action
|
|||
protected function processDestination(Document $migration, string $apiKey): Destination
|
||||
{
|
||||
$destination = $migration->getAttribute('destination');
|
||||
$options = $migration->getAttribute('options', []);
|
||||
|
||||
return match ($destination) {
|
||||
DestinationAppwrite::getName() => new DestinationAppwrite(
|
||||
|
|
@ -172,6 +207,17 @@ class Migrations extends Action
|
|||
$this->dbForProject,
|
||||
Config::getParam('collections', [])['databases']['collections'],
|
||||
),
|
||||
DestinationCSV::getName() => new DestinationCSV(
|
||||
$this->deviceForFiles,
|
||||
$migration->getAttribute('resourceId'),
|
||||
$options['bucketId'],
|
||||
$options['filename'],
|
||||
$options['columns'],
|
||||
$options['delimiter'],
|
||||
$options['enclosure'],
|
||||
$options['escape'],
|
||||
$options['header'],
|
||||
),
|
||||
default => throw new \Exception('Invalid destination type'),
|
||||
};
|
||||
}
|
||||
|
|
@ -185,35 +231,19 @@ class Migrations extends Action
|
|||
*/
|
||||
protected function updateMigrationDocument(Document $migration, Document $project, Realtime $queueForRealtime): Document
|
||||
{
|
||||
$errorMessages = [];
|
||||
$clonedMigrationDocument = clone $migration;
|
||||
|
||||
// we cannot use #sensitive because
|
||||
// `errors` is nested which requires an override.
|
||||
$errors = $clonedMigrationDocument->getAttribute('errors', []);
|
||||
|
||||
foreach ($errors as $error) {
|
||||
$decoded = json_decode($error, true);
|
||||
|
||||
if (is_array($decoded) && isset($decoded['trace'])) {
|
||||
unset($decoded['trace']);
|
||||
$errorMessages[] = json_encode($decoded);
|
||||
}
|
||||
}
|
||||
|
||||
// set the errors back without trace
|
||||
$clonedMigrationDocument->setAttribute('errors', $errorMessages);
|
||||
|
||||
/** Trigger Realtime Events */
|
||||
$queueForRealtime
|
||||
->setProject($project)
|
||||
->setSubscribers(['console', $project->getId()])
|
||||
->setEvent('migrations.[migrationId].update')
|
||||
->setParam('migrationId', $migration->getId())
|
||||
->setPayload($clonedMigrationDocument->getArrayCopy(), ['options', 'credentials'])
|
||||
->setPayload($migration->getArrayCopy(), sensitive: ['credentials'])
|
||||
->trigger();
|
||||
|
||||
return $this->dbForProject->updateDocument('migrations', $migration->getId(), $migration);
|
||||
return $this->dbForProject->updateDocument(
|
||||
'migrations',
|
||||
$migration->getId(),
|
||||
$migration
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -243,13 +273,6 @@ class Migrations extends Action
|
|||
'files.write',
|
||||
'functions.read',
|
||||
'functions.write',
|
||||
'databases.read',
|
||||
'collections.read',
|
||||
'tables.read',
|
||||
'documents.read',
|
||||
'documents.write',
|
||||
'rows.read',
|
||||
'rows.write',
|
||||
'tokens.read',
|
||||
'tokens.write',
|
||||
]
|
||||
|
|
@ -266,11 +289,13 @@ class Migrations extends Action
|
|||
* @throws \Utopia\Database\Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function processMigration(Document $migration, Realtime $queueForRealtime): void
|
||||
{
|
||||
$project = $this->project;
|
||||
$projectDocument = $this->dbForPlatform->getDocument('projects', $project->getId());
|
||||
$tempAPIKey = $this->generateAPIKey($projectDocument);
|
||||
protected function processMigration(
|
||||
Document $migration,
|
||||
Realtime $queueForRealtime,
|
||||
Mail $queueForMails,
|
||||
): void {
|
||||
$project = $this->dbForPlatform->getDocument('projects', $this->project->getId());
|
||||
$tempAPIKey = $this->generateAPIKey($project);
|
||||
|
||||
$transfer = $source = $destination = null;
|
||||
|
||||
|
|
@ -280,17 +305,15 @@ class Migrations extends Action
|
|||
empty($migration->getAttribute('credentials', []))
|
||||
) {
|
||||
$credentials = $migration->getAttribute('credentials', []);
|
||||
|
||||
$credentials['projectId'] = $credentials['projectId'] ?? $projectDocument->getId();
|
||||
$credentials['projectId'] = $credentials['projectId'] ?? $project->getId();
|
||||
$credentials['endpoint'] = $credentials['endpoint'] ?? 'http://appwrite/v1';
|
||||
$credentials['apiKey'] = $credentials['apiKey'] ?? $tempAPIKey;
|
||||
|
||||
$migration->setAttribute('credentials', $credentials);
|
||||
}
|
||||
|
||||
$migration->setAttribute('stage', 'processing');
|
||||
$migration->setAttribute('status', 'processing');
|
||||
$this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
|
||||
$this->updateMigrationDocument($migration, $project, $queueForRealtime);
|
||||
|
||||
$source = $this->processSource($migration);
|
||||
$destination = $this->processDestination($migration, $tempAPIKey);
|
||||
|
|
@ -303,40 +326,30 @@ class Migrations extends Action
|
|||
/** Start Transfer */
|
||||
if (empty($source->getErrors())) {
|
||||
$migration->setAttribute('stage', 'migrating');
|
||||
$this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
|
||||
$this->updateMigrationDocument($migration, $project, $queueForRealtime);
|
||||
|
||||
$transfer->run(
|
||||
$migration->getAttribute('resources'),
|
||||
function () use ($migration, $transfer, $projectDocument, $queueForRealtime) {
|
||||
function () use ($migration, $transfer, $project, $queueForRealtime) {
|
||||
$migration->setAttribute('resourceData', json_encode($transfer->getCache()));
|
||||
$migration->setAttribute('statusCounters', json_encode($transfer->getStatusCounters()));
|
||||
$this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
|
||||
$this->updateMigrationDocument($migration, $project, $queueForRealtime);
|
||||
},
|
||||
$migration->getAttribute('resourceId'),
|
||||
$migration->getAttribute('resourceType')
|
||||
);
|
||||
}
|
||||
|
||||
$destination->shutDown();
|
||||
$source->shutDown();
|
||||
$destination->shutdown();
|
||||
$source->shutdown();
|
||||
|
||||
$sourceErrors = $source->getErrors();
|
||||
$destinationErrors = $destination->getErrors();
|
||||
|
||||
if (! empty($sourceErrors) || ! empty($destinationErrors)) {
|
||||
if (!empty($sourceErrors) || ! empty($destinationErrors)) {
|
||||
$migration->setAttribute('status', 'failed');
|
||||
$migration->setAttribute('stage', 'finished');
|
||||
|
||||
$errorMessages = [];
|
||||
foreach ($sourceErrors as $error) {
|
||||
$errorMessages[] = json_encode($error);
|
||||
}
|
||||
foreach ($destinationErrors as $error) {
|
||||
$errorMessages[] = json_encode($error);
|
||||
}
|
||||
|
||||
$migration->setAttribute('errors', $errorMessages);
|
||||
|
||||
$migration->setAttribute('errors', $this->sanitizeErrors($sourceErrors, $destinationErrors));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -362,58 +375,286 @@ class Migrations extends Action
|
|||
if ($transfer) {
|
||||
$sourceErrors = $source->getErrors();
|
||||
$destinationErrors = $destination->getErrors();
|
||||
|
||||
$errorMessages = [];
|
||||
foreach ($sourceErrors as $error) {
|
||||
$errorMessages[] = json_encode($error);
|
||||
}
|
||||
foreach ($destinationErrors as $error) {
|
||||
$errorMessages[] = json_encode($error);
|
||||
}
|
||||
|
||||
$migration->setAttribute('errors', $errorMessages);
|
||||
$migration->setAttribute('errors', $this->sanitizeErrors($sourceErrors, $destinationErrors));
|
||||
}
|
||||
} finally {
|
||||
$this->updateMigrationDocument($migration, $projectDocument, $queueForRealtime);
|
||||
$this->updateMigrationDocument($migration, $project, $queueForRealtime);
|
||||
|
||||
if ($migration->getAttribute('status', '') === 'failed') {
|
||||
Console::error('Migration('.$migration->getSequence().':'.$migration->getId().') failed, Project('.$this->project->getSequence().':'.$this->project->getId().')');
|
||||
|
||||
if ($destination) {
|
||||
$destination->error();
|
||||
$sourceErrors = $source?->getErrors() ?? [];
|
||||
$destinationErrors = $destination?->getErrors() ?? [];
|
||||
|
||||
foreach ($destination->getErrors() as $error) {
|
||||
/** @var MigrationException $error */
|
||||
call_user_func($this->logError, $error, 'appwrite-worker', 'appwrite-queue-' . self::getName(), [
|
||||
foreach ([...$sourceErrors, ...$destinationErrors] as $error) {
|
||||
/** @var MigrationException $error */
|
||||
if ($error->getCode() === 0 || $error->getCode() >= 500) {
|
||||
($this->logError)($error, 'appwrite-worker', 'appwrite-queue-' . self::getName(), [
|
||||
'migrationId' => $migration->getId(),
|
||||
'source' => $migration->getAttribute('source') ?? '',
|
||||
'destination' => $migration->getAttribute('destination') ?? '',
|
||||
'resourceName' => $error->getResourceName(),
|
||||
'resourceGroup' => $error->getResourceGroup()
|
||||
'resourceGroup' => $error->getResourceGroup(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($source) {
|
||||
$source->error();
|
||||
|
||||
foreach ($source->getErrors() as $error) {
|
||||
/** @var MigrationException $error */
|
||||
call_user_func($this->logError, $error, 'appwrite-worker', 'appwrite-queue-' . self::getName(), [
|
||||
'migrationId' => $migration->getId(),
|
||||
'source' => $migration->getAttribute('source') ?? '',
|
||||
'destination' => $migration->getAttribute('destination') ?? '',
|
||||
'resourceName' => $error->getResourceName(),
|
||||
'resourceGroup' => $error->getResourceGroup()
|
||||
]);
|
||||
}
|
||||
}
|
||||
$source?->error();
|
||||
$destination?->error();
|
||||
}
|
||||
|
||||
if ($migration->getAttribute('status', '') === 'completed') {
|
||||
$destination?->success();
|
||||
$source?->success();
|
||||
|
||||
if ($migration->getAttribute('destination') === DestinationCSV::getName()) {
|
||||
$this->handleCSVExportComplete($project, $migration, $queueForMails);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle actions to be performed when a CSV export migration is successfully completed
|
||||
*
|
||||
* @param Document $project
|
||||
* @param Document $migration
|
||||
* @param Mail $queueForMails
|
||||
* @return void
|
||||
* @throws Authorization
|
||||
* @throws Structure
|
||||
* @throws \Utopia\Database\Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function handleCSVExportComplete(
|
||||
Document $project,
|
||||
Document $migration,
|
||||
Mail $queueForMails
|
||||
): void {
|
||||
$options = $migration->getAttribute('options', []);
|
||||
$bucketId = $options['bucketId'] ?? null;
|
||||
$filename = $options['filename'] ?? 'export_' . \time();
|
||||
$userInternalId = $options['userInternalId'] ?? '';
|
||||
|
||||
$bucket = $this->dbForProject->getDocument('buckets', $bucketId);
|
||||
if ($bucket->isEmpty()) {
|
||||
throw new \Exception("Bucket not found: $bucketId");
|
||||
}
|
||||
|
||||
$path = $this->deviceForFiles->getPath($bucketId . '/' . $this->sanitizeFilename($filename) . '.csv');
|
||||
$size = $this->deviceForFiles->getFileSize($path);
|
||||
$mime = $this->deviceForFiles->getFileMimeType($path);
|
||||
$hash = $this->deviceForFiles->getFileHash($path);
|
||||
$algorithm = Compression::NONE;
|
||||
$fileId = ID::unique();
|
||||
|
||||
$sizeMB = \round($size / (1000 * 1000), 2);
|
||||
if ($sizeMB > $this->plan['fileSize'] ?? PHP_INT_MAX) {
|
||||
try {
|
||||
$this->deviceForFiles->delete($path);
|
||||
} finally {
|
||||
$message = "Export file size {$sizeMB}MB exceeds your plan limit.";
|
||||
|
||||
$this->dbForProject->updateDocument('migrations', $migration->getId(), $migration->setAttribute(
|
||||
'errors',
|
||||
json_encode(['code' => 0, 'message' => $message]),
|
||||
Document::SET_TYPE_APPEND,
|
||||
));
|
||||
|
||||
$this->sendCSVEmail(
|
||||
success: false,
|
||||
project: $project,
|
||||
userInternalId: $userInternalId,
|
||||
options: $options,
|
||||
queueForMails: $queueForMails,
|
||||
sizeMB: $sizeMB
|
||||
);
|
||||
|
||||
throw new \Exception($message);
|
||||
}
|
||||
}
|
||||
|
||||
$this->dbForProject->createDocument('bucket_' . $bucket->getSequence(), new Document([
|
||||
'$id' => $fileId,
|
||||
'$permissions' => [],
|
||||
'bucketId' => $bucket->getId(),
|
||||
'bucketInternalId' => $bucket->getSequence(),
|
||||
'name' => $filename,
|
||||
'path' => $path,
|
||||
'signature' => $hash,
|
||||
'mimeType' => $mime,
|
||||
'sizeOriginal' => $size,
|
||||
'sizeActual' => $size,
|
||||
'algorithm' => $algorithm,
|
||||
'comment' => '',
|
||||
'chunksTotal' => 1,
|
||||
'chunksUploaded' => 1,
|
||||
'openSSLVersion' => null,
|
||||
'openSSLCipher' => null,
|
||||
'openSSLTag' => null,
|
||||
'openSSLIV' => null,
|
||||
'search' => \implode(' ', [$fileId, $filename]),
|
||||
'metadata' => ['content_type' => $mime]
|
||||
]));
|
||||
|
||||
Console::info("Created file document in bucket: $fileId");
|
||||
|
||||
// Generate JWT valid for 1 hour
|
||||
$maxAge = 60 * 60;
|
||||
$encoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $maxAge, 0);
|
||||
$jwt = $encoder->encode([
|
||||
'bucketId' => $bucketId,
|
||||
'fileId' => $fileId,
|
||||
'projectId' => $project->getId(),
|
||||
]);
|
||||
|
||||
// Generate download URL with JWT
|
||||
$endpoint = System::getEnv('_APP_DOMAIN', '');
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled' ? 'https' : 'http';
|
||||
$downloadUrl = "{$protocol}://{$endpoint}/v1/storage/buckets/{$bucketId}/files/{$fileId}/push?project={$project->getId()}&jwt={$jwt}";
|
||||
|
||||
$this->sendCSVEmail(
|
||||
success: true,
|
||||
project: $project,
|
||||
userInternalId: $userInternalId,
|
||||
options: $options,
|
||||
queueForMails: $queueForMails,
|
||||
downloadUrl: $downloadUrl
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send CSV export notification email
|
||||
*
|
||||
* @param bool $success Whether the export was successful
|
||||
* @param Document $project
|
||||
* @param string $userInternalId Internal ID of the user
|
||||
* @param array $options Migration options
|
||||
* @param Mail $queueForMails
|
||||
* @param string $downloadUrl Download URL for successful exports
|
||||
* @param float $sizeMB File size in MB for failed exports
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function sendCSVEmail(
|
||||
bool $success,
|
||||
Document $project,
|
||||
string $userInternalId,
|
||||
array $options,
|
||||
Mail $queueForMails,
|
||||
string $downloadUrl = '',
|
||||
float $sizeMB = 0.0
|
||||
): void {
|
||||
if (!($options['notify'] ?? false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $this->dbForPlatform->findOne('users', [
|
||||
Query::equal('$sequence', [$userInternalId])
|
||||
]);
|
||||
|
||||
if ($user->isEmpty()) {
|
||||
Console::warning("User not found for CSV export notification: $userInternalId");
|
||||
return;
|
||||
}
|
||||
|
||||
$locale = new Locale(System::getEnv('_APP_LOCALE', 'en'));
|
||||
$locale->setFallback(System::getEnv('_APP_LOCALE', 'en'));
|
||||
|
||||
$emailType = $success
|
||||
? 'success'
|
||||
: 'failure';
|
||||
|
||||
// Get localized email content
|
||||
$subject = $locale->getText("emails.csvExport.{$emailType}.subject");
|
||||
$preview = $locale->getText("emails.csvExport.{$emailType}.preview");
|
||||
$hello = $locale->getText("emails.csvExport.{$emailType}.hello");
|
||||
$body = $locale->getText("emails.csvExport.{$emailType}.body");
|
||||
$footer = $locale->getText("emails.csvExport.{$emailType}.footer");
|
||||
$thanks = $locale->getText("emails.csvExport.{$emailType}.thanks");
|
||||
$signature = $locale->getText("emails.csvExport.{$emailType}.signature");
|
||||
$buttonText = $success ? $locale->getText("emails.csvExport.{$emailType}.buttonText") : '';
|
||||
|
||||
// Build email body using inner template
|
||||
$message = Template::fromFile(__DIR__ . '/../../../../app/config/locale/templates/email-inner-base.tpl')
|
||||
->setParam('{{body}}', $body, escapeHtml: false)
|
||||
->setParam('{{hello}}', $hello)
|
||||
->setParam('{{footer}}', $footer)
|
||||
->setParam('{{thanks}}', $thanks)
|
||||
->setParam('{{buttonText}}', $buttonText)
|
||||
->setParam('{{signature}}', $signature)
|
||||
->setParam('{{direction}}', $locale->getText('settings.direction'))
|
||||
->setParam('{{project}}', $project->getAttribute('name'))
|
||||
->setParam('{{user}}', $user->getAttribute('name', $user->getAttribute('email')))
|
||||
->setParam('{{redirect}}', $downloadUrl)
|
||||
->setParam('{{size}}', $success ? '' : (string)$sizeMB);
|
||||
|
||||
$emailBody = $message->render();
|
||||
|
||||
$emailVariables = [
|
||||
'direction' => $locale->getText('settings.direction'),
|
||||
'project' => $project->getAttribute('name'),
|
||||
'user' => $user->getAttribute('name', $user->getAttribute('email')),
|
||||
];
|
||||
|
||||
if ($success) {
|
||||
$emailVariables['redirect'] = $downloadUrl;
|
||||
} else {
|
||||
$emailVariables['size'] = (string)$sizeMB;
|
||||
}
|
||||
|
||||
$queueForMails
|
||||
->setSubject($subject)
|
||||
->setPreview($preview)
|
||||
->setBody($emailBody)
|
||||
->setBodyTemplate(__DIR__ . '/../../../../app/config/locale/templates/email-base-styled.tpl')
|
||||
->setVariables($emailVariables)
|
||||
->setName($user->getAttribute('name', $user->getAttribute('email')))
|
||||
->setRecipient($user->getAttribute('email'))
|
||||
->trigger();
|
||||
|
||||
Console::info("CSV export {$emailType} notification email sent to " . $user->getAttribute('email'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a filename to make it filesystem-safe
|
||||
*
|
||||
* @param string $filename
|
||||
* @return string
|
||||
*/
|
||||
protected function sanitizeFilename(string $filename): string
|
||||
{
|
||||
// Replace problematic characters with underscores
|
||||
$sanitized = \preg_replace('/[:\/<>"|*?]/', '_', $filename);
|
||||
$sanitized = \preg_replace('/[^\x20-\x7E]/', '_', $sanitized);
|
||||
$sanitized = \trim($sanitized);
|
||||
return empty($sanitized) ? 'export' : $sanitized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize migration errors, removing sensitive information like stack traces
|
||||
*
|
||||
* @param array $sourceErrors
|
||||
* @param array $destinationErrors
|
||||
* @return array
|
||||
*/
|
||||
protected function sanitizeErrors(
|
||||
array $sourceErrors,
|
||||
array $destinationErrors,
|
||||
): array {
|
||||
$errors = [];
|
||||
foreach ([...$sourceErrors, ...$destinationErrors] as $error) {
|
||||
$encoded = \json_decode(\json_encode($error), true);
|
||||
if (\is_array($encoded)) {
|
||||
if (isset($encoded['trace'])) {
|
||||
unset($encoded['trace']);
|
||||
}
|
||||
$errors[] = \json_encode($encoded);
|
||||
} else {
|
||||
$errors[] = \json_encode($error);
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,12 @@ class Migration extends Model
|
|||
'default' => [],
|
||||
'example' => [],
|
||||
])
|
||||
->addRule('options', [
|
||||
'type' => self::TYPE_JSON,
|
||||
'description' => 'Migration options used during the migration process.',
|
||||
'default' => [],
|
||||
'example' => '{"bucketId": "exports", "notify": false}',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
|
|
@ -117,18 +123,16 @@ class Migration extends Model
|
|||
}
|
||||
|
||||
foreach ($errors as $index => $error) {
|
||||
$decoded = json_decode($error, true);
|
||||
$decoded = \json_decode($error, true);
|
||||
|
||||
// frontend doesn't need too many details.
|
||||
if (is_array($decoded)) {
|
||||
$errors[$index] = json_encode([
|
||||
'code' => $decoded['code'] ?? 0,
|
||||
'message' => $decoded['message'] ?? null,
|
||||
]);
|
||||
if (\is_array($decoded)) {
|
||||
if (isset($decoded['trace'])) {
|
||||
unset($decoded['trace']);
|
||||
}
|
||||
$errors[$index] = \json_encode($decoded);
|
||||
}
|
||||
}
|
||||
|
||||
// errors now only have code and message.
|
||||
$document->setAttribute('errors', $errors);
|
||||
|
||||
return $document;
|
||||
|
|
|
|||
|
|
@ -900,7 +900,7 @@ trait MigrationsBase
|
|||
/**
|
||||
* Import documents from a CSV file.
|
||||
*/
|
||||
public function testCreateCsvMigration(): void
|
||||
public function testCreateCSVImport(): void
|
||||
{
|
||||
// Make a database
|
||||
$response = $this->client->call(Client::METHOD_POST, '/databases', [
|
||||
|
|
@ -1194,4 +1194,241 @@ trait MigrationsBase
|
|||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CSV export with email notification
|
||||
*/
|
||||
public function testCreateCSVExport(): void
|
||||
{
|
||||
// Create a database
|
||||
$database = $this->client->call(Client::METHOD_POST, '/databases', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'databaseId' => ID::unique(),
|
||||
'name' => 'Test Export Database'
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $database['headers']['status-code']);
|
||||
$databaseId = $database['body']['$id'];
|
||||
|
||||
// Create a collection
|
||||
$collection = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'collectionId' => ID::unique(),
|
||||
'name' => 'Test Export Collection',
|
||||
'permissions' => []
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $collection['headers']['status-code']);
|
||||
$collectionId = $collection['body']['$id'];
|
||||
|
||||
// Create a simple attribute like the basic test
|
||||
$name = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'key' => 'name',
|
||||
'size' => 255,
|
||||
'required' => true,
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $name['headers']['status-code']);
|
||||
|
||||
// Create a simple attribute like the basic test
|
||||
$email = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/attributes/string', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'key' => 'email',
|
||||
'size' => 255,
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $email['headers']['status-code']);
|
||||
|
||||
\sleep(3);
|
||||
|
||||
// Create sample documents
|
||||
for ($i = 1; $i <= 10; $i++) {
|
||||
$doc = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'documentId' => ID::unique(),
|
||||
'data' => [
|
||||
'name' => 'Test User ' . $i,
|
||||
'email' => 'user' . $i . '@appwrite.io'
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $doc['headers']['status-code'], 'Failed to create document ' . $i);
|
||||
}
|
||||
|
||||
// Verify documents were created
|
||||
$docs = $this->client->call(Client::METHOD_GET, '/databases/' . $databaseId . '/collections/' . $collectionId . '/documents', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $docs['headers']['status-code']);
|
||||
$this->assertEquals(10, $docs['body']['total'], 'Expected 10 documents but got ' . $docs['body']['total']);
|
||||
|
||||
// Create a storage bucket for the export
|
||||
$bucketIdUnique = ID::unique();
|
||||
$bucket = $this->client->call(Client::METHOD_POST, '/storage/buckets', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'bucketId' => $bucketIdUnique,
|
||||
'name' => 'Test Export Bucket',
|
||||
'permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::create(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'fileSecurity' => false,
|
||||
'enabled' => true,
|
||||
'maximumFileSize' => 10485760, // 10MB
|
||||
'allowedFileExtensions' => ['csv'],
|
||||
'compression' => 'none',
|
||||
'encryption' => false,
|
||||
'antivirus' => false
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $bucket['headers']['status-code']);
|
||||
$bucketId = $bucket['body']['$id'];
|
||||
|
||||
// Perform CSV export with notification enabled
|
||||
$migration = $this->client->call(Client::METHOD_POST, '/migrations/csv/exports', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
], $this->getHeaders()), [
|
||||
'bucketId' => $bucketId,
|
||||
'resourceId' => $databaseId . ':' . $collectionId,
|
||||
'filename' => 'test-export',
|
||||
'columns' => [],
|
||||
'delimiter' => ',',
|
||||
'enclosure' => '"',
|
||||
'escape' => '\\',
|
||||
'header' => true,
|
||||
'notify' => true
|
||||
]);
|
||||
|
||||
$this->assertEquals(202, $migration['headers']['status-code']);
|
||||
$this->assertNotEmpty($migration['body']['$id']);
|
||||
$migrationId = $migration['body']['$id'];
|
||||
|
||||
$this->assertEventually(function () use ($bucketId, $migrationId) {
|
||||
$response = $this->client->call(Client::METHOD_GET, '/migrations/' . $migrationId, [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals('finished', $response['body']['stage']);
|
||||
$this->assertEquals('completed', $response['body']['status']);
|
||||
$this->assertEquals('Appwrite', $response['body']['source']);
|
||||
$this->assertEquals('CSV', $response['body']['destination']);
|
||||
$this->assertEquals($bucketId, $response['body']['options']['bucketId']);
|
||||
|
||||
return true;
|
||||
}, 30000, 500);
|
||||
|
||||
// Check that the file was created in the bucket
|
||||
// Query files by filename
|
||||
$files = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
], [
|
||||
'queries' => [
|
||||
Query::equal('name', ['test-export'])->toString()
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $files['headers']['status-code']);
|
||||
$this->assertEquals(1, $files['body']['total'], 'Expected exactly one file with name "test-export"');
|
||||
|
||||
// Get the exported file
|
||||
$file = $files['body']['files'][0];
|
||||
$fileId = $file['$id'];
|
||||
|
||||
$this->assertEquals($bucketId, $file['bucketId']);
|
||||
$this->assertEquals('test-export', $file['name']);
|
||||
$this->assertEquals('text/csv', $file['mimeType']);
|
||||
$this->assertGreaterThan(0, $file['sizeOriginal']);
|
||||
|
||||
// Download and verify CSV content
|
||||
$download = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/download', \array_merge([
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()));
|
||||
|
||||
$this->assertEquals(200, $download['headers']['status-code']);
|
||||
|
||||
$csvContent = $download['body'];
|
||||
$lines = explode("\n", trim($csvContent));
|
||||
$this->assertCount(11, $lines);
|
||||
$this->assertStringContainsString('$id', $lines[0]);
|
||||
$this->assertStringContainsString('$permissions', $lines[0]);
|
||||
$this->assertStringContainsString('$createdAt', $lines[0]);
|
||||
$this->assertStringContainsString('$updatedAt', $lines[0]);
|
||||
$this->assertStringContainsString('name', $lines[0]);
|
||||
$this->assertStringContainsString('email', $lines[0]);
|
||||
|
||||
$this->assertStringContainsString('Test User 1', $lines[1]);
|
||||
$this->assertStringContainsString('user1@appwrite.io', $lines[1]);
|
||||
|
||||
// Check that email was sent with download link
|
||||
$lastEmail = $this->getLastEmail();
|
||||
$this->assertNotEmpty($lastEmail);
|
||||
$this->assertEquals('Your CSV export is ready', $lastEmail['subject']);
|
||||
$this->assertStringContainsStringIgnoringCase('Your data export has been completed successfully', $lastEmail['text']);
|
||||
|
||||
// Extract download URL from email HTML
|
||||
\preg_match('/href="([^"]*\/storage\/buckets\/[^"]*\/push[^"]*)"/', $lastEmail['html'], $matches);
|
||||
$this->assertNotEmpty($matches[1], 'Download URL not found in email');
|
||||
$downloadUrl = html_entity_decode($matches[1]);
|
||||
|
||||
// Parse the URL to extract components
|
||||
$components = \parse_url($downloadUrl);
|
||||
$this->assertNotEmpty($components);
|
||||
\parse_str($components['query'] ?? '', $queryParams);
|
||||
$this->assertArrayHasKey('jwt', $queryParams, 'JWT not found in download URL');
|
||||
$this->assertNotEmpty($queryParams['jwt']);
|
||||
|
||||
// Test download with JWT
|
||||
$path = \str_replace('/v1', '', $components['path']);
|
||||
$downloadWithJwt = $this->client->call(Client::METHOD_GET, $path . '?project=' . $queryParams['project'] . '&jwt=' . $queryParams['jwt']);
|
||||
$this->assertEquals(200, $downloadWithJwt['headers']['status-code'], 'Failed to download file with JWT');
|
||||
$this->assertEquals($csvContent, $downloadWithJwt['body'], 'Downloaded content differs from original');
|
||||
|
||||
// Test that download without JWT fails
|
||||
$downloadWithoutJwt = $this->client->call(Client::METHOD_GET, '/storage/buckets/' . $bucketId . '/files/' . $fileId . '/download');
|
||||
$this->assertEquals(404, $downloadWithoutJwt['headers']['status-code'], 'File should not be downloadable without JWT');
|
||||
|
||||
$this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId . '/files/' . $fileId, [
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId, [
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
$this->client->call(Client::METHOD_DELETE, '/storage/buckets/' . $bucketId, [
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue