From 1acdc3d1217cceea5591fbe9550e7e495aaa7a24 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 16 Nov 2023 21:30:59 +1300 Subject: [PATCH 1/2] Fix between query output --- README.md | 2 +- docs/examples/health/get-queue-builds.md | 2 + .../examples/health/get-queue-certificates.md | 3 +- docs/examples/health/get-queue-databases.md | 3 + docs/examples/health/get-queue-deletes.md | 2 + docs/examples/health/get-queue-functions.md | 3 +- docs/examples/health/get-queue-logs.md | 3 +- docs/examples/health/get-queue-mails.md | 2 + docs/examples/health/get-queue-messaging.md | 2 + docs/examples/health/get-queue-migrations.md | 2 + docs/examples/health/get-queue-webhooks.md | 3 +- lib/commands/account.js | 28 +-- lib/commands/avatars.js | 4 +- lib/commands/databases.js | 20 +- lib/commands/functions.js | 6 +- lib/commands/health.js | 221 +++++++++++++++++- lib/commands/storage.js | 32 +-- lib/commands/teams.js | 10 +- lib/commands/users.js | 18 +- 19 files changed, 298 insertions(+), 68 deletions(-) create mode 100644 docs/examples/health/get-queue-builds.md create mode 100644 docs/examples/health/get-queue-databases.md create mode 100644 docs/examples/health/get-queue-deletes.md create mode 100644 docs/examples/health/get-queue-mails.md create mode 100644 docs/examples/health/get-queue-messaging.md create mode 100644 docs/examples/health/get-queue-migrations.md diff --git a/README.md b/README.md index 94078ba..570b5c3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Appwrite Command Line SDK ![License](https://img.shields.io/github/license/appwrite/sdk-for-cli.svg?style=flat-square) -![Version](https://img.shields.io/badge/api%20version-1.4.2-blue.svg?style=flat-square) +![Version](https://img.shields.io/badge/api%20version-1.4.12-blue.svg?style=flat-square) [![Build Status](https://img.shields.io/travis/com/appwrite/sdk-generator?style=flat-square)](https://travis-ci.com/appwrite/sdk-generator) [![Twitter Account](https://img.shields.io/twitter/follow/appwrite?color=00acee&label=twitter&style=flat-square)](https://twitter.com/appwrite) [![Discord](https://img.shields.io/discord/564160730845151244?label=discord&style=flat-square)](https://appwrite.io/discord) diff --git a/docs/examples/health/get-queue-builds.md b/docs/examples/health/get-queue-builds.md new file mode 100644 index 0000000..7204c2c --- /dev/null +++ b/docs/examples/health/get-queue-builds.md @@ -0,0 +1,2 @@ +appwrite health getQueueBuilds \ + diff --git a/docs/examples/health/get-queue-certificates.md b/docs/examples/health/get-queue-certificates.md index 7cae239..203e865 100644 --- a/docs/examples/health/get-queue-certificates.md +++ b/docs/examples/health/get-queue-certificates.md @@ -1 +1,2 @@ -appwrite health getQueueCertificates +appwrite health getQueueCertificates \ + diff --git a/docs/examples/health/get-queue-databases.md b/docs/examples/health/get-queue-databases.md new file mode 100644 index 0000000..f30b941 --- /dev/null +++ b/docs/examples/health/get-queue-databases.md @@ -0,0 +1,3 @@ +appwrite health getQueueDatabases \ + + diff --git a/docs/examples/health/get-queue-deletes.md b/docs/examples/health/get-queue-deletes.md new file mode 100644 index 0000000..1d44146 --- /dev/null +++ b/docs/examples/health/get-queue-deletes.md @@ -0,0 +1,2 @@ +appwrite health getQueueDeletes \ + diff --git a/docs/examples/health/get-queue-functions.md b/docs/examples/health/get-queue-functions.md index 9edfdda..4aca467 100644 --- a/docs/examples/health/get-queue-functions.md +++ b/docs/examples/health/get-queue-functions.md @@ -1 +1,2 @@ -appwrite health getQueueFunctions +appwrite health getQueueFunctions \ + diff --git a/docs/examples/health/get-queue-logs.md b/docs/examples/health/get-queue-logs.md index 9a09749..3f1386b 100644 --- a/docs/examples/health/get-queue-logs.md +++ b/docs/examples/health/get-queue-logs.md @@ -1 +1,2 @@ -appwrite health getQueueLogs +appwrite health getQueueLogs \ + diff --git a/docs/examples/health/get-queue-mails.md b/docs/examples/health/get-queue-mails.md new file mode 100644 index 0000000..a41f4c3 --- /dev/null +++ b/docs/examples/health/get-queue-mails.md @@ -0,0 +1,2 @@ +appwrite health getQueueMails \ + diff --git a/docs/examples/health/get-queue-messaging.md b/docs/examples/health/get-queue-messaging.md new file mode 100644 index 0000000..11b7ff8 --- /dev/null +++ b/docs/examples/health/get-queue-messaging.md @@ -0,0 +1,2 @@ +appwrite health getQueueMessaging \ + diff --git a/docs/examples/health/get-queue-migrations.md b/docs/examples/health/get-queue-migrations.md new file mode 100644 index 0000000..2f17bb5 --- /dev/null +++ b/docs/examples/health/get-queue-migrations.md @@ -0,0 +1,2 @@ +appwrite health getQueueMigrations \ + diff --git a/docs/examples/health/get-queue-webhooks.md b/docs/examples/health/get-queue-webhooks.md index f38eaa4..471175b 100644 --- a/docs/examples/health/get-queue-webhooks.md +++ b/docs/examples/health/get-queue-webhooks.md @@ -1 +1,2 @@ -appwrite health getQueueWebhooks +appwrite health getQueueWebhooks \ + diff --git a/lib/commands/account.js b/lib/commands/account.js index 8771ef5..cdc1543 100644 --- a/lib/commands/account.js +++ b/lib/commands/account.js @@ -815,7 +815,7 @@ account account .command(`create`) - .description(`Use this endpoint to allow a new user to register a new account in your project. After the user registration completes successfully, you can use the [/account/verfication](/docs/client/account#accountCreateVerification) route to start verifying the user email address. To allow the new user to login to their new account, you need to create a new [account session](/docs/client/account#accountCreateSession).`) + .description(`Use this endpoint to allow a new user to register a new account in your project. After the user registration completes successfully, you can use the [/account/verfication](https://appwrite.io/docs/references/cloud/client-web/account#createVerification) route to start verifying the user email address. To allow the new user to login to their new account, you need to create a new [account session](https://appwrite.io/docs/references/cloud/client-web/account#createEmailSession).`) .requiredOption(`--userId `, `Unique Id. Choose a custom ID or generate a random ID with 'ID.unique()'. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.`) .requiredOption(`--email `, `User email.`) .requiredOption(`--password `, `New user password. Must be at least 8 chars.`) @@ -867,7 +867,7 @@ account account .command(`updatePhone`) - .description(`Update the currently logged in user's phone number. After updating the phone number, the phone verification status will be reset. A confirmation SMS is not sent automatically, however you can use the [POST /account/verification/phone](/docs/client/account#accountCreatePhoneVerification) endpoint to send a confirmation SMS.`) + .description(`Update the currently logged in user's phone number. After updating the phone number, the phone verification status will be reset. A confirmation SMS is not sent automatically, however you can use the [POST /account/verification/phone](https://appwrite.io/docs/references/cloud/client-web/account#createPhoneVerification) endpoint to send a confirmation SMS.`) .requiredOption(`--phone `, `Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.`) .requiredOption(`--password `, `User password. Must be at least 8 chars.`) .action(actionRunner(accountUpdatePhone)) @@ -885,14 +885,14 @@ account account .command(`createRecovery`) - .description(`Sends the user an email with a temporary secret key for password reset. When the user clicks the confirmation link he is redirected back to your app password reset URL with the secret key and email address values attached to the URL query string. Use the query string params to submit a request to the [PUT /account/recovery](/docs/client/account#accountUpdateRecovery) endpoint to complete the process. The verification link sent to the user's email address is valid for 1 hour.`) + .description(`Sends the user an email with a temporary secret key for password reset. When the user clicks the confirmation link he is redirected back to your app password reset URL with the secret key and email address values attached to the URL query string. Use the query string params to submit a request to the [PUT /account/recovery](https://appwrite.io/docs/references/cloud/client-web/account#updateRecovery) endpoint to complete the process. The verification link sent to the user's email address is valid for 1 hour.`) .requiredOption(`--email `, `User email.`) .requiredOption(`--url `, `URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.`) .action(actionRunner(accountCreateRecovery)) account .command(`updateRecovery`) - .description(`Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST /account/recovery](/docs/client/account#accountCreateRecovery) endpoint. Please note that in order to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.`) + .description(`Use this endpoint to complete the user account password reset. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST /account/recovery](https://appwrite.io/docs/references/cloud/client-web/account#createRecovery) endpoint. Please note that in order to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.`) .requiredOption(`--userId `, `User ID.`) .requiredOption(`--secret `, `Valid reset token.`) .requiredOption(`--password `, `New user password. Must be at least 8 chars.`) @@ -911,19 +911,19 @@ account account .command(`createAnonymousSession`) - .description(`Use this endpoint to allow a new user to register an anonymous account in your project. This route will also create a new session for the user. To allow the new user to convert an anonymous account to a normal account, you need to update its [email and password](/docs/client/account#accountUpdateEmail) or create an [OAuth2 session](/docs/client/account#accountCreateOAuth2Session).`) + .description(`Use this endpoint to allow a new user to register an anonymous account in your project. This route will also create a new session for the user. To allow the new user to convert an anonymous account to a normal account, you need to update its [email and password](https://appwrite.io/docs/references/cloud/client-web/account#updateEmail) or create an [OAuth2 session](https://appwrite.io/docs/references/cloud/client-web/account#CreateOAuth2Session).`) .action(actionRunner(accountCreateAnonymousSession)) account .command(`createEmailSession`) - .description(`Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user. A user is limited to 10 active sessions at a time by default. [Learn more about session limits](/docs/authentication-security#limits).`) + .description(`Allow the user to login into their account by providing a valid email and password combination. This route will create a new session for the user. A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits).`) .requiredOption(`--email `, `User email.`) .requiredOption(`--password `, `User password. Must be at least 8 chars.`) .action(actionRunner(accountCreateEmailSession)) account .command(`createMagicURLSession`) - .description(`Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [PUT /account/sessions/magic-url](/docs/client/account#accountUpdateMagicURLSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default. A user is limited to 10 active sessions at a time by default. [Learn more about session limits](/docs/authentication-security#limits). `) + .description(`Sends the user an email with a secret key for creating a session. If the provided user ID has not been registered, a new user will be created. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [PUT /account/sessions/magic-url](https://appwrite.io/docs/references/cloud/client-web/account#updateMagicURLSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default. A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). `) .requiredOption(`--userId `, `Unique Id. Choose a custom ID or generate a random ID with 'ID.unique()'. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.`) .requiredOption(`--email `, `User email.`) .option(`--url `, `URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.`) @@ -931,14 +931,14 @@ account account .command(`updateMagicURLSession`) - .description(`Use this endpoint to complete creating the session with the Magic URL. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST /account/sessions/magic-url](/docs/client/account#accountCreateMagicURLSession) endpoint. Please note that in order to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.`) + .description(`Use this endpoint to complete creating the session with the Magic URL. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST /account/sessions/magic-url](https://appwrite.io/docs/references/cloud/client-web/account#createMagicURLSession) endpoint. Please note that in order to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.`) .requiredOption(`--userId `, `User ID.`) .requiredOption(`--secret `, `Valid verification token.`) .action(actionRunner(accountUpdateMagicURLSession)) account .command(`createOAuth2Session`) - .description(`Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. If there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user. A user is limited to 10 active sessions at a time by default. [Learn more about session limits](/docs/authentication-security#limits). `) + .description(`Allow the user to login to their account using the OAuth2 provider of their choice. Each OAuth2 provider should be enabled from the Appwrite console first. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed. If there is already an active session, the new session will be attached to the logged-in account. If there are no active sessions, the server will attempt to look for a user with the same email address as the email received from the OAuth2 provider and attach the new session to the existing user. If no matching user is found - the server will create a new user. A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits). `) .requiredOption(`--provider `, `OAuth2 Provider. Currently, supported providers are: amazon, apple, auth0, authentik, autodesk, bitbucket, bitly, box, dailymotion, discord, disqus, dropbox, etsy, facebook, github, gitlab, google, linkedin, microsoft, notion, oidc, okta, paypal, paypalSandbox, podio, salesforce, slack, spotify, stripe, tradeshift, tradeshiftBox, twitch, wordpress, yahoo, yammer, yandex, zoom.`) .option(`--success `, `URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.`) .option(`--failure `, `URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project's platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.`) @@ -947,14 +947,14 @@ account account .command(`createPhoneSession`) - .description(`Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [PUT /account/sessions/phone](/docs/client/account#accountUpdatePhoneSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes. A user is limited to 10 active sessions at a time by default. [Learn more about session limits](/docs/authentication-security#limits).`) + .description(`Sends the user an SMS with a secret key for creating a session. If the provided user ID has not be registered, a new user will be created. Use the returned user ID and secret and submit a request to the [PUT /account/sessions/phone](https://appwrite.io/docs/references/cloud/client-web/account#updatePhoneSession) endpoint to complete the login process. The secret sent to the user's phone is valid for 15 minutes. A user is limited to 10 active sessions at a time by default. [Learn more about session limits](https://appwrite.io/docs/authentication-security#limits).`) .requiredOption(`--userId `, `Unique Id. Choose a custom ID or generate a random ID with 'ID.unique()'. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.`) .requiredOption(`--phone `, `Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.`) .action(actionRunner(accountCreatePhoneSession)) account .command(`updatePhoneSession`) - .description(`Use this endpoint to complete creating a session with SMS. Use the **userId** from the [createPhoneSession](/docs/client/account#accountCreatePhoneSession) endpoint and the **secret** received via SMS to successfully update and confirm the phone session.`) + .description(`Use this endpoint to complete creating a session with SMS. Use the **userId** from the [createPhoneSession](https://appwrite.io/docs/references/cloud/client-web/account#createPhoneSession) endpoint and the **secret** received via SMS to successfully update and confirm the phone session.`) .requiredOption(`--userId `, `User ID.`) .requiredOption(`--secret `, `Valid verification token.`) .action(actionRunner(accountUpdatePhoneSession)) @@ -973,7 +973,7 @@ account account .command(`deleteSession`) - .description(`Logout the user. Use 'current' as the session ID to logout on this device, use a session ID to logout on another device. If you're looking to logout the user on all devices, use [Delete Sessions](/docs/client/account#accountDeleteSessions) instead.`) + .description(`Logout the user. Use 'current' as the session ID to logout on this device, use a session ID to logout on another device. If you're looking to logout the user on all devices, use [Delete Sessions](https://appwrite.io/docs/references/cloud/client-web/account#deleteSessions) instead.`) .requiredOption(`--sessionId `, `Session ID. Use the string 'current' to delete the current device session.`) .action(actionRunner(accountDeleteSession)) @@ -984,7 +984,7 @@ account account .command(`createVerification`) - .description(`Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](/docs/client/account#accountUpdateEmailVerification). The verification link sent to the user's email address is valid for 7 days. Please note that in order to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface. `) + .description(`Use this endpoint to send a verification message to your user email address to confirm they are the valid owners of that address. Both the **userId** and **secret** arguments will be passed as query parameters to the URL you have provided to be attached to the verification email. The provided URL should redirect the user back to your app and allow you to complete the verification process by verifying both the **userId** and **secret** parameters. Learn more about how to [complete the verification process](https://appwrite.io/docs/references/cloud/client-web/account#updateVerification). The verification link sent to the user's email address is valid for 7 days. Please note that in order to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface. `) .requiredOption(`--url `, `URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.`) .action(actionRunner(accountCreateVerification)) @@ -997,7 +997,7 @@ account account .command(`createPhoneVerification`) - .description(`Use this endpoint to send a verification SMS to the currently logged in user. This endpoint is meant for use after updating a user's phone number using the [accountUpdatePhone](/docs/client/account#accountUpdatePhone) endpoint. Learn more about how to [complete the verification process](/docs/client/account#accountUpdatePhoneVerification). The verification code sent to the user's phone number is valid for 15 minutes.`) + .description(`Use this endpoint to send a verification SMS to the currently logged in user. This endpoint is meant for use after updating a user's phone number using the [accountUpdatePhone](https://appwrite.io/docs/references/cloud/client-web/account#updatePhone) endpoint. Learn more about how to [complete the verification process](https://appwrite.io/docs/references/cloud/client-web/account#updatePhoneVerification). The verification code sent to the user's phone number is valid for 15 minutes.`) .action(actionRunner(accountCreatePhoneVerification)) account diff --git a/lib/commands/avatars.js b/lib/commands/avatars.js index 0008de3..b09b94f 100644 --- a/lib/commands/avatars.js +++ b/lib/commands/avatars.js @@ -272,7 +272,7 @@ const avatarsGetQR = async ({ text, size, margin, download, parseOutput = true, avatars .command(`getBrowser`) - .description(`You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET /account/sessions](/docs/client/account#accountGetSessions) endpoint. Use width, height and quality arguments to change the output settings. When one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.`) + .description(`You can use this endpoint to show different browser icons to your users. The code argument receives the browser code as it appears in your user [GET /account/sessions](https://appwrite.io/docs/references/cloud/client-web/account#getSessions) endpoint. Use width, height and quality arguments to change the output settings. When one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px.`) .requiredOption(`--code `, `Browser Code.`) .option(`--width `, `Image width. Pass an integer between 0 to 2000. Defaults to 100.`, parseInteger) .option(`--height `, `Image height. Pass an integer between 0 to 2000. Defaults to 100.`, parseInteger) @@ -299,7 +299,7 @@ avatars avatars .command(`getFlag`) - .description(`You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](http://en.wikipedia.org/wiki/ISO_3166-1) standard. When one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px. `) + .description(`You can use this endpoint to show different country flags icons to your users. The code argument receives the 2 letter country code. Use width, height and quality arguments to change the output settings. Country codes follow the [ISO 3166-1](https://en.wikipedia.org/wiki/ISO_3166-1) standard. When one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 100x100px. `) .requiredOption(`--code `, `Country Code. ISO Alpha-2 country code format.`) .option(`--width `, `Image width. Pass an integer between 0 to 2000. Defaults to 100.`, parseInteger) .option(`--height `, `Image height. Pass an integer between 0 to 2000. Defaults to 100.`, parseInteger) diff --git a/lib/commands/databases.js b/lib/commands/databases.js index b81d5b9..efb5028 100644 --- a/lib/commands/databases.js +++ b/lib/commands/databases.js @@ -1699,12 +1699,12 @@ databases databases .command(`createCollection`) - .description(`Create a new Collection. Before using this route, you should create a new database resource using either a [server integration](/docs/server/databases#databasesCreateCollection) API or directly from your database console.`) + .description(`Create a new Collection. Before using this route, you should create a new database resource using either a [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection) API or directly from your database console.`) .requiredOption(`--databaseId `, `Database ID.`) .requiredOption(`--collectionId `, `Unique Id. Choose a custom ID or generate a random ID with 'ID.unique()'. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.`) .requiredOption(`--name `, `Collection name. Max length: 128 chars.`) - .option(`--permissions [permissions...]`, `An array of permissions strings. By default, no user is granted with any permissions. [Learn more about permissions](/docs/permissions).`) - .option(`--documentSecurity `, `Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. [Learn more about permissions](/docs/permissions).`, parseBool) + .option(`--permissions [permissions...]`, `An array of permissions strings. By default, no user is granted with any permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).`) + .option(`--documentSecurity `, `Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. [Learn more about permissions](https://appwrite.io/docs/permissions).`, parseBool) .option(`--enabled `, `Is collection enabled? When set to 'disabled', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.`, parseBool) .action(actionRunner(databasesCreateCollection)) @@ -1721,8 +1721,8 @@ databases .requiredOption(`--databaseId `, `Database ID.`) .requiredOption(`--collectionId `, `Collection ID.`) .requiredOption(`--name `, `Collection name. Max length: 128 chars.`) - .option(`--permissions [permissions...]`, `An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](/docs/permissions).`) - .option(`--documentSecurity `, `Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. [Learn more about permissions](/docs/permissions).`, parseBool) + .option(`--permissions [permissions...]`, `An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).`) + .option(`--documentSecurity `, `Enables configuring permissions for individual documents. A user needs one of document or collection level permissions to access a document. [Learn more about permissions](https://appwrite.io/docs/permissions).`, parseBool) .option(`--enabled `, `Is collection enabled? When set to 'disabled', users cannot access the collection but Server SDKs with and API key can still read and write to the collection. No data is lost when this is toggled.`, parseBool) .action(actionRunner(databasesUpdateCollection)) @@ -1900,7 +1900,7 @@ databases databases .command(`createRelationshipAttribute`) - .description(`Create relationship attribute. [Learn more about relationship attributes](/docs/databases-relationships#relationship-attributes). `) + .description(`Create relationship attribute. [Learn more about relationship attributes](https://appwrite.io/docs/databases-relationships#relationship-attributes). `) .requiredOption(`--databaseId `, `Database ID.`) .requiredOption(`--collectionId `, `Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).`) .requiredOption(`--relatedCollectionId `, `Related Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).`) @@ -1973,7 +1973,7 @@ databases databases .command(`updateRelationshipAttribute`) - .description(`Update relationship attribute. [Learn more about relationship attributes](/docs/databases-relationships#relationship-attributes). `) + .description(`Update relationship attribute. [Learn more about relationship attributes](https://appwrite.io/docs/databases-relationships#relationship-attributes). `) .requiredOption(`--databaseId `, `Database ID.`) .requiredOption(`--collectionId `, `Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).`) .requiredOption(`--key `, `Attribute Key.`) @@ -1990,12 +1990,12 @@ databases databases .command(`createDocument`) - .description(`Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](/docs/server/databases#databasesCreateCollection) API or directly from your database console.`) + .description(`Create a new Document. Before using this route, you should create a new collection resource using either a [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection) API or directly from your database console.`) .requiredOption(`--databaseId `, `Database ID.`) .requiredOption(`--collectionId `, `Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.`) .requiredOption(`--documentId `, `Document ID. Choose a custom ID or generate a random ID with 'ID.unique()'. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.`) .requiredOption(`--data `, `Document data as JSON object.`) - .option(`--permissions [permissions...]`, `An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](/docs/permissions).`) + .option(`--permissions [permissions...]`, `An array of permissions strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).`) .action(actionRunner(databasesCreateDocument)) databases @@ -2014,7 +2014,7 @@ databases .requiredOption(`--collectionId `, `Collection ID.`) .requiredOption(`--documentId `, `Document ID.`) .option(`--data `, `Document data as JSON object. Include only attribute and value pairs to be updated.`) - .option(`--permissions [permissions...]`, `An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](/docs/permissions).`) + .option(`--permissions [permissions...]`, `An array of permissions strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).`) .action(actionRunner(databasesUpdateDocument)) databases diff --git a/lib/commands/functions.js b/lib/commands/functions.js index 5ed1e3f..5d6d00d 100644 --- a/lib/commands/functions.js +++ b/lib/commands/functions.js @@ -866,7 +866,7 @@ functions functions .command(`create`) - .description(`Create a new function. You can pass a list of [permissions](/docs/permissions) to allow different project users or team with access to execute the function using the client API.`) + .description(`Create a new function. You can pass a list of [permissions](https://appwrite.io/docs/permissions) to allow different project users or team with access to execute the function using the client API.`) .requiredOption(`--functionId `, `Function ID. Choose a custom ID or generate a random ID with 'ID.unique()'. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.`) .requiredOption(`--name `, `Function name. Max length: 128 chars.`) .requiredOption(`--runtime `, `Execution runtime.`) @@ -943,7 +943,7 @@ functions functions .command(`createDeployment`) - .description(`Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID. This endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](/docs/functions). Use the "command" param to set the entrypoint used to execute your code.`) + .description(`Create a new function code deployment. Use this endpoint to upload a new version of your code function. To execute your newly uploaded code, you'll need to update the function's deployment to use your new deployment UID. This endpoint accepts a tar.gz file compressed with your code. Make sure to include any dependencies your code has within the compressed file. You can learn more about code packaging in the [Appwrite Cloud Functions tutorial](https://appwrite.io/docs/functions). Use the "command" param to set the entrypoint used to execute your code.`) .requiredOption(`--functionId `, `Function ID.`) .requiredOption(`--code `, `Gzip file with your code package. When used with the Appwrite CLI, pass the path to your code directory, and the CLI will automatically package your code. Use a path that is within the current directory.`) .requiredOption(`--activate `, `Automatically activate the deployment when it is finished building.`, parseBool) @@ -982,7 +982,7 @@ functions functions .command(`downloadDeployment`) - .description(``) + .description(`Get a Deployment's contents by its unique ID. This endpoint supports range requests for partial or streaming file download.`) .requiredOption(`--functionId `, `Function ID.`) .requiredOption(`--deploymentId `, `Deployment ID.`) .requiredOption(`--destination `, `output file path.`) diff --git a/lib/commands/health.js b/lib/commands/health.js index 679b714..c47cad6 100644 --- a/lib/commands/health.js +++ b/lib/commands/health.js @@ -116,11 +116,67 @@ const healthGetQueue = async ({ parseOutput = true, sdk = undefined}) => { return response; } -const healthGetQueueCertificates = async ({ parseOutput = true, sdk = undefined}) => { +const healthGetQueueBuilds = async ({ threshold, parseOutput = true, sdk = undefined}) => { + /* @param {number} threshold */ + + let client = !sdk ? await sdkForProject() : sdk; + let apiPath = '/health/queue/builds'; + let payload = {}; + + /** Query Params */ + if (typeof threshold !== 'undefined') { + payload['threshold'] = threshold; + } + let response = undefined; + response = await client.call('get', apiPath, { + 'content-type': 'application/json', + }, payload); + + if (parseOutput) { + parse(response) + success() + } + return response; +} + +const healthGetQueueCertificates = async ({ threshold, parseOutput = true, sdk = undefined}) => { + /* @param {number} threshold */ let client = !sdk ? await sdkForProject() : sdk; let apiPath = '/health/queue/certificates'; let payload = {}; + + /** Query Params */ + if (typeof threshold !== 'undefined') { + payload['threshold'] = threshold; + } + let response = undefined; + response = await client.call('get', apiPath, { + 'content-type': 'application/json', + }, payload); + + if (parseOutput) { + parse(response) + success() + } + return response; +} + +const healthGetQueueDatabases = async ({ name, threshold, parseOutput = true, sdk = undefined}) => { + /* @param {string} name */ + /* @param {number} threshold */ + + let client = !sdk ? await sdkForProject() : sdk; + let apiPath = '/health/queue/databases'; + let payload = {}; + + /** Query Params */ + if (typeof name !== 'undefined') { + payload['name'] = name; + } + if (typeof threshold !== 'undefined') { + payload['threshold'] = threshold; + } let response = undefined; response = await client.call('get', apiPath, { 'content-type': 'application/json', @@ -133,11 +189,40 @@ const healthGetQueueCertificates = async ({ parseOutput = true, sdk = undefined} return response; } -const healthGetQueueFunctions = async ({ parseOutput = true, sdk = undefined}) => { +const healthGetQueueDeletes = async ({ threshold, parseOutput = true, sdk = undefined}) => { + /* @param {number} threshold */ + + let client = !sdk ? await sdkForProject() : sdk; + let apiPath = '/health/queue/deletes'; + let payload = {}; + + /** Query Params */ + if (typeof threshold !== 'undefined') { + payload['threshold'] = threshold; + } + let response = undefined; + response = await client.call('get', apiPath, { + 'content-type': 'application/json', + }, payload); + + if (parseOutput) { + parse(response) + success() + } + return response; +} + +const healthGetQueueFunctions = async ({ threshold, parseOutput = true, sdk = undefined}) => { + /* @param {number} threshold */ let client = !sdk ? await sdkForProject() : sdk; let apiPath = '/health/queue/functions'; let payload = {}; + + /** Query Params */ + if (typeof threshold !== 'undefined') { + payload['threshold'] = threshold; + } let response = undefined; response = await client.call('get', apiPath, { 'content-type': 'application/json', @@ -150,11 +235,17 @@ const healthGetQueueFunctions = async ({ parseOutput = true, sdk = undefined}) = return response; } -const healthGetQueueLogs = async ({ parseOutput = true, sdk = undefined}) => { +const healthGetQueueLogs = async ({ threshold, parseOutput = true, sdk = undefined}) => { + /* @param {number} threshold */ let client = !sdk ? await sdkForProject() : sdk; let apiPath = '/health/queue/logs'; let payload = {}; + + /** Query Params */ + if (typeof threshold !== 'undefined') { + payload['threshold'] = threshold; + } let response = undefined; response = await client.call('get', apiPath, { 'content-type': 'application/json', @@ -167,11 +258,86 @@ const healthGetQueueLogs = async ({ parseOutput = true, sdk = undefined}) => { return response; } -const healthGetQueueWebhooks = async ({ parseOutput = true, sdk = undefined}) => { +const healthGetQueueMails = async ({ threshold, parseOutput = true, sdk = undefined}) => { + /* @param {number} threshold */ + + let client = !sdk ? await sdkForProject() : sdk; + let apiPath = '/health/queue/mails'; + let payload = {}; + + /** Query Params */ + if (typeof threshold !== 'undefined') { + payload['threshold'] = threshold; + } + let response = undefined; + response = await client.call('get', apiPath, { + 'content-type': 'application/json', + }, payload); + + if (parseOutput) { + parse(response) + success() + } + return response; +} + +const healthGetQueueMessaging = async ({ threshold, parseOutput = true, sdk = undefined}) => { + /* @param {number} threshold */ + + let client = !sdk ? await sdkForProject() : sdk; + let apiPath = '/health/queue/messaging'; + let payload = {}; + + /** Query Params */ + if (typeof threshold !== 'undefined') { + payload['threshold'] = threshold; + } + let response = undefined; + response = await client.call('get', apiPath, { + 'content-type': 'application/json', + }, payload); + + if (parseOutput) { + parse(response) + success() + } + return response; +} + +const healthGetQueueMigrations = async ({ threshold, parseOutput = true, sdk = undefined}) => { + /* @param {number} threshold */ + + let client = !sdk ? await sdkForProject() : sdk; + let apiPath = '/health/queue/migrations'; + let payload = {}; + + /** Query Params */ + if (typeof threshold !== 'undefined') { + payload['threshold'] = threshold; + } + let response = undefined; + response = await client.call('get', apiPath, { + 'content-type': 'application/json', + }, payload); + + if (parseOutput) { + parse(response) + success() + } + return response; +} + +const healthGetQueueWebhooks = async ({ threshold, parseOutput = true, sdk = undefined}) => { + /* @param {number} threshold */ let client = !sdk ? await sdkForProject() : sdk; let apiPath = '/health/queue/webhooks'; let payload = {}; + + /** Query Params */ + if (typeof threshold !== 'undefined') { + payload['threshold'] = threshold; + } let response = undefined; response = await client.call('get', apiPath, { 'content-type': 'application/json', @@ -249,24 +415,65 @@ health .description(`Check the Appwrite queue messaging servers are up and connection is successful.`) .action(actionRunner(healthGetQueue)) +health + .command(`getQueueBuilds`) + .description(`Get the number of builds that are waiting to be processed in the Appwrite internal queue server.`) + .option(`--threshold `, `Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.`, parseInteger) + .action(actionRunner(healthGetQueueBuilds)) + health .command(`getQueueCertificates`) .description(`Get the number of certificates that are waiting to be issued against [Letsencrypt](https://letsencrypt.org/) in the Appwrite internal queue server.`) + .option(`--threshold `, `Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.`, parseInteger) .action(actionRunner(healthGetQueueCertificates)) +health + .command(`getQueueDatabases`) + .description(`Get the number of database changes that are waiting to be processed in the Appwrite internal queue server.`) + .option(`--name `, `Queue name for which to check the queue size`) + .option(`--threshold `, `Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.`, parseInteger) + .action(actionRunner(healthGetQueueDatabases)) + +health + .command(`getQueueDeletes`) + .description(`Get the number of background destructive changes that are waiting to be processed in the Appwrite internal queue server.`) + .option(`--threshold `, `Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.`, parseInteger) + .action(actionRunner(healthGetQueueDeletes)) + health .command(`getQueueFunctions`) .description(``) + .option(`--threshold `, `Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.`, parseInteger) .action(actionRunner(healthGetQueueFunctions)) health .command(`getQueueLogs`) .description(`Get the number of logs that are waiting to be processed in the Appwrite internal queue server.`) + .option(`--threshold `, `Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.`, parseInteger) .action(actionRunner(healthGetQueueLogs)) +health + .command(`getQueueMails`) + .description(`Get the number of mails that are waiting to be processed in the Appwrite internal queue server.`) + .option(`--threshold `, `Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.`, parseInteger) + .action(actionRunner(healthGetQueueMails)) + +health + .command(`getQueueMessaging`) + .description(`Get the number of messages that are waiting to be processed in the Appwrite internal queue server.`) + .option(`--threshold `, `Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.`, parseInteger) + .action(actionRunner(healthGetQueueMessaging)) + +health + .command(`getQueueMigrations`) + .description(`Get the number of migrations that are waiting to be processed in the Appwrite internal queue server.`) + .option(`--threshold `, `Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.`, parseInteger) + .action(actionRunner(healthGetQueueMigrations)) + health .command(`getQueueWebhooks`) .description(`Get the number of webhooks that are waiting to be processed in the Appwrite internal queue server.`) + .option(`--threshold `, `Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.`, parseInteger) .action(actionRunner(healthGetQueueWebhooks)) health @@ -288,9 +495,15 @@ module.exports = { healthGetDB, healthGetPubSub, healthGetQueue, + healthGetQueueBuilds, healthGetQueueCertificates, + healthGetQueueDatabases, + healthGetQueueDeletes, healthGetQueueFunctions, healthGetQueueLogs, + healthGetQueueMails, + healthGetQueueMessaging, + healthGetQueueMigrations, healthGetQueueWebhooks, healthGetStorageLocal, healthGetTime diff --git a/lib/commands/storage.js b/lib/commands/storage.js index b6cdadf..9224aa7 100644 --- a/lib/commands/storage.js +++ b/lib/commands/storage.js @@ -599,8 +599,8 @@ storage .description(`Create a new storage bucket.`) .requiredOption(`--bucketId `, `Unique Id. Choose a custom ID or generate a random ID with 'ID.unique()'. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.`) .requiredOption(`--name `, `Bucket name`) - .option(`--permissions [permissions...]`, `An array of permission strings. By default, no user is granted with any permissions. [Learn more about permissions](/docs/permissions).`) - .option(`--fileSecurity `, `Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](/docs/permissions).`, parseBool) + .option(`--permissions [permissions...]`, `An array of permission strings. By default, no user is granted with any permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).`) + .option(`--fileSecurity `, `Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](https://appwrite.io/docs/permissions).`, parseBool) .option(`--enabled `, `Is bucket enabled? When set to 'disabled', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.`, parseBool) .option(`--maximumFileSize `, `Maximum file size allowed in bytes. Maximum allowed value is 30MB.`, parseInteger) .option(`--allowedFileExtensions [allowedFileExtensions...]`, `Allowed file extensions. Maximum of 100 extensions are allowed, each 64 characters long.`) @@ -620,8 +620,8 @@ storage .description(`Update a storage bucket by its unique ID.`) .requiredOption(`--bucketId `, `Bucket unique ID.`) .requiredOption(`--name `, `Bucket name`) - .option(`--permissions [permissions...]`, `An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](/docs/permissions).`) - .option(`--fileSecurity `, `Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](/docs/permissions).`, parseBool) + .option(`--permissions [permissions...]`, `An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).`) + .option(`--fileSecurity `, `Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](https://appwrite.io/docs/permissions).`, parseBool) .option(`--enabled `, `Is bucket enabled? When set to 'disabled', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.`, parseBool) .option(`--maximumFileSize `, `Maximum file size allowed in bytes. Maximum allowed value is 30MB.`, parseInteger) .option(`--allowedFileExtensions [allowedFileExtensions...]`, `Allowed file extensions. Maximum of 100 extensions are allowed, each 64 characters long.`) @@ -639,47 +639,47 @@ storage storage .command(`listFiles`) .description(`Get a list of all the user files. You can use the query params to filter your results.`) - .requiredOption(`--bucketId `, `Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).`) + .requiredOption(`--bucketId `, `Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).`) .option(`--queries [queries...]`, `Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on the following attributes: name, signature, mimeType, sizeOriginal, chunksTotal, chunksUploaded`) .option(`--search `, `Search term to filter your list results. Max length: 256 chars.`) .action(actionRunner(storageListFiles)) storage .command(`createFile`) - .description(`Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](/docs/server/storage#storageCreateBucket) API or directly from your Appwrite console. Larger files should be uploaded using multiple requests with the [content-range](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range) header to send a partial request with a maximum supported chunk of '5MB'. The 'content-range' header values should always be in bytes. When the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in 'x-appwrite-id' header to allow the server to know that the partial upload is for the existing file and not for a new one. If you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally. `) - .requiredOption(`--bucketId `, `Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).`) + .description(`Create a new file. Before using this route, you should create a new bucket resource using either a [server integration](https://appwrite.io/docs/server/storage#storageCreateBucket) API or directly from your Appwrite console. Larger files should be uploaded using multiple requests with the [content-range](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range) header to send a partial request with a maximum supported chunk of '5MB'. The 'content-range' header values should always be in bytes. When the first request is sent, the server will return the **File** object, and the subsequent part request must include the file's **id** in 'x-appwrite-id' header to allow the server to know that the partial upload is for the existing file and not for a new one. If you're creating a new file using one of the Appwrite SDKs, all the chunking logic will be managed by the SDK internally. `) + .requiredOption(`--bucketId `, `Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).`) .requiredOption(`--fileId `, `File ID. Choose a custom ID or generate a random ID with 'ID.unique()'. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.`) - .requiredOption(`--file `, `Binary file. Appwrite SDKs provide helpers to handle file input. [Learn about file input](/docs/storage#file-input).`) - .option(`--permissions [permissions...]`, `An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](/docs/permissions).`) + .requiredOption(`--file `, `Binary file. Appwrite SDKs provide helpers to handle file input. [Learn about file input](https://appwrite.io/docs/storage#file-input).`) + .option(`--permissions [permissions...]`, `An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).`) .action(actionRunner(storageCreateFile)) storage .command(`getFile`) .description(`Get a file by its unique ID. This endpoint response returns a JSON object with the file metadata.`) - .requiredOption(`--bucketId `, `Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).`) + .requiredOption(`--bucketId `, `Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).`) .requiredOption(`--fileId `, `File ID.`) .action(actionRunner(storageGetFile)) storage .command(`updateFile`) .description(`Update a file by its unique ID. Only users with write permissions have access to update this resource.`) - .requiredOption(`--bucketId `, `Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).`) + .requiredOption(`--bucketId `, `Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).`) .requiredOption(`--fileId `, `File unique ID.`) .option(`--name `, `Name of the file`) - .option(`--permissions [permissions...]`, `An array of permission string. By default, the current permissions are inherited. [Learn more about permissions](/docs/permissions).`) + .option(`--permissions [permissions...]`, `An array of permission string. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).`) .action(actionRunner(storageUpdateFile)) storage .command(`deleteFile`) .description(`Delete a file by its unique ID. Only users with write permissions have access to delete this resource.`) - .requiredOption(`--bucketId `, `Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).`) + .requiredOption(`--bucketId `, `Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).`) .requiredOption(`--fileId `, `File ID.`) .action(actionRunner(storageDeleteFile)) storage .command(`getFileDownload`) .description(`Get a file content by its unique ID. The endpoint response return with a 'Content-Disposition: attachment' header that tells the browser to start downloading the file to user downloads directory.`) - .requiredOption(`--bucketId `, `Storage bucket ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).`) + .requiredOption(`--bucketId `, `Storage bucket ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).`) .requiredOption(`--fileId `, `File ID.`) .requiredOption(`--destination `, `output file path.`) .action(actionRunner(storageGetFileDownload)) @@ -687,7 +687,7 @@ storage storage .command(`getFilePreview`) .description(`Get a file preview image. Currently, this method supports preview for image files (jpg, png, and gif), other supported formats, like pdf, docs, slides, and spreadsheets, will return the file icon image. You can also pass query string arguments for cutting and resizing your preview image. Preview is supported only for image files smaller than 10MB.`) - .requiredOption(`--bucketId `, `Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).`) + .requiredOption(`--bucketId `, `Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).`) .requiredOption(`--fileId `, `File ID`) .option(`--width `, `Resize preview image width, Pass an integer between 0 to 4000.`, parseInteger) .option(`--height `, `Resize preview image height, Pass an integer between 0 to 4000.`, parseInteger) @@ -706,7 +706,7 @@ storage storage .command(`getFileView`) .description(`Get a file content by its unique ID. This endpoint is similar to the download method but returns with no 'Content-Disposition: attachment' header.`) - .requiredOption(`--bucketId `, `Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](/docs/server/storage#createBucket).`) + .requiredOption(`--bucketId `, `Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).`) .requiredOption(`--fileId `, `File ID.`) .requiredOption(`--destination `, `output file path.`) .action(actionRunner(storageGetFileView)) diff --git a/lib/commands/teams.js b/lib/commands/teams.js index 665eeb9..6f63d71 100644 --- a/lib/commands/teams.js +++ b/lib/commands/teams.js @@ -405,7 +405,7 @@ teams .description(`Create a new team. The user who creates the team will automatically be assigned as the owner of the team. Only the users with the owner role can invite new members, add new owners and delete or update the team.`) .requiredOption(`--teamId `, `Team ID. Choose a custom ID or generate a random ID with 'ID.unique()'. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.`) .requiredOption(`--name `, `Team name. Max length: 128 chars.`) - .option(`--roles [roles...]`, `Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Maximum of 100 roles are allowed, each 32 characters long.`) + .option(`--roles [roles...]`, `Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of 100 roles are allowed, each 32 characters long.`) .action(actionRunner(teamsCreate)) teams @@ -444,9 +444,9 @@ teams teams .command(`createMembership`) - .description(`Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team. You only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters. Use the 'url' parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](/docs/client/teams#teamsUpdateMembershipStatus) endpoint to allow the user to accept the invitation to the team. Please note that to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console. `) + .description(`Invite a new member to join your team. Provide an ID for existing users, or invite unregistered users using an email or phone number. If initiated from a Client SDK, Appwrite will send an email or sms with a link to join the team to the invited user, and an account will be created for them if one doesn't exist. If initiated from a Server SDK, the new member will be added automatically to the team. You only need to provide one of a user ID, email, or phone number. Appwrite will prioritize accepting the user ID > email > phone number if you provide more than one of these parameters. Use the 'url' parameter to redirect the user from the invitation email to your app. After the user is redirected, use the [Update Team Membership Status](https://appwrite.io/docs/references/cloud/client-web/teams#updateMembershipStatus) endpoint to allow the user to accept the invitation to the team. Please note that to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) Appwrite will accept the only redirect URLs under the domains you have added as a platform on the Appwrite Console. `) .requiredOption(`--teamId `, `Team ID.`) - .requiredOption(`--roles [roles...]`, `Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](/docs/permissions). Maximum of 100 roles are allowed, each 32 characters long.`) + .requiredOption(`--roles [roles...]`, `Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of 100 roles are allowed, each 32 characters long.`) .option(`--email `, `Email of the new team member.`) .option(`--userId `, `ID of the user to be added to a team.`) .option(`--phone `, `Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.`) @@ -463,7 +463,7 @@ teams teams .command(`updateMembership`) - .description(`Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](/docs/permissions). `) + .description(`Modify the roles of a team member. Only team members with the owner role have access to this endpoint. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). `) .requiredOption(`--teamId `, `Team ID.`) .requiredOption(`--membershipId `, `Membership ID.`) .requiredOption(`--roles [roles...]`, `An array of strings. Use this param to set the user's roles in the team. A role can be any string. Learn more about [roles and permissions](https://appwrite.io/docs/permissions). Maximum of 100 roles are allowed, each 32 characters long.`) @@ -487,7 +487,7 @@ teams teams .command(`getPrefs`) - .description(`Get the team's shared preferences by its unique ID. If a preference doesn't need to be shared by all team members, prefer storing them in [user preferences](/docs/client/account#accountGetPrefs).`) + .description(`Get the team's shared preferences by its unique ID. If a preference doesn't need to be shared by all team members, prefer storing them in [user preferences](https://appwrite.io/docs/references/cloud/client-web/account#getPrefs).`) .requiredOption(`--teamId `, `Team ID.`) .action(actionRunner(teamsGetPrefs)) diff --git a/lib/commands/users.js b/lib/commands/users.js index 7cc67f0..660730c 100644 --- a/lib/commands/users.js +++ b/lib/commands/users.js @@ -922,7 +922,7 @@ users users .command(`createArgon2User`) - .description(`Create a new user. Password provided must be hashed with the [Argon2](https://en.wikipedia.org/wiki/Argon2) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to create users with a plain text password.`) + .description(`Create a new user. Password provided must be hashed with the [Argon2](https://en.wikipedia.org/wiki/Argon2) algorithm. Use the [POST /users](https://appwrite.io/docs/server/users#usersCreate) endpoint to create users with a plain text password.`) .requiredOption(`--userId `, `User ID. Choose a custom ID or generate a random ID with 'ID.unique()'. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.`) .requiredOption(`--email `, `User email.`) .requiredOption(`--password `, `User password hashed using Argon2.`) @@ -931,7 +931,7 @@ users users .command(`createBcryptUser`) - .description(`Create a new user. Password provided must be hashed with the [Bcrypt](https://en.wikipedia.org/wiki/Bcrypt) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to create users with a plain text password.`) + .description(`Create a new user. Password provided must be hashed with the [Bcrypt](https://en.wikipedia.org/wiki/Bcrypt) algorithm. Use the [POST /users](https://appwrite.io/docs/server/users#usersCreate) endpoint to create users with a plain text password.`) .requiredOption(`--userId `, `User ID. Choose a custom ID or generate a random ID with 'ID.unique()'. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.`) .requiredOption(`--email `, `User email.`) .requiredOption(`--password `, `User password hashed using Bcrypt.`) @@ -953,7 +953,7 @@ users users .command(`createMD5User`) - .description(`Create a new user. Password provided must be hashed with the [MD5](https://en.wikipedia.org/wiki/MD5) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to create users with a plain text password.`) + .description(`Create a new user. Password provided must be hashed with the [MD5](https://en.wikipedia.org/wiki/MD5) algorithm. Use the [POST /users](https://appwrite.io/docs/server/users#usersCreate) endpoint to create users with a plain text password.`) .requiredOption(`--userId `, `User ID. Choose a custom ID or generate a random ID with 'ID.unique()'. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.`) .requiredOption(`--email `, `User email.`) .requiredOption(`--password `, `User password hashed using MD5.`) @@ -962,7 +962,7 @@ users users .command(`createPHPassUser`) - .description(`Create a new user. Password provided must be hashed with the [PHPass](https://www.openwall.com/phpass/) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to create users with a plain text password.`) + .description(`Create a new user. Password provided must be hashed with the [PHPass](https://www.openwall.com/phpass/) algorithm. Use the [POST /users](https://appwrite.io/docs/server/users#usersCreate) endpoint to create users with a plain text password.`) .requiredOption(`--userId `, `User ID. Choose a custom ID or pass the string 'ID.unique()'to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.`) .requiredOption(`--email `, `User email.`) .requiredOption(`--password `, `User password hashed using PHPass.`) @@ -971,7 +971,7 @@ users users .command(`createScryptUser`) - .description(`Create a new user. Password provided must be hashed with the [Scrypt](https://github.com/Tarsnap/scrypt) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to create users with a plain text password.`) + .description(`Create a new user. Password provided must be hashed with the [Scrypt](https://github.com/Tarsnap/scrypt) algorithm. Use the [POST /users](https://appwrite.io/docs/server/users#usersCreate) endpoint to create users with a plain text password.`) .requiredOption(`--userId `, `User ID. Choose a custom ID or generate a random ID with 'ID.unique()'. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.`) .requiredOption(`--email `, `User email.`) .requiredOption(`--password `, `User password hashed using Scrypt.`) @@ -985,7 +985,7 @@ users users .command(`createScryptModifiedUser`) - .description(`Create a new user. Password provided must be hashed with the [Scrypt Modified](https://gist.github.com/Meldiron/eecf84a0225eccb5a378d45bb27462cc) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to create users with a plain text password.`) + .description(`Create a new user. Password provided must be hashed with the [Scrypt Modified](https://gist.github.com/Meldiron/eecf84a0225eccb5a378d45bb27462cc) algorithm. Use the [POST /users](https://appwrite.io/docs/server/users#usersCreate) endpoint to create users with a plain text password.`) .requiredOption(`--userId `, `User ID. Choose a custom ID or generate a random ID with 'ID.unique()'. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.`) .requiredOption(`--email `, `User email.`) .requiredOption(`--password `, `User password hashed using Scrypt Modified.`) @@ -997,7 +997,7 @@ users users .command(`createSHAUser`) - .description(`Create a new user. Password provided must be hashed with the [SHA](https://en.wikipedia.org/wiki/Secure_Hash_Algorithm) algorithm. Use the [POST /users](/docs/server/users#usersCreate) endpoint to create users with a plain text password.`) + .description(`Create a new user. Password provided must be hashed with the [SHA](https://en.wikipedia.org/wiki/Secure_Hash_Algorithm) algorithm. Use the [POST /users](https://appwrite.io/docs/server/users#usersCreate) endpoint to create users with a plain text password.`) .requiredOption(`--userId `, `User ID. Choose a custom ID or generate a random ID with 'ID.unique()'. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.`) .requiredOption(`--email `, `User email.`) .requiredOption(`--password `, `User password hashed using SHA.`) @@ -1020,7 +1020,7 @@ users users .command(`delete`) - .description(`Delete a user by its unique ID, thereby releasing it's ID. Since ID is released and can be reused, all user-related resources like documents or storage files should be deleted before user deletion. If you want to keep ID reserved, use the [updateStatus](/docs/server/users#usersUpdateStatus) endpoint instead.`) + .description(`Delete a user by its unique ID, thereby releasing it's ID. Since ID is released and can be reused, all user-related resources like documents or storage files should be deleted before user deletion. If you want to keep ID reserved, use the [updateStatus](https://appwrite.io/docs/server/users#usersUpdateStatus) endpoint instead.`) .requiredOption(`--userId `, `User ID.`) .action(actionRunner(usersDelete)) @@ -1033,7 +1033,7 @@ users users .command(`updateLabels`) - .description(`Update the user labels by its unique ID. Labels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](/docs/permissions) for more info.`) + .description(`Update the user labels by its unique ID. Labels can be used to grant access to resources. While teams are a way for user's to share access to a resource, labels can be defined by the developer to grant access without an invitation. See the [Permissions docs](https://appwrite.io/docs/permissions) for more info.`) .requiredOption(`--userId `, `User ID.`) .requiredOption(`--labels [labels...]`, `Array of user labels. Replaces the previous labels. Maximum of 100 labels are allowed, each up to 36 alphanumeric characters long.`) .action(actionRunner(usersUpdateLabels)) From 8522fe67f820ff7d3f67482a0bccb403a7496416 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Mon, 20 Nov 2023 23:17:14 +1300 Subject: [PATCH 2/2] Add pagination + scaling timeouts for database deploys --- README.md | 4 +- install.ps1 | 4 +- install.sh | 2 +- lib/client.js | 4 +- lib/commands/deploy.js | 269 ++++++++++++++++++++++++++--------------- lib/commands/init.js | 34 ++---- lib/paginate.js | 51 ++++++++ lib/questions.js | 17 ++- package.json | 2 +- scoop/appwrite.json | 6 +- 10 files changed, 250 insertions(+), 143 deletions(-) create mode 100644 lib/paginate.js diff --git a/README.md b/README.md index 570b5c3..2a49447 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Once the installation is complete, you can verify the install using ```sh $ appwrite -v -4.1.0 +4.2.0 ``` ### Install using prebuilt binaries @@ -63,7 +63,7 @@ $ scoop install https://raw.githubusercontent.com/appwrite/sdk-for-cli/master/sc Once the installation completes, you can verify your install using ``` $ appwrite -v -4.1.0 +4.2.0 ``` ## Getting Started diff --git a/install.ps1 b/install.ps1 index d5812a8..257d9b0 100644 --- a/install.ps1 +++ b/install.ps1 @@ -13,8 +13,8 @@ # You can use "View source" of this page to see the full script. # REPO -$GITHUB_x64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/4.1.0/appwrite-cli-win-x64.exe" -$GITHUB_arm64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/4.1.0/appwrite-cli-win-arm64.exe" +$GITHUB_x64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/4.2.0/appwrite-cli-win-x64.exe" +$GITHUB_arm64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/4.2.0/appwrite-cli-win-arm64.exe" $APPWRITE_BINARY_NAME = "appwrite.exe" diff --git a/install.sh b/install.sh index d476a2f..93151cc 100644 --- a/install.sh +++ b/install.sh @@ -97,7 +97,7 @@ printSuccess() { downloadBinary() { echo "[2/4] Downloading executable for $OS ($ARCH) ..." - GITHUB_LATEST_VERSION="4.1.0" + GITHUB_LATEST_VERSION="4.2.0" GITHUB_FILE="appwrite-cli-${OS}-${ARCH}" GITHUB_URL="https://github.com/$GITHUB_REPOSITORY_NAME/releases/download/$GITHUB_LATEST_VERSION/$GITHUB_FILE" diff --git a/lib/client.js b/lib/client.js index 1ea7063..a437997 100644 --- a/lib/client.js +++ b/lib/client.js @@ -16,8 +16,8 @@ class Client { 'x-sdk-name': 'Command Line', 'x-sdk-platform': 'console', 'x-sdk-language': 'cli', - 'x-sdk-version': '4.1.0', - 'user-agent' : `AppwriteCLI/4.1.0 (${os.type()} ${os.version()}; ${os.arch()})`, + 'x-sdk-version': '4.2.0', + 'user-agent' : `AppwriteCLI/4.2.0 (${os.type()} ${os.version()}; ${os.arch()})`, 'X-Appwrite-Response-Format' : '1.4.0', }; } diff --git a/lib/commands/deploy.js b/lib/commands/deploy.js index 7b06dd5..5eed0d9 100644 --- a/lib/commands/deploy.js +++ b/lib/commands/deploy.js @@ -2,6 +2,7 @@ const inquirer = require("inquirer"); const JSONbig = require("json-bigint")({ storeAsString: false }); const { Command } = require("commander"); const { localConfig } = require("../config"); +const { paginate } = require('../paginate'); const { questionsDeployBuckets, questionsDeployTeams, questionsDeployFunctions, questionsGetEntrypoint, questionsDeployCollections, questionsConfirmDeployCollections } = require("../questions"); const { actionRunner, success, log, error, commandDescriptions } = require("../parser"); const { functionsGet, functionsCreate, functionsUpdate, functionsCreateDeployment, functionsUpdateDeployment, functionsListVariables, functionsDeleteVariable, functionsCreateVariable } = require('./functions'); @@ -37,113 +38,163 @@ const { teamsCreate } = require("./teams"); -const POOL_DEBOUNCE = 2000; // in milliseconds -const POOL_MAX_DEBOUNCES = 30; +const STEP_SIZE = 100; // Resources +const POOL_DEBOUNCE = 2000; // Milliseconds + +let poolMaxDebounces = 30; const awaitPools = { wipeAttributes: async (databaseId, collectionId, iteration = 1) => { - if (iteration > POOL_MAX_DEBOUNCES) { + if (iteration > poolMaxDebounces) { return false; } - // TODO: Pagination? - const { attributes: remoteAttributes } = await databasesListAttributes({ + const { total } = await databasesListAttributes({ databaseId, collectionId, - queries: ['limit(100)'], + queries: ['limit(1)'], parseOutput: false }); - if (remoteAttributes.length <= 0) { + if (total === 0) { return true; } + let steps = Math.max(1, Math.ceil(total / STEP_SIZE)); + if (steps > 1 && iteration === 1) { + poolMaxDebounces *= steps; + + log('Found a large number of attributes, increasing timeout to ' + (poolMaxDebounces * POOL_DEBOUNCE / 1000 / 60) + ' minutes') + } + await new Promise(resolve => setTimeout(resolve, POOL_DEBOUNCE)); - return await awaitPools.wipeAttributes(databaseId, collectionId, iteration + 1); + + return await awaitPools.wipeAttributes( + databaseId, + collectionId, + iteration + 1 + ); }, wipeIndexes: async (databaseId, collectionId, iteration = 1) => { - if (iteration > POOL_MAX_DEBOUNCES) { + if (iteration > poolMaxDebounces) { return false; } - // TODO: Pagination? - const { indexes: remoteIndexes } = await databasesListIndexes({ + const { total } = await databasesListIndexes({ databaseId, collectionId, queries: ['limit(100)'], parseOutput: false }); - if (remoteIndexes.length <= 0) { + if (total === 0) { return true; } + let steps = Math.max(1, Math.ceil(total / STEP_SIZE)); + if (steps > 1 && iteration === 1) { + poolMaxDebounces *= steps; + + log('Found a large number of indexes, increasing timeout to ' + (poolMaxDebounces * POOL_DEBOUNCE / 1000 / 60) + ' minutes') + } + await new Promise(resolve => setTimeout(resolve, POOL_DEBOUNCE)); - return await awaitPools.wipeIndexes(databaseId, collectionId, iteration + 1); + + return await awaitPools.wipeIndexes( + databaseId, + collectionId, + iteration + 1 + ); }, expectAttributes: async (databaseId, collectionId, attributeKeys, iteration = 1) => { - if (iteration > POOL_MAX_DEBOUNCES) { + if (iteration > poolMaxDebounces) { return false; } - // TODO: Pagination? - const { attributes: remoteAttributes } = await databasesListAttributes({ + let steps = Math.max(1, Math.ceil(attributeKeys.length / STEP_SIZE)); + if (steps > 1 && iteration === 1) { + poolMaxDebounces *= steps; + + log('Creating a large number of attributes, increasing timeout to ' + (poolMaxDebounces * POOL_DEBOUNCE / 1000 / 60) + ' minutes') + } + + const { attributes } = await paginate(databasesListAttributes, { databaseId, collectionId, - queries: ['limit(100)'], parseOutput: false - }); + }, 100, 'attributes'); - const readyAttributeKeys = remoteAttributes.filter((attribute) => { - if (attributeKeys.includes(attribute.key)) { - if (['stuck', 'failed'].includes(attribute.status)) { - throw new Error(`Attribute '${attribute.key}' failed!`); - } + const ready = attributes + .filter(attribute => { + if (attributeKeys.includes(attribute.key)) { + if (['stuck', 'failed'].includes(attribute.status)) { + throw new Error(`Attribute '${attribute.key}' failed!`); + } - return attribute.status === 'available'; - } + return attribute.status === 'available'; + } - return false; - }).map(attribute => attribute.key); + return false; + }) + .map(attribute => attribute.key); - if (readyAttributeKeys.length >= attributeKeys.length) { + if (ready.length === attributeKeys.length) { return true; } await new Promise(resolve => setTimeout(resolve, POOL_DEBOUNCE)); - return await awaitPools.expectAttributes(databaseId, collectionId, attributeKeys, iteration + 1); + + return await awaitPools.expectAttributes( + databaseId, + collectionId, + attributeKeys, + iteration + 1 + ); }, expectIndexes: async (databaseId, collectionId, indexKeys, iteration = 1) => { - if (iteration > POOL_MAX_DEBOUNCES) { + if (iteration > poolMaxDebounces) { return false; } - // TODO: Pagination? - const { indexes: remoteIndexes } = await databasesListIndexes({ + let steps = Math.max(1, Math.ceil(indexKeys.length / STEP_SIZE)); + if (steps > 1 && iteration === 1) { + poolMaxDebounces *= steps; + + log('Creating a large number of indexes, increasing timeout to ' + (poolMaxDebounces * POOL_DEBOUNCE / 1000 / 60) + ' minutes') + } + + const { indexes } = await paginate(databasesListIndexes, { databaseId, collectionId, - queries: ['limit(100)'], parseOutput: false - }); + }, 100, 'indexes'); - const readyIndexKeys = remoteIndexes.filter((index) => { - if (indexKeys.includes(index.key)) { - if (['stuck', 'failed'].includes(index.status)) { - throw new Error(`Index '${index.key}' failed!`); - } + const ready = indexes + .filter((index) => { + if (indexKeys.includes(index.key)) { + if (['stuck', 'failed'].includes(index.status)) { + throw new Error(`Index '${index.key}' failed!`); + } - return index.status === 'available'; - } + return index.status === 'available'; + } - return false; - }).map(index => index.key); + return false; + }) + .map(index => index.key); - if (readyIndexKeys.length >= indexKeys.length) { + if (ready.length >= indexKeys.length) { return true; } await new Promise(resolve => setTimeout(resolve, POOL_DEBOUNCE)); - return await awaitPools.expectIndexes(databaseId, collectionId, indexKeys, iteration + 1); + + return await awaitPools.expectIndexes( + databaseId, + collectionId, + indexKeys, + iteration + 1 + ); }, } @@ -249,19 +300,19 @@ const deployFunction = async ({ functionId, all, yes } = {}) => { if (func.variables) { // Delete existing variables - // TODO: Pagination? - const { variables: remoteVariables } = await functionsListVariables({ + const { total } = await functionsListVariables({ functionId: func['$id'], - queries: ['limit(100)'], + queries: ['limit(1)'], parseOutput: false }); let deployVariables = yes; - if (remoteVariables.length == 0) { + + if (total === 0) { deployVariables = true; } else if (remoteVariables.length > 0 && !yes) { const variableAnswers = await inquirer.prompt(questionsDeployFunctions[1]) - deployVariables = variableAnswers.override === "YES"; + deployVariables = variableAnswers.override.toLowerCase() === "yes"; } if (!deployVariables) { @@ -291,7 +342,7 @@ const deployFunction = async ({ functionId, all, yes } = {}) => { // Create tag if (!func.entrypoint) { - answers = await inquirer.prompt(questionsGetEntrypoint) + const answers = await inquirer.prompt(questionsGetEntrypoint) func.entrypoint = answers.entrypoint; localConfig.updateFunction(func['$id'], func); } @@ -473,22 +524,27 @@ const deployCollection = async ({ all, yes } = {}) => { databaseId: collection.databaseId, parseOutput: false, }); + databaseId = database.$id; - await databasesUpdate({ - databaseId: collection.databaseId, - name: localDatabase.name ?? collection.databaseId, - parseOutput: false - }) + if (database.name !== (localDatabase.name ?? collection.databaseId)) { + await databasesUpdate({ + databaseId: collection.databaseId, + name: localDatabase.name ?? collection.databaseId, + parseOutput: false + }) - success(`Updated ${localDatabase.name} ( ${collection.databaseId} )`); + success(`Updated ${localDatabase.name} ( ${collection.databaseId} )`); + } } catch (err) { log(`Database ${collection.databaseId} not found. Creating it now...`); + const database = await databasesCreate({ databaseId: collection.databaseId, name: localDatabase.name ?? collection.databaseId, parseOutput: false, }); + databaseId = database.$id; } @@ -498,11 +554,12 @@ const deployCollection = async ({ all, yes } = {}) => { collectionId: collection['$id'], parseOutput: false, }) + log(`Collection ${collection.name} ( ${collection['$id']} ) already exists.`); if (!yes) { - answers = await inquirer.prompt(questionsDeployCollections[1]) - if (answers.override !== "YES") { + const answers = await inquirer.prompt(questionsDeployCollections[1]) + if (answers.override.toLowerCase() !== "yes") { log(`Received "${answers.override}". Skipping ${collection.name} ( ${collection['$id']} )`); continue; } @@ -510,15 +567,13 @@ const deployCollection = async ({ all, yes } = {}) => { log(`Deleting indexes and attributes ... `); - // TODO: Pagination? - const { indexes: remoteIndexes } = await databasesListIndexes({ + const { indexes } = await paginate(databasesListIndexes, { databaseId, collectionId: collection['$id'], - queries: ['limit(100)'], parseOutput: false - }); + }, 100, 'indexes'); - await Promise.all(remoteIndexes.map(async index => { + await Promise.all(indexes.map(async index => { await databasesDeleteIndex({ databaseId, collectionId: collection['$id'], @@ -527,20 +582,18 @@ const deployCollection = async ({ all, yes } = {}) => { }); })); - const deleteIndexesPoolStatus = await awaitPools.wipeIndexes(databaseId, collection['$id']); - if (!deleteIndexesPoolStatus) { - throw new Error("Index deletion did not finish for too long."); + let result = await awaitPools.wipeIndexes(databaseId, collection['$id']); + if (!result) { + throw new Error("Index deletion timed out."); } - // TODO: Pagination? - const { attributes: remoteAttributes } = await databasesListAttributes({ + const { attributes } = await paginate(databasesListAttributes, { databaseId, collectionId: collection['$id'], - queries: ['limit(100)'], parseOutput: false - }); + }, 100, 'attributes'); - await Promise.all(remoteAttributes.map(async attribute => { + await Promise.all(attributes.map(async attribute => { await databasesDeleteAttribute({ databaseId, collectionId: collection['$id'], @@ -551,7 +604,7 @@ const deployCollection = async ({ all, yes } = {}) => { const deleteAttributesPoolStatus = await awaitPools.wipeAttributes(databaseId, collection['$id']); if (!deleteAttributesPoolStatus) { - throw new Error("Attribute deletion did not finish for too long."); + throw new Error("Attribute deletion timed out."); } await databasesUpdateCollection({ @@ -581,20 +634,26 @@ const deployCollection = async ({ all, yes } = {}) => { } // Create all non-relationship attributes first - const nonRelationshipAttributes = collection.attributes.filter(attribute => attribute.type !== 'relationship'); - await Promise.all(nonRelationshipAttributes.map(attribute => { + const attributes = collection.attributes.filter(attribute => attribute.type !== 'relationship'); + + await Promise.all(attributes.map(attribute => { return createAttribute(databaseId, collection['$id'], attribute); })); - const nonRelationshipAttributeKeys = nonRelationshipAttributes.map(attribute => attribute.key); - const createPoolStatus = await awaitPools.expectAttributes(databaseId, collection['$id'], nonRelationshipAttributeKeys); - if (!createPoolStatus) { - throw new Error("Attribute creation did not finish for too long."); + let result = await awaitPools.expectAttributes( + databaseId, + collection['$id'], + attributes.map(attribute => attribute.key) + ); + + if (!result) { + throw new Error("Attribute creation timed out."); } - success(`Created ${nonRelationshipAttributeKeys.length} non-relationship attributes`); + success(`Created ${attributes.length} non-relationship attributes`); log(`Creating indexes ...`) + await Promise.all(collection.indexes.map(async index => { await databasesCreateIndex({ databaseId, @@ -607,10 +666,14 @@ const deployCollection = async ({ all, yes } = {}) => { }); })); - const indexKeys = collection.indexes.map(attribute => attribute.key); - const indexPoolStatus = await awaitPools.expectIndexes(databaseId, collection['$id'], indexKeys); - if (!indexPoolStatus) { - throw new Error("Index creation did not finish for too long."); + result = await awaitPools.expectIndexes( + databaseId, + collection['$id'], + collection.indexes.map(attribute => attribute.key) + ); + + if (!result) { + throw new Error("Index creation timed out."); } success(`Created ${collection.indexes.length} indexes`); @@ -620,23 +683,31 @@ const deployCollection = async ({ all, yes } = {}) => { // Create the relationship attributes for (let collection of collections) { - const relationshipAttributes = collection.attributes.filter(attribute => attribute.type === 'relationship' && attribute.side === 'parent'); + const relationships = collection.attributes.filter(attribute => + attribute.type === 'relationship' && attribute.side === 'parent' + ); - if (relationshipAttributes.length === 0) continue; + if (relationships.length === 0) { + continue; + } log(`Deploying relationships for collection ${collection.name} ( ${collection['$id']} )`); - await Promise.all(relationshipAttributes.map(attribute => { + await Promise.all(relationships.map(attribute => { return createAttribute(collection['databaseId'], collection['$id'], attribute); })); - const nonRelationshipAttributeKeys = relationshipAttributes.map(attribute => attribute.key); - const createPoolStatus = await awaitPools.expectAttributes(collection['databaseId'], collection['$id'], nonRelationshipAttributeKeys); - if (!createPoolStatus) { - throw new Error("Attribute creation did not finish for too long."); + let result = await awaitPools.expectAttributes( + collection['databaseId'], + collection['$id'], + relationships.map(attribute => attribute.key) + ); + + if (!result) { + throw new Error("Attribute creation timed out."); } - success(`Created ${nonRelationshipAttributeKeys.length} relationship attributes`); + success(`Created ${relationships.length} relationship attributes`); } } @@ -654,7 +725,7 @@ const deployBucket = async ({ all, yes } = {}) => { } if (bucketIds.length === 0) { - let answers = await inquirer.prompt(questionsDeployBuckets[0]) + const answers = await inquirer.prompt(questionsDeployBuckets[0]) bucketIds.push(...answers.buckets); } @@ -676,8 +747,8 @@ const deployBucket = async ({ all, yes } = {}) => { log(`Bucket ${bucket.name} ( ${bucket['$id']} ) already exists.`); if (!yes) { - answers = await inquirer.prompt(questionsDeployBuckets[1]) - if (answers.override !== "YES") { + const answers = await inquirer.prompt(questionsDeployBuckets[1]) + if (answers.override.toLowerCase() !== "yes") { log(`Received "${answers.override}". Skipping ${bucket.name} ( ${bucket['$id']} )`); continue; } @@ -741,7 +812,7 @@ const deployTeam = async ({ all, yes } = {}) => { } if (teamIds.length === 0) { - let answers = await inquirer.prompt(questionsDeployTeams[0]) + const answers = await inquirer.prompt(questionsDeployTeams[0]) teamIds.push(...answers.teams); } @@ -763,8 +834,8 @@ const deployTeam = async ({ all, yes } = {}) => { log(`Team ${team.name} ( ${team['$id']} ) already exists.`); if (!yes) { - answers = await inquirer.prompt(questionsDeployTeams[1]) - if (answers.override !== "YES") { + const answers = await inquirer.prompt(questionsDeployTeams[1]) + if (answers.override.toLowerCase() !== "yes") { log(`Received "${answers.override}". Skipping ${team.name} ( ${team['$id']} )`); continue; } @@ -808,7 +879,7 @@ deploy deploy .command("collection") .description("Deploy collections in the current project.") - .option(`--all`, `Flag to deploy all functions`) + .option(`--all`, `Flag to deploy all collections`) .option(`--yes`, `Flag to confirm all warnings`) .action(actionRunner(deployCollection)); diff --git a/lib/commands/init.js b/lib/commands/init.js index e91c085..706ed97 100644 --- a/lib/commands/init.js +++ b/lib/commands/init.js @@ -10,6 +10,7 @@ const { databasesGet, databasesListCollections, databasesList } = require("./dat const { storageListBuckets } = require("./storage"); const { sdkForConsole } = require("../sdks"); const { localConfig } = require("../config"); +const { paginate } = require("../paginate"); const { questionsInitProject, questionsInitFunction, questionsInitCollection } = require("../questions"); const { success, log, actionRunner, commandDescriptions } = require("../parser"); @@ -24,11 +25,11 @@ const init = new Command("init") const initProject = async () => { let response = {} - let answers = await inquirer.prompt(questionsInitProject) + const answers = await inquirer.prompt(questionsInitProject) if (!answers.project) process.exit(1) let sdk = await sdkForConsole(); - if (answers.start == "new") { + if (answers.start === "new") { response = await teamsCreate({ teamId: 'unique()', name: answers.project, @@ -53,8 +54,8 @@ const initProject = async () => { const initFunction = async () => { // TODO: Add CI/CD support (ID, name, runtime) - let answers = await inquirer.prompt(questionsInitFunction) - let functionFolder = path.join(process.cwd(), 'functions'); + const answers = await inquirer.prompt(questionsInitFunction) + const functionFolder = path.join(process.cwd(), 'functions'); if (!fs.existsSync(functionFolder)) { fs.mkdirSync(functionFolder, { @@ -92,7 +93,7 @@ const initFunction = async () => { let gitPullCommands = `git sparse-checkout add ${answers.runtime.id}`; /* Force use CMD as powershell does not support && */ - if (process.platform == 'win32') { + if (process.platform === 'win32') { gitInitCommands = 'cmd /c "' + gitInitCommands + '"'; gitPullCommands = 'cmd /c "' + gitPullCommands + '"'; } @@ -187,15 +188,12 @@ const initCollection = async ({ all, databaseId } = {}) => { localConfig.addDatabase(database); - // TODO: Pagination? - let response = await databasesListCollections({ + const { collections, total } = await paginate(databasesListCollections, { databaseId, - queries: ['limit(100)'], parseOutput: false - }) + }, 100, 'collections'); - let collections = response.collections; - log(`Found ${collections.length} collections`); + log(`Found ${total} collections`); collections.forEach(async collection => { log(`Fetching ${collection.name} ...`); @@ -211,13 +209,8 @@ const initCollection = async ({ all, databaseId } = {}) => { } const initBucket = async () => { - // TODO: Pagination? - let response = await storageListBuckets({ - queries: ['limit(100)'], - parseOutput: false - }) + const { buckets } = await paginate(storageListBuckets, { parseOutput: false }, 100, 'buckets'); - let buckets = response.buckets; log(`Found ${buckets.length} buckets`); buckets.forEach(async bucket => { @@ -229,13 +222,8 @@ const initBucket = async () => { } const initTeam = async () => { - // TODO: Pagination? - let response = await teamsList({ - queries: ['limit(100)'], - parseOutput: false - }) + const { teams } = await paginate(teamsList, { parseOutput: false }, 100, 'teams'); - let teams = response.teams; log(`Found ${teams.length} teams`); teams.forEach(async team => { diff --git a/lib/paginate.js b/lib/paginate.js new file mode 100644 index 0000000..309e2ef --- /dev/null +++ b/lib/paginate.js @@ -0,0 +1,51 @@ +const paginate = async (action, args = {}, limit = 100, wrapper = '') => { + let pageNumber = 0; + let results = []; + let total = 0; + + while (true) { + const offset = pageNumber * limit; + + // Merge the limit and offset into the args + const response = await action({ + ...args, + queries: [ + `limit(${limit})`, + `offset(${offset})` + ] + }); + + if (wrapper === '') { + if (response.length === 0) { + break; + } + results = results.concat(response); + } else { + if (response[wrapper].length === 0) { + break; + } + results = results.concat(response[wrapper]); + } + + total = response.total; + + if (results.length >= total) { + break; + } + + pageNumber++; + } + + if (wrapper === '') { + return results; + } + + return { + [wrapper]: results, + total, + }; +} + +module.exports = { + paginate +}; \ No newline at end of file diff --git a/lib/questions.js b/lib/questions.js index 54a6244..755fa3f 100644 --- a/lib/questions.js +++ b/lib/questions.js @@ -309,13 +309,12 @@ const questionsDeployCollections = [ if (collections.length === 0) { throw new Error("No collections found in the current directory. Run `appwrite init collection` to fetch all your collections."); } - let choices = collections.map((collection, idx) => { + return collections.map(collection => { return { name: `${collection.name} (${collection['databaseId']} - ${collection['$id']})`, value: `${collection['databaseId']}|${collection['$id']}` } - }) - return choices; + }); } }, { @@ -335,13 +334,12 @@ const questionsDeployBuckets = [ if (buckets.length === 0) { throw new Error("No buckets found in the current directory. Run `appwrite init bucket` to fetch all your buckets."); } - let choices = buckets.map((bucket, idx) => { + return buckets.map(bucket => { return { name: `${bucket.name} (${bucket['$id']})`, value: bucket.$id } - }) - return choices; + }); } }, { @@ -359,7 +357,7 @@ const questionsGetEntrypoint = [ default: null, validate(value) { if (!value) { - return "Please enter your enrtypoint"; + return "Please enter your entrypoint"; } return true; } @@ -376,13 +374,12 @@ const questionsDeployTeams = [ if (teams.length === 0) { throw new Error("No teams found in the current directory. Run `appwrite init team` to fetch all your teams."); } - let choices = teams.map((team, idx) => { + return teams.map(team => { return { name: `${team.name} (${team['$id']})`, value: team.$id } - }) - return choices; + }); } }, { diff --git a/package.json b/package.json index 0202a1a..021e1eb 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "appwrite-cli", "homepage": "https://appwrite.io/support", "description": "Appwrite is an open-source self-hosted backend server that abstract and simplify complex and repetitive development tasks behind a very simple REST API", - "version": "4.1.0", + "version": "4.2.0", "license": "BSD-3-Clause", "main": "index.js", "bin": { diff --git a/scoop/appwrite.json b/scoop/appwrite.json index 5f96b3f..83ef62f 100644 --- a/scoop/appwrite.json +++ b/scoop/appwrite.json @@ -1,12 +1,12 @@ { "$schema": "https://raw.githubusercontent.com/ScoopInstaller/Scoop/master/schema.json", - "version": "4.1.0", + "version": "4.2.0", "description": "The Appwrite CLI is a command-line application that allows you to interact with Appwrite and perform server-side tasks using your terminal.", "homepage": "https://github.com/appwrite/sdk-for-cli", "license": "BSD-3-Clause", "architecture": { "64bit": { - "url": "https://github.com/appwrite/sdk-for-cli/releases/download/4.1.0/appwrite-cli-win-x64.exe", + "url": "https://github.com/appwrite/sdk-for-cli/releases/download/4.2.0/appwrite-cli-win-x64.exe", "bin": [ [ "appwrite-cli-win-x64.exe", @@ -15,7 +15,7 @@ ] }, "arm64": { - "url": "https://github.com/appwrite/sdk-for-cli/releases/download/4.1.0/appwrite-cli-win-arm64.exe", + "url": "https://github.com/appwrite/sdk-for-cli/releases/download/4.2.0/appwrite-cli-win-arm64.exe", "bin": [ [ "appwrite-cli-win-arm64.exe",