diff --git a/CHANGELOG.md b/CHANGELOG.md index 099e71cb..6e9345fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Change Log +## 28.0.0 + +* Breaking: removed `account.createJWT`; use `users.createJWT` instead. A leaked JWT could mint further JWTs, letting a credential outlive its own expiry — a session cannot duplicate itself to live forever either +* Breaking: removed `project.createKey`. A leaked key could mint further hidden keys, making a compromise far harder to contain and revoke +* Breaking: `activities.listEvents` `queries` now takes an array instead of a string +* Added: `embeddings` service with `createTextEmbeddings`, plus the `EmbeddingModel` enum +* Added: TablesDB migration methods `listMigrations`, `createMigration`, `getMigration`, `deleteMigration`, `cutoverMigration`, and `listOperations` +* Added: `proxy.createInvalidation` for purging cached edge responses, plus the `InvalidationType` enum +* Added: `apps.deleteInstallation` +* Added: `users.getMFAChallenge` and the `MfaChallengeSecret` model +* Added: `project.updateMFAFactorsPolicy` and the `PolicyMfaFactors` model +* Added: `client.setOrganization` for organization-scoped requests +* Added: `folder` parameter to `storage.createFile` +* Added: `syncMode` parameter to `tablesDB.create` and `tablesDB.update`, and `specification` to `tablesDB.update` +* Added: `installationScopes` parameter to `project.updateOAuth2Server` +* Added: `custom` authentication factor, `node-26` runtime, `mfa-factors` project policy, and the `embeddings.write` and `proxy.invalidations.write` key scopes +* Updated: response format to `1.9.6` + ## 27.1.0 * Added: `Apps` service for managing OAuth2 applications, keys, and installations diff --git a/README.md b/README.md index a47ec4c5..c5c62c7b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Appwrite PHP SDK ![License](https://img.shields.io/github/license/appwrite/sdk-for-php.svg?style=flat-square&v=1) -![Version](https://img.shields.io/badge/api%20version-1.9.5-blue.svg?style=flat-square&v=1) +![Version](https://img.shields.io/badge/api%20version-1.9.6-blue.svg?style=flat-square&v=1) [![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/account.md b/docs/account.md index 7600b611..fcf89b5f 100644 --- a/docs/account.md +++ b/docs/account.md @@ -150,19 +150,6 @@ DELETE https://cloud.appwrite.io/v1/account/identities/{identityId} | identityId | string | **Required** Identity ID. | | -```http request -POST https://cloud.appwrite.io/v1/account/jwts -``` - -** Use this endpoint to create a JSON Web Token. You can use the resulting JWT to authenticate on behalf of the current user when working with the Appwrite server-side API and SDKs. The JWT secret is valid for 15 minutes from its creation and will be invalid if the user will logout in that time frame. ** - -### Parameters - -| Field Name | Type | Description | Default | -| --- | --- | --- | --- | -| duration | integer | Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds. | 900 | - - ```http request GET https://cloud.appwrite.io/v1/account/logs ``` @@ -280,7 +267,7 @@ POST https://cloud.appwrite.io/v1/account/mfa/challenges | Field Name | Type | Description | Default | | --- | --- | --- | --- | -| factor | string | Factor used for verification. Must be one of following: `email`, `phone`, `totp`, `recoveryCode`. | | +| factor | string | Factor used for verification. Must be one of following: `email`, `phone`, `totp`, `recoveryCode`, `custom`. | | ```http request @@ -293,7 +280,7 @@ POST https://cloud.appwrite.io/v1/account/mfa/challenges | Field Name | Type | Description | Default | | --- | --- | --- | --- | -| factor | string | Factor used for verification. Must be one of following: `email`, `phone`, `totp`, `recoveryCode`. | | +| factor | string | Factor used for verification. Must be one of following: `email`, `phone`, `totp`, `recoveryCode`, `custom`. | | ```http request @@ -561,7 +548,7 @@ GET https://cloud.appwrite.io/v1/account/sessions/{sessionId} | Field Name | Type | Description | Default | | --- | --- | --- | --- | -| sessionId | string | **Required** Session ID. Use the string 'current' to get the current device session. | | +| sessionId | string | **Required** Session ID. Use the string 'current' to get the current device session. | current | ```http request @@ -574,7 +561,7 @@ PATCH https://cloud.appwrite.io/v1/account/sessions/{sessionId} | Field Name | Type | Description | Default | | --- | --- | --- | --- | -| sessionId | string | **Required** Session ID. Use the string 'current' to update the current device session. | | +| sessionId | string | **Required** Session ID. Use the string 'current' to update the current device session. | current | ```http request @@ -587,7 +574,7 @@ DELETE https://cloud.appwrite.io/v1/account/sessions/{sessionId} | Field Name | Type | Description | Default | | --- | --- | --- | --- | -| sessionId | string | **Required** Session ID. Use the string 'current' to delete the current device session. | | +| sessionId | string | **Required** Session ID. Use the string 'current' to delete the current device session. | current | ```http request diff --git a/docs/activities.md b/docs/activities.md index 06b40cb6..cf0d0f6c 100644 --- a/docs/activities.md +++ b/docs/activities.md @@ -11,7 +11,7 @@ GET https://cloud.appwrite.io/v1/activities/events | Field Name | Type | Description | Default | | --- | --- | --- | --- | -| queries | string | Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on attributes such as userId, teamId, etc. | [] | +| queries | array | Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of 100 queries are allowed, each 4096 characters long. You may filter on attributes such as userId, teamId, etc. | [] | ```http request diff --git a/docs/apps.md b/docs/apps.md index a8d9a2e8..436275b2 100644 --- a/docs/apps.md +++ b/docs/apps.md @@ -70,7 +70,7 @@ GET https://cloud.appwrite.io/v1/apps/{appId} | Field Name | Type | Description | Default | | --- | --- | --- | --- | -| appId | string | **Required** Application unique ID or HTTPS client ID metadata document URL. | | +| appId | string | **Required** Application unique ID. | | ```http request @@ -101,7 +101,7 @@ PUT https://cloud.appwrite.io/v1/apps/{appId} | postLogoutRedirectUris | array | Post-logout redirect URIs for OpenID Connect RP-Initiated Logout. Each must be an https URL, an http loopback URL, or a private-use scheme URI, and must not contain a fragment. After ending the user session, the logout endpoint only redirects to URIs in this list. | [] | | type | string | OAuth2 client type. Use `public` for SPAs, mobile, and native apps that cannot keep a `client_secret` — PKCE is then required at the token endpoint. Use `confidential` for server-side clients that present a `client_secret`. Defaults to `confidential`. | confidential | | deviceFlow | boolean | Allow this client to use the OAuth2 Device Authorization Grant (RFC 8628) for input-constrained devices such as TVs and CLIs. Defaults to false. | | -| installationScopes | array | Scopes the application requests when installed on a team. Organization-level and project-level scopes only; use the list scopes endpoint with `type=installation` to discover available values. Maximum of 100 scopes are allowed. | [] | +| installationScopes | array | Scopes the application requests when installed on a team. Only scopes allowed by the project's OAuth2 server installation scopes configuration are accepted; use the list installation scopes endpoint to discover available values. Maximum of 100 scopes are allowed. | [] | | installationRedirectUrl | string | URL users are redirected to after creating or updating an installation of this application. Must be an https URL, an http loopback URL (localhost, 127.0.0.1, [::1]), or a private-use scheme URI, and must not contain a fragment. Leave empty for no redirect. | | @@ -122,7 +122,7 @@ DELETE https://cloud.appwrite.io/v1/apps/{appId} GET https://cloud.appwrite.io/v1/apps/{appId}/installations ``` -** List installations of an application. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header. ** +** List installations of an application. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header, or a caller with update access to the app. ** ### Parameters @@ -137,7 +137,21 @@ GET https://cloud.appwrite.io/v1/apps/{appId}/installations GET https://cloud.appwrite.io/v1/apps/{appId}/installations/{installationId} ``` -** Get an installation of an application by its unique ID. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header. ** +** Get an installation of an application by its unique ID. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header, or a caller with update access to the app. ** + +### Parameters + +| Field Name | Type | Description | Default | +| --- | --- | --- | --- | +| appId | string | **Required** Application unique ID. | | +| installationId | string | **Required** Installation unique ID. | | + + +```http request +DELETE https://cloud.appwrite.io/v1/apps/{appId}/installations/{installationId} +``` + +** Delete an installation of an application by its unique ID. Requires a caller with update access to the app. Previously issued installation access tokens are revoked. ** ### Parameters @@ -151,7 +165,7 @@ GET https://cloud.appwrite.io/v1/apps/{appId}/installations/{installationId} POST https://cloud.appwrite.io/v1/apps/{appId}/installations/{installationId}/tokens ``` -** Create a token for an installation of an application. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header. The returned token carries the scopes and authorization details granted to the installation, and can be used as an `Authorization: Bearer` header everywhere OAuth2 access tokens are accepted. Multiple tokens can be active for the same installation at once; each token stays valid until it expires or the installation is updated or deleted. ** +** Create a token for an installation of an application. Requires an app key sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header, or a caller with update access to the app. The returned token carries the scopes and authorization details granted to the installation, and can be used as an `Authorization: Bearer` header everywhere OAuth2 access tokens are accepted. Multiple tokens can be active for the same installation at once; each token stays valid until it expires or the installation is updated or deleted. ** ### Parameters diff --git a/docs/backups.md b/docs/backups.md index 3ba25e1d..24727348 100644 --- a/docs/backups.md +++ b/docs/backups.md @@ -135,13 +135,13 @@ POST https://cloud.appwrite.io/v1/backups/restoration ** Create and trigger a new restoration for a backup on a project. -For a backup of one database, the restoration resolves its destination before it is queued. Pass `newResourceId` to restore into that database ID, including the archived database ID to overwrite it. When `newResourceId` is omitted, a new database ID is generated and returned in `options`. +For a backup of one database, the restoration resolves its destination before it is queued. When `newResourceId` is omitted, the archived database is restored in place and its own ID is returned in `options`. Pass a different `newResourceId` to restore alongside it as a new database instead. The restoration migration records the archived database in `resourceId` and `resourceType`, and the resolved database in `destinationResourceId` and `destinationResourceType`. Database types are stored canonically as `database`, `documentsdb`, or `vectorsdb`. Project-wide restorations leave these fields empty because they do not have a single source or destination database. To list every migration related to one database, use its canonical type in a nested `OR(AND(...), AND(...), AND(...))` across the root, parent, and destination relation pairs: `(resourceType, resourceId)`, `(parentResourceType, parentResourceId)`, and `(destinationResourceType, destinationResourceId)`. Legacy and TablesDB databases use `database`; the operational `resourceType` of a table migration is not rewritten to `tablesdb`. -When restoring a DocumentsDB or VectorsDB database to a new resource from a dedicated source, the restore provisions a fresh dedicated backing database at the source database's own specification. +When restoring a DocumentsDB or VectorsDB database from a dedicated source, the restore provisions a fresh dedicated backing database at the source database's own specification and lands the data there. An in-place restore swaps the database onto that backing only once the restore has succeeded, and retires the backing it displaced only once that swap is confirmed, so the source keeps serving its own data until the restored data is in place and any failure leaves it untouched. A serverless source has no dedicated backing to clone and restores onto the archived database instead. ** ### Parameters @@ -150,7 +150,7 @@ When restoring a DocumentsDB or VectorsDB database to a new resource from a dedi | --- | --- | --- | --- | | archiveId | string | Backup archive ID to restore | | | services | array | Array of services to restore | | -| newResourceId | string | Destination resource ID. Omit to generate a new ID, or pass the archived resource ID to overwrite 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. | | +| newResourceId | string | Destination resource ID. Omit to restore the archived resource in place, or pass a different ID to restore alongside it as a new resource. 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. | | | newResourceName | string | Database name. Max length: 128 chars. | | diff --git a/docs/databases.md b/docs/databases.md index b3435f40..f976fef2 100644 --- a/docs/databases.md +++ b/docs/databases.md @@ -186,7 +186,7 @@ POST https://cloud.appwrite.io/v1/databases/{databaseId}/collections | permissions | array | An array of permissions strings. By default, no user is granted with any permissions. [Learn more about permissions](https://appwrite.io/docs/permissions). | | | documentSecurity | boolean | 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). | | | enabled | boolean | 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. | 1 | -| attributes | array | Array of attribute definitions to create. Each attribute should contain: key (string), type (string: string, integer, float, boolean, datetime), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options. | [] | +| attributes | array | Array of attribute definitions to create. Each attribute should contain: key (string), type (string: string, varchar, text, mediumtext, longtext, integer, bigint, double, boolean, datetime, point, linestring, polygon, email, url, ip, enum), size (integer, required for string and varchar types), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options. | [] | | indexes | array | Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of attribute keys), orders (array of ASC/DESC, optional), and lengths (array of integers, optional). | [] | @@ -765,11 +765,11 @@ POST https://cloud.appwrite.io/v1/databases/{databaseId}/collections/{collection | databaseId | string | **Required** Database ID. | | | collectionId | string | **Required** Collection ID. | | | relatedCollectionId | string | Related Collection ID. | | -| type | string | Relation type | | +| type | string | Relationship type. Possible values are: oneToOne, oneToMany, manyToOne, manyToMany. | | | twoWay | boolean | Is Two Way? | | | key | string | Attribute Key. | | | twoWayKey | string | Two Way Attribute Key. | | -| onDelete | string | Constraints option | restrict | +| onDelete | string | Delete constraint. Possible values are: cascade, restrict, setNull. | restrict | ```http request @@ -786,7 +786,7 @@ PATCH https://cloud.appwrite.io/v1/databases/{databaseId}/collections/{collectio | databaseId | string | **Required** Database ID. | | | collectionId | string | **Required** Collection ID. | | | key | string | **Required** Attribute Key. | | -| onDelete | string | Constraints option | | +| onDelete | string | Delete constraint. Possible values are: cascade, restrict, setNull. | | | newKey | string | New Attribute Key. | | diff --git a/docs/embeddings.md b/docs/embeddings.md new file mode 100644 index 00000000..50d53b9b --- /dev/null +++ b/docs/embeddings.md @@ -0,0 +1,17 @@ +# Embeddings Service + + +```http request +POST https://cloud.appwrite.io/v1/embeddings/text +``` + +** Generate vector embeddings for an array of text using the selected embedding model. Use the returned vectors to power semantic search and similarity queries against your vector collections. + ** + +### Parameters + +| Field Name | Type | Description | Default | +| --- | --- | --- | --- | +| texts | array | Array of text to generate embeddings. | | +| model | string | The embedding model to use for generating vector embeddings. | nomic-embed-text | + diff --git a/docs/examples/activities/list-events.md b/docs/examples/activities/list-events.md index ef9a9d70..cab59cfc 100644 --- a/docs/examples/activities/list-events.md +++ b/docs/examples/activities/list-events.md @@ -12,5 +12,5 @@ $client = (new Client()) $activities = new Activities($client); $result = $activities->listEvents( - queries: '' // optional + queries: [] // optional );``` diff --git a/docs/examples/apps/create-installation-token.md b/docs/examples/apps/create-installation-token.md index ef4ca380..33d117ae 100644 --- a/docs/examples/apps/create-installation-token.md +++ b/docs/examples/apps/create-installation-token.md @@ -7,7 +7,7 @@ use Appwrite\Services\Apps; $client = (new Client()) ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID - ->setKey(''); // Your secret API key + ->setSession(''); // The user session to authenticate with $apps = new Apps($client); diff --git a/docs/examples/account/create-jwt.md b/docs/examples/apps/delete-installation.md similarity index 63% rename from docs/examples/account/create-jwt.md rename to docs/examples/apps/delete-installation.md index 3314407a..e57a12f4 100644 --- a/docs/examples/account/create-jwt.md +++ b/docs/examples/apps/delete-installation.md @@ -2,15 +2,16 @@ setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID ->setSession(''); // The user session to authenticate with -$account = new Account($client); +$apps = new Apps($client); -$result = $account->createJWT( - duration: 0 // optional +$result = $apps->deleteInstallation( + appId: '', + installationId: '' );``` diff --git a/docs/examples/apps/get-installation.md b/docs/examples/apps/get-installation.md index 63de2616..0f7a9bac 100644 --- a/docs/examples/apps/get-installation.md +++ b/docs/examples/apps/get-installation.md @@ -7,7 +7,7 @@ use Appwrite\Services\Apps; $client = (new Client()) ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID - ->setKey(''); // Your secret API key + ->setSession(''); // The user session to authenticate with $apps = new Apps($client); diff --git a/docs/examples/apps/list-installations.md b/docs/examples/apps/list-installations.md index fbdff242..0209cd24 100644 --- a/docs/examples/apps/list-installations.md +++ b/docs/examples/apps/list-installations.md @@ -7,7 +7,7 @@ use Appwrite\Services\Apps; $client = (new Client()) ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint ->setProject('') // Your project ID - ->setKey(''); // Your secret API key + ->setSession(''); // The user session to authenticate with $apps = new Apps($client); diff --git a/docs/examples/embeddings/create-text-embeddings.md b/docs/examples/embeddings/create-text-embeddings.md new file mode 100644 index 00000000..15f92318 --- /dev/null +++ b/docs/examples/embeddings/create-text-embeddings.md @@ -0,0 +1,18 @@ +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$embeddings = new Embeddings($client); + +$result = $embeddings->createTextEmbeddings( + texts: [], + model: EmbeddingModel::NOMICEMBEDTEXT() // optional +);``` diff --git a/docs/examples/project/create-key.md b/docs/examples/project/update-mfa-factors-policy.md similarity index 60% rename from docs/examples/project/create-key.md rename to docs/examples/project/update-mfa-factors-policy.md index cd3dbfd9..8823cc8f 100644 --- a/docs/examples/project/create-key.md +++ b/docs/examples/project/update-mfa-factors-policy.md @@ -3,7 +3,6 @@ use Appwrite\Client; use Appwrite\Services\Project; -use Appwrite\Enums\ProjectKeyScopes; $client = (new Client()) ->setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint @@ -12,9 +11,9 @@ $client = (new Client()) $project = new Project($client); -$result = $project->createKey( - keyId: '', - name: '', - scopes: [ProjectKeyScopes::PROJECTREAD()], - expire: '2020-10-15T06:38:00.000+00:00' // optional +$result = $project->updateMFAFactorsPolicy( + totp: false, // optional + email: false, // optional + phone: false, // optional + custom: false // optional );``` diff --git a/docs/examples/project/update-o-auth-2-server.md b/docs/examples/project/update-o-auth-2-server.md index 4a7782f2..1d06279a 100644 --- a/docs/examples/project/update-o-auth-2-server.md +++ b/docs/examples/project/update-o-auth-2-server.md @@ -26,5 +26,6 @@ $result = $project->updateOAuth2Server( userCodeLength: 6, // optional userCodeFormat: 'numeric', // optional deviceCodeDuration: 60, // optional - defaultScopes: [] // optional + defaultScopes: [], // optional + installationScopes: [] // optional );``` diff --git a/docs/examples/project/update-session-duration-policy.md b/docs/examples/project/update-session-duration-policy.md index 89526e79..882e2b2f 100644 --- a/docs/examples/project/update-session-duration-policy.md +++ b/docs/examples/project/update-session-duration-policy.md @@ -12,5 +12,5 @@ $client = (new Client()) $project = new Project($client); $result = $project->updateSessionDurationPolicy( - duration: 5 + duration: 60 );``` diff --git a/docs/examples/project/update-user-limit-policy.md b/docs/examples/project/update-user-limit-policy.md index bb787e9e..0dc2f1f7 100644 --- a/docs/examples/project/update-user-limit-policy.md +++ b/docs/examples/project/update-user-limit-policy.md @@ -12,5 +12,5 @@ $client = (new Client()) $project = new Project($client); $result = $project->updateUserLimitPolicy( - total: 1 + total: 0 );``` diff --git a/docs/examples/proxy/create-invalidation.md b/docs/examples/proxy/create-invalidation.md new file mode 100644 index 00000000..00ae00ad --- /dev/null +++ b/docs/examples/proxy/create-invalidation.md @@ -0,0 +1,19 @@ +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$proxy = new Proxy($client); + +$result = $proxy->createInvalidation( + domain: '', + type: InvalidationType::TAG(), + reference: '' // optional +);``` diff --git a/docs/examples/storage/create-file.md b/docs/examples/storage/create-file.md index 62301b10..19c38797 100644 --- a/docs/examples/storage/create-file.md +++ b/docs/examples/storage/create-file.md @@ -18,5 +18,6 @@ $result = $storage->createFile( bucketId: '', fileId: '', file: InputFile::withPath('file.png'), - permissions: [Permission::read(Role::any())] // optional + permissions: [Permission::read(Role::any())], // optional + folder: '' // optional );``` diff --git a/docs/examples/tablesdb/create-migration.md b/docs/examples/tablesdb/create-migration.md new file mode 100644 index 00000000..37f670fe --- /dev/null +++ b/docs/examples/tablesdb/create-migration.md @@ -0,0 +1,18 @@ +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$tablesDB = new TablesDB($client); + +$result = $tablesDB->createMigration( + databaseId: '', + specification: 's-1vcpu-1gb', + autoCutover: false // optional +);``` diff --git a/docs/examples/tablesdb/create.md b/docs/examples/tablesdb/create.md index 87c81dd2..7df1d49a 100644 --- a/docs/examples/tablesdb/create.md +++ b/docs/examples/tablesdb/create.md @@ -16,5 +16,6 @@ $result = $tablesDB->create( name: '', enabled: false, // optional specification: 'serverless', // optional - replicas: 0 // optional + replicas: 0, // optional + syncMode: 'async' // optional );``` diff --git a/docs/examples/tablesdb/cutover-migration.md b/docs/examples/tablesdb/cutover-migration.md new file mode 100644 index 00000000..71a37d33 --- /dev/null +++ b/docs/examples/tablesdb/cutover-migration.md @@ -0,0 +1,17 @@ +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$tablesDB = new TablesDB($client); + +$result = $tablesDB->cutoverMigration( + databaseId: '', + migrationId: '' +);``` diff --git a/docs/examples/tablesdb/delete-migration.md b/docs/examples/tablesdb/delete-migration.md new file mode 100644 index 00000000..255d6aa8 --- /dev/null +++ b/docs/examples/tablesdb/delete-migration.md @@ -0,0 +1,17 @@ +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$tablesDB = new TablesDB($client); + +$result = $tablesDB->deleteMigration( + databaseId: '', + migrationId: '' +);``` diff --git a/docs/examples/tablesdb/get-migration.md b/docs/examples/tablesdb/get-migration.md new file mode 100644 index 00000000..88d922b1 --- /dev/null +++ b/docs/examples/tablesdb/get-migration.md @@ -0,0 +1,17 @@ +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$tablesDB = new TablesDB($client); + +$result = $tablesDB->getMigration( + databaseId: '', + migrationId: '' +);``` diff --git a/docs/examples/tablesdb/list-migrations.md b/docs/examples/tablesdb/list-migrations.md new file mode 100644 index 00000000..20c8485c --- /dev/null +++ b/docs/examples/tablesdb/list-migrations.md @@ -0,0 +1,16 @@ +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$tablesDB = new TablesDB($client); + +$result = $tablesDB->listMigrations( + databaseId: '' +);``` diff --git a/docs/examples/tablesdb/list-operations.md b/docs/examples/tablesdb/list-operations.md new file mode 100644 index 00000000..311b1464 --- /dev/null +++ b/docs/examples/tablesdb/list-operations.md @@ -0,0 +1,19 @@ +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$tablesDB = new TablesDB($client); + +$result = $tablesDB->listOperations( + databaseId: '', + status: 'running', // optional + limit: 1, // optional + offset: 0 // optional +);``` diff --git a/docs/examples/tablesdb/update.md b/docs/examples/tablesdb/update.md index e6c703af..c5bafb8d 100644 --- a/docs/examples/tablesdb/update.md +++ b/docs/examples/tablesdb/update.md @@ -15,5 +15,7 @@ $result = $tablesDB->update( databaseId: '', name: '', // optional enabled: false, // optional - replicas: 0 // optional + specification: 'serverless', // optional + replicas: 0, // optional + syncMode: 'async' // optional );``` diff --git a/docs/examples/users/get-mfa-challenge.md b/docs/examples/users/get-mfa-challenge.md new file mode 100644 index 00000000..8ef5c4aa --- /dev/null +++ b/docs/examples/users/get-mfa-challenge.md @@ -0,0 +1,17 @@ +```php +setEndpoint('https://.cloud.appwrite.io/v1') // Your API Endpoint + ->setProject('') // Your project ID + ->setKey(''); // Your secret API key + +$users = new Users($client); + +$result = $users->getMFAChallenge( + userId: '', + challengeId: '' +);``` diff --git a/docs/functions.md b/docs/functions.md index eb12a051..bc4a62ed 100644 --- a/docs/functions.md +++ b/docs/functions.md @@ -67,7 +67,7 @@ GET https://cloud.appwrite.io/v1/functions/specifications | Field Name | Type | Description | Default | | --- | --- | --- | --- | -| type | string | Specification type to list. Can be one of: runtimes, builds. | runtimes | +| type | string | Specification type to list. Can be one of: runtimes, builds. Defaults to runtimes. | runtimes | ```http request diff --git a/docs/project.md b/docs/project.md index c98ef4bc..39a21349 100644 --- a/docs/project.md +++ b/docs/project.md @@ -43,24 +43,6 @@ GET https://cloud.appwrite.io/v1/project/keys | total | boolean | When set to false, the total count returned will be 0 and will not be calculated. | 1 | -```http request -POST https://cloud.appwrite.io/v1/project/keys -``` - -** Create a new API key. It's recommended to have multiple API keys with strict scopes for separate functions within your project. - -You can also create an ephemeral API key if you need a short-lived key instead. ** - -### Parameters - -| Field Name | Type | Description | Default | -| --- | --- | --- | --- | -| keyId | string | Key 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. | | -| name | string | Key name. Max length: 128 chars. | | -| scopes | array | Key scopes list. Maximum of 200 scopes are allowed. | | -| expire | string | Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration. | | - - ```http request POST https://cloud.appwrite.io/v1/project/keys/ephemeral ``` @@ -239,6 +221,7 @@ PUT https://cloud.appwrite.io/v1/project/oauth2-server | userCodeFormat | string | Character set for device flow user codes: `numeric` (digits only — best for numeric keypads and TV remotes), `alphabetic` (letters only), or `alphanumeric` (letters and digits — highest entropy per character). Defaults to `alphanumeric`. | alphanumeric | | deviceCodeDuration | integer | Lifetime in seconds of device flow device codes and user codes. Device codes are intentionally short-lived. Leave empty to use default 600. | | | defaultScopes | array | List of OAuth2 scopes used when an authorization request omits the scope parameter. Every default scope must also be allowed by the OAuth2 server. Maximum of 100 scopes are allowed, each up to 128 characters long. | [] | +| installationScopes | array | List of scopes an application may request when installed on a team. Omitting the parameter clears the list, so no installation scopes can be granted. Maximum of 100 scopes are allowed, each up to 128 characters long. | [] | ```http request @@ -1191,6 +1174,22 @@ PATCH https://cloud.appwrite.io/v1/project/policies/membership-privacy | userAccessedAt | boolean | Set to true if you want make user last access time visible to all team members, or false to hide it. | | +```http request +PATCH https://cloud.appwrite.io/v1/project/policies/mfa-factors +``` + +** Updating this policy allows you to control which factors users can use to complete an MFA challenge. Disabled factors cannot be used to create a challenge and are reported as unavailable when listing factors. The custom factor is disabled by default; enable it to deliver challenge codes through your own channel. Recovery codes always remain available as a fallback. ** + +### Parameters + +| Field Name | Type | Description | Default | +| --- | --- | --- | --- | +| totp | boolean | Set to true to allow TOTP to complete an MFA challenge, or false to disable it. | | +| email | boolean | Set to true to allow email to complete an MFA challenge, or false to disable it. | | +| phone | boolean | Set to true to allow phone (SMS) to complete an MFA challenge, or false to disable it. | | +| custom | boolean | Set to true to allow the custom factor to complete an MFA challenge, or false to disable it. | | + + ```http request PATCH https://cloud.appwrite.io/v1/project/policies/password-dictionary ``` @@ -1216,7 +1215,7 @@ Keep in mind, while password history policy is disabled, the history is not bein | Field Name | Type | Description | Default | | --- | --- | --- | --- | -| total | integer | Set the password history length per user. Value can be between 1 and 5000, or null to disable the limit. | | +| total | integer | Set the password history length per user. Value can be between 1 and 20, or null to disable the limit. | | ```http request @@ -1272,7 +1271,7 @@ PATCH https://cloud.appwrite.io/v1/project/policies/session-duration | Field Name | Type | Description | Default | | --- | --- | --- | --- | -| duration | integer | Maximum session length in seconds. Minium allowed value is 5 second, and maximum is 1 year, which is 31536000 seconds. | | +| duration | integer | Maximum session length in seconds. Minium allowed value is 60 seconds, and maximum is 1 year, which is 31536000 seconds. | | ```http request @@ -1298,7 +1297,7 @@ PATCH https://cloud.appwrite.io/v1/project/policies/session-limit | Field Name | Type | Description | Default | | --- | --- | --- | --- | -| total | integer | Set the maximum number of sessions allowed per user. Value can be between 1 and 5000, or null to disable the limit. | | +| total | integer | Set the maximum number of sessions allowed per user. Value can be between 1 and 100. | | ```http request @@ -1311,7 +1310,7 @@ PATCH https://cloud.appwrite.io/v1/project/policies/user-limit | Field Name | Type | Description | Default | | --- | --- | --- | --- | -| total | integer | Set the maximum number of users allowed in the project. Value can be between 1 and 5000, or null to disable the limit. | | +| total | integer | Set the maximum number of users allowed in the project. Value can be between 0 and 10000. Use 0 or null to disable the limit. | | ```http request @@ -1324,7 +1323,7 @@ GET https://cloud.appwrite.io/v1/project/policies/{policyId} | Field Name | Type | Description | Default | | --- | --- | --- | --- | -| policyId | string | **Required** Policy ID. Can be one of: password-dictionary, password-history, password-strength, password-personal-data, session-alert, session-duration, session-invalidation, session-limit, user-limit, membership-privacy, deny-aliased-email, deny-disposable-email, deny-free-email, deny-corporate-email. | | +| policyId | string | **Required** Policy ID. Can be one of: password-dictionary, password-history, password-strength, password-personal-data, session-alert, session-duration, session-invalidation, session-limit, user-limit, membership-privacy, mfa-factors, deny-aliased-email, deny-disposable-email, deny-free-email, deny-corporate-email. | | ```http request diff --git a/docs/proxy.md b/docs/proxy.md index 3c8d9b51..b429411d 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -1,6 +1,23 @@ # Proxy Service +```http request +POST https://cloud.appwrite.io/v1/proxy/invalidations +``` + +** Create a new CDN cache invalidation for a domain. Executes a hard purge of cached content. + +Depending on type, the invalidation purges a single cache tag, a single URL path, or all cached content for the domain. ** + +### Parameters + +| Field Name | Type | Description | Default | +| --- | --- | --- | --- | +| domain | string | Domain name. | | +| type | string | Type of reference passed. Allowed values are: tag, path, all | | +| reference | string | Reference to invalidate. Depending on type this can be: cache tag name (up to 128 characters), URL path (up to 2048 characters). Not required when type is all. | | + + ```http request GET https://cloud.appwrite.io/v1/proxy/rules ``` diff --git a/docs/sites.md b/docs/sites.md index 68f9b7d2..dac7de92 100644 --- a/docs/sites.md +++ b/docs/sites.md @@ -68,7 +68,7 @@ GET https://cloud.appwrite.io/v1/sites/specifications | Field Name | Type | Description | Default | | --- | --- | --- | --- | -| type | string | Specification type to list. Can be one of: runtimes, builds. | runtimes | +| type | string | Specification type to list. Can be one of: runtimes, builds. Defaults to runtimes. | runtimes | ```http request diff --git a/docs/storage.md b/docs/storage.md index c051c9b5..86acceb0 100644 --- a/docs/storage.md +++ b/docs/storage.md @@ -99,7 +99,7 @@ GET https://cloud.appwrite.io/v1/storage/buckets/{bucketId}/files | Field Name | Type | Description | Default | | --- | --- | --- | --- | | bucketId | string | **Required** Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket). | | -| queries | array | 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 | [] | +| queries | array | 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, folder, signature, mimeType, sizeOriginal, chunksTotal, chunksUploaded | [] | | search | string | Search term to filter your list results. Max length: 256 chars. | | | total | boolean | When set to false, the total count returned will be 0 and will not be calculated. | 1 | @@ -125,6 +125,7 @@ If you're creating a new file using one of the Appwrite SDKs, all the chunk | fileId | string | 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. | | | file | file | Binary file. Appwrite SDKs provide helpers to handle file input. [Learn about file input](https://appwrite.io/docs/products/storage/upload-download#input-file). | | | permissions | array | An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions). | | +| folder | string | Virtual folder to place the file in, for example "photos/2026". Nest folders with `/`. Defaults to the bucket root. | | ```http request diff --git a/docs/tablesdb.md b/docs/tablesdb.md index eefcfcde..49f78e2f 100644 --- a/docs/tablesdb.md +++ b/docs/tablesdb.md @@ -32,6 +32,7 @@ POST https://cloud.appwrite.io/v1/tablesdb | enabled | boolean | Is the database enabled? When set to 'disabled', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled. | 1 | | specification | string | Database specification. Defaults to `serverless`, which creates the database on the shared pool. Any other value provisions a dedicated database on that specification. | serverless | | replicas | integer | Number of high availability replicas (0-5) for the dedicated database backing this database. Requires a dedicated `specification`; must be 0 for a serverless database. High availability is enabled when greater than 0. | 0 | +| syncMode | string | Replication sync mode for the dedicated database backing this database. Requires a dedicated `specification`; the mode is only in force once there is at least one replica. Allowed values: async, sync, quorum. | | ```http request @@ -148,7 +149,9 @@ PUT https://cloud.appwrite.io/v1/tablesdb/{databaseId} | databaseId | string | **Required** Database ID. | | | name | string | Database name. Max length: 128 chars. | | | enabled | boolean | Is database enabled? When set to 'disabled', users cannot access the database but Server SDKs with an API key can still read and write to the database. No data is lost when this is toggled. | 1 | +| specification | string | Database specification. Resizing between dedicated specifications changes cpu, memory, storage and the connection ceiling via a rolling cutover with zero downtime. Moving a `serverless` database onto a dedicated specification is a data migration, not a resize. | | | replicas | integer | Number of high availability replicas (0-5) for the dedicated database backing this database. Only valid when the database is backed by a dedicated specification. High availability is enabled when greater than 0. | | +| syncMode | string | Replication sync mode for the dedicated database backing this database. Only valid when the database is backed by a dedicated specification; the mode is only in force once there is at least one replica. Allowed values: async, sync, quorum. | | ```http request @@ -168,7 +171,7 @@ DELETE https://cloud.appwrite.io/v1/tablesdb/{databaseId} POST https://cloud.appwrite.io/v1/tablesdb/{databaseId}/failovers ``` -** Trigger a manual failover for a dedicated database with high availability enabled. Promotes a replica to primary. The failover runs asynchronously; poll the database document for status updates. ** +** Trigger a manual failover for a dedicated database with high availability enabled. Promotes a replica to primary. The failover runs asynchronously; poll the database document for status updates. A database left mid-operation by a failover that did not finish also accepts this call as a repair, provided `targetReplicaId` names the member to promote. ** ### Parameters @@ -178,6 +181,92 @@ POST https://cloud.appwrite.io/v1/tablesdb/{databaseId}/failovers | targetReplicaId | string | Target replica ID to promote. If not specified, the healthiest replica is selected. | | +```http request +GET https://cloud.appwrite.io/v1/tablesdb/{databaseId}/migrations +``` + +** List the dedicated migrations for a TablesDB database. A database has at most one in-flight migration. ** + +### Parameters + +| Field Name | Type | Description | Default | +| --- | --- | --- | --- | +| databaseId | string | **Required** Database ID. | | + + +```http request +POST https://cloud.appwrite.io/v1/tablesdb/{databaseId}/migrations +``` + +** Start migrating a serverless TablesDB database onto a dedicated MySQL compute. Data is copied to the target while the source stays live, with a brief read-only window during cutover. ** + +### Parameters + +| Field Name | Type | Description | Default | +| --- | --- | --- | --- | +| databaseId | string | **Required** Database ID. | | +| specification | string | Dedicated compute specification to provision as the migration target (e.g. s-2vcpu-4gb). The migration always targets a dedicated compute, so `serverless` is not accepted. | | +| autoCutover | boolean | Whether to cut over automatically once the copy is verified. When disabled the migration parks at ready_to_cutover and holds there until the cutover is performed manually. | 1 | + + +```http request +GET https://cloud.appwrite.io/v1/tablesdb/{databaseId}/migrations/{migrationId} +``` + +** Get a single dedicated migration for a TablesDB database by its ID. ** + +### Parameters + +| Field Name | Type | Description | Default | +| --- | --- | --- | --- | +| databaseId | string | **Required** Database ID. | | +| migrationId | string | **Required** Migration ID. | | + + +```http request +DELETE https://cloud.appwrite.io/v1/tablesdb/{databaseId}/migrations/{migrationId} +``` + +** Abort an in-flight TablesDB dedicated migration. Only allowed before cutover; once the migration has cut over it cannot be aborted. ** + +### Parameters + +| Field Name | Type | Description | Default | +| --- | --- | --- | --- | +| databaseId | string | **Required** Database ID. | | +| migrationId | string | **Required** Migration ID. | | + + +```http request +POST https://cloud.appwrite.io/v1/tablesdb/{databaseId}/migrations/{migrationId}/cutover +``` + +** Cut a verified TablesDB migration over to its dedicated compute. Only applies to a migration created with `autoCutover` disabled, which waits at `ready_to_cutover` until this is called. The routing flip happens shortly after this returns, with a brief read-only window. One call buys one attempt: a cutover that fails a check returns the migration to `verifying` and parks it again, so call this once more to retry. ** + +### Parameters + +| Field Name | Type | Description | Default | +| --- | --- | --- | --- | +| databaseId | string | **Required** Database ID. | | +| migrationId | string | **Required** Migration ID. | | + + +```http request +GET https://cloud.appwrite.io/v1/tablesdb/{databaseId}/operations +``` + +** List the lifecycle operations recorded for a dedicated database, newest first. Every provision, update, restore, backup and replication action is recorded here with its outcome, including an attempt that was abandoned because another worker took over the database. ** + +### Parameters + +| Field Name | Type | Description | Default | +| --- | --- | --- | --- | +| databaseId | string | **Required** Database ID. | | +| status | string | Filter by operation status. | | +| limit | integer | Maximum number of operations to return. | 25 | +| offset | integer | Number of operations to skip. | 0 | + + ```http request GET https://cloud.appwrite.io/v1/tablesdb/{databaseId}/replicas ``` @@ -236,7 +325,7 @@ POST https://cloud.appwrite.io/v1/tablesdb/{databaseId}/tables | permissions | array | An array of permissions strings. By default, no user is granted with any permissions. [Learn more about permissions](https://appwrite.io/docs/permissions). | | | rowSecurity | boolean | Enables configuring permissions for individual rows. A user needs one of row or table level permissions to access a row. [Learn more about permissions](https://appwrite.io/docs/permissions). | | | enabled | boolean | Is table enabled? When set to 'disabled', users cannot access the table but Server SDKs with and API key can still read and write to the table. No data is lost when this is toggled. | 1 | -| columns | array | Array of column definitions to create. Each column should contain: key (string), type (string: string, integer, float, boolean, datetime, relationship), size (integer, required for string type), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options. | [] | +| columns | array | Array of column definitions to create. Each column should contain: key (string), type (string: string, varchar, text, mediumtext, longtext, integer, bigint, double, boolean, datetime, point, linestring, polygon, email, url, ip, enum), size (integer, required for string and varchar types), required (boolean, optional), default (mixed, optional), array (boolean, optional), and type-specific options. | [] | | indexes | array | Array of index definitions to create. Each index should contain: key (string), type (string: key, fulltext, unique, spatial), attributes (array of column keys), orders (array of ASC/DESC, optional), and lengths (array of integers, optional). | [] | @@ -814,11 +903,11 @@ POST https://cloud.appwrite.io/v1/tablesdb/{databaseId}/tables/{tableId}/columns | databaseId | string | **Required** Database ID. | | | tableId | string | **Required** Table ID. | | | relatedTableId | string | Related Table ID. | | -| type | string | Relation type | | +| type | string | Relationship type. Possible values are: oneToOne, oneToMany, manyToOne, manyToMany. | | | twoWay | boolean | Is Two Way? | | | key | string | Column Key. | | | twoWayKey | string | Two Way Column Key. | | -| onDelete | string | Constraints option | restrict | +| onDelete | string | Delete constraint. Possible values are: cascade, restrict, setNull. | restrict | ```http request @@ -1024,7 +1113,7 @@ PATCH https://cloud.appwrite.io/v1/tablesdb/{databaseId}/tables/{tableId}/column | databaseId | string | **Required** Database ID. | | | tableId | string | **Required** Table ID. | | | key | string | **Required** Column Key. | | -| onDelete | string | Constraints option | | +| onDelete | string | Delete constraint. Possible values are: cascade, restrict, setNull. | | | newKey | string | New Column Key. | | diff --git a/docs/users.md b/docs/users.md index 02d1d014..7ded8472 100644 --- a/docs/users.md +++ b/docs/users.md @@ -248,7 +248,7 @@ POST https://cloud.appwrite.io/v1/users/{userId}/jwts | Field Name | Type | Description | Default | | --- | --- | --- | --- | | userId | string | **Required** User ID. | | -| sessionId | string | Session ID. Use the string 'recent' to use the most recent session. Defaults to the most recent session. | | +| sessionId | string | Session ID. Use the string 'recent' to use the most recent session. Defaults to the most recent session. | recent | | duration | integer | Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds. | 900 | @@ -355,6 +355,20 @@ DELETE https://cloud.appwrite.io/v1/users/{userId}/mfa/authenticators/{type} | type | string | **Required** Type of authenticator. | | +```http request +GET https://cloud.appwrite.io/v1/users/{userId}/mfa/challenges/{challengeId} +``` + +** Get a custom MFA challenge for a user, including the code to be delivered through your own channel. ** + +### Parameters + +| Field Name | Type | Description | Default | +| --- | --- | --- | --- | +| userId | string | **Required** User ID. | | +| challengeId | string | **Required** ID of the challenge. | | + + ```http request GET https://cloud.appwrite.io/v1/users/{userId}/mfa/factors ``` diff --git a/src/Appwrite/Client.php b/src/Appwrite/Client.php index 23ee27b6..e56b2adc 100644 --- a/src/Appwrite/Client.php +++ b/src/Appwrite/Client.php @@ -40,11 +40,11 @@ class Client */ protected array $headers = [ 'content-type' => '', - 'user-agent' => 'AppwritePHPSDK/27.1.0 ()', + 'user-agent' => 'AppwritePHPSDK/28.0.0 ()', 'x-sdk-name'=> 'PHP', 'x-sdk-platform'=> 'server', 'x-sdk-language'=> 'php', - 'x-sdk-version'=> '27.1.0', + 'x-sdk-version'=> '28.0.0', ]; /** @@ -94,7 +94,7 @@ class Client */ public function __construct() { - $this->headers['X-Appwrite-Response-Format'] = '1.9.5'; + $this->headers['X-Appwrite-Response-Format'] = '1.9.6'; } @@ -131,6 +131,23 @@ public function setKey(string $value): Client return $this; } + /** + * Set Organization + * + * Your organization ID + * + * @param string $value + * + * @return Client + */ + public function setOrganization(string $value): Client + { + $this->addHeader('X-Appwrite-Organization', $value); + $this->config['organization'] = $value; + + return $this; + } + /** * Set JWT * diff --git a/src/Appwrite/Enums/AuthenticationFactor.php b/src/Appwrite/Enums/AuthenticationFactor.php index b3694722..046cf53f 100644 --- a/src/Appwrite/Enums/AuthenticationFactor.php +++ b/src/Appwrite/Enums/AuthenticationFactor.php @@ -10,6 +10,7 @@ class AuthenticationFactor implements JsonSerializable private static AuthenticationFactor $PHONE; private static AuthenticationFactor $TOTP; private static AuthenticationFactor $RECOVERYCODE; + private static AuthenticationFactor $CUSTOM; private string $value; @@ -56,6 +57,13 @@ public static function RECOVERYCODE(): AuthenticationFactor } return self::$RECOVERYCODE; } + public static function CUSTOM(): AuthenticationFactor + { + if (!isset(self::$CUSTOM)) { + self::$CUSTOM = new AuthenticationFactor('custom'); + } + return self::$CUSTOM; + } public static function from(string $value): self { @@ -64,6 +72,7 @@ public static function from(string $value): self 'phone' => self::PHONE(), 'totp' => self::TOTP(), 'recoverycode' => self::RECOVERYCODE(), + 'custom' => self::CUSTOM(), default => throw new \InvalidArgumentException('Unknown AuthenticationFactor value: ' . $value), }; } diff --git a/src/Appwrite/Enums/BuildRuntime.php b/src/Appwrite/Enums/BuildRuntime.php index 186b4de3..ec65bb79 100644 --- a/src/Appwrite/Enums/BuildRuntime.php +++ b/src/Appwrite/Enums/BuildRuntime.php @@ -16,6 +16,7 @@ class BuildRuntime implements JsonSerializable private static BuildRuntime $NODE23; private static BuildRuntime $NODE24; private static BuildRuntime $NODE25; + private static BuildRuntime $NODE26; private static BuildRuntime $PHP80; private static BuildRuntime $PHP81; private static BuildRuntime $PHP82; @@ -185,6 +186,13 @@ public static function NODE25(): BuildRuntime } return self::$NODE25; } + public static function NODE26(): BuildRuntime + { + if (!isset(self::$NODE26)) { + self::$NODE26 = new BuildRuntime('node-26'); + } + return self::$NODE26; + } public static function PHP80(): BuildRuntime { if (!isset(self::$PHP80)) { @@ -766,6 +774,7 @@ public static function from(string $value): self 'node-23' => self::NODE23(), 'node-24' => self::NODE24(), 'node-25' => self::NODE25(), + 'node-26' => self::NODE26(), 'php-8.0' => self::PHP80(), 'php-8.1' => self::PHP81(), 'php-8.2' => self::PHP82(), diff --git a/src/Appwrite/Enums/EmbeddingModel.php b/src/Appwrite/Enums/EmbeddingModel.php new file mode 100644 index 00000000..ec104ce9 --- /dev/null +++ b/src/Appwrite/Enums/EmbeddingModel.php @@ -0,0 +1,70 @@ +value = $value; + } + + public function __toString(): string + { + return $this->value; + } + + public function jsonSerialize(): string + { + return $this->value; + } + + public static function NOMICEMBEDTEXT(): EmbeddingModel + { + if (!isset(self::$NOMICEMBEDTEXT)) { + self::$NOMICEMBEDTEXT = new EmbeddingModel('nomic-embed-text'); + } + return self::$NOMICEMBEDTEXT; + } + public static function EMBEDDINGGEMMA(): EmbeddingModel + { + if (!isset(self::$EMBEDDINGGEMMA)) { + self::$EMBEDDINGGEMMA = new EmbeddingModel('embedding-gemma'); + } + return self::$EMBEDDINGGEMMA; + } + public static function ALLMINILM(): EmbeddingModel + { + if (!isset(self::$ALLMINILM)) { + self::$ALLMINILM = new EmbeddingModel('all-minilm'); + } + return self::$ALLMINILM; + } + public static function BGESMALL(): EmbeddingModel + { + if (!isset(self::$BGESMALL)) { + self::$BGESMALL = new EmbeddingModel('bge-small'); + } + return self::$BGESMALL; + } + + public static function from(string $value): self + { + return match ($value) { + 'nomic-embed-text' => self::NOMICEMBEDTEXT(), + 'embedding-gemma' => self::EMBEDDINGGEMMA(), + 'all-minilm' => self::ALLMINILM(), + 'bge-small' => self::BGESMALL(), + default => throw new \InvalidArgumentException('Unknown EmbeddingModel value: ' . $value), + }; + } +} diff --git a/src/Appwrite/Enums/InvalidationType.php b/src/Appwrite/Enums/InvalidationType.php new file mode 100644 index 00000000..828bd90d --- /dev/null +++ b/src/Appwrite/Enums/InvalidationType.php @@ -0,0 +1,61 @@ +value = $value; + } + + public function __toString(): string + { + return $this->value; + } + + public function jsonSerialize(): string + { + return $this->value; + } + + public static function TAG(): InvalidationType + { + if (!isset(self::$TAG)) { + self::$TAG = new InvalidationType('tag'); + } + return self::$TAG; + } + public static function PATH(): InvalidationType + { + if (!isset(self::$PATH)) { + self::$PATH = new InvalidationType('path'); + } + return self::$PATH; + } + public static function ALL(): InvalidationType + { + if (!isset(self::$ALL)) { + self::$ALL = new InvalidationType('all'); + } + return self::$ALL; + } + + public static function from(string $value): self + { + return match ($value) { + 'tag' => self::TAG(), + 'path' => self::PATH(), + 'all' => self::ALL(), + default => throw new \InvalidArgumentException('Unknown InvalidationType value: ' . $value), + }; + } +} diff --git a/src/Appwrite/Enums/ProjectKeyScopes.php b/src/Appwrite/Enums/ProjectKeyScopes.php index b7d37a76..a0c12987 100644 --- a/src/Appwrite/Enums/ProjectKeyScopes.php +++ b/src/Appwrite/Enums/ProjectKeyScopes.php @@ -40,6 +40,7 @@ class ProjectKeyScopes implements JsonSerializable private static ProjectKeyScopes $INDEXESWRITE; private static ProjectKeyScopes $ROWSREAD; private static ProjectKeyScopes $ROWSWRITE; + private static ProjectKeyScopes $EMBEDDINGSWRITE; private static ProjectKeyScopes $COLLECTIONSREAD; private static ProjectKeyScopes $COLLECTIONSWRITE; private static ProjectKeyScopes $ATTRIBUTESREAD; @@ -104,6 +105,7 @@ class ProjectKeyScopes implements JsonSerializable private static ProjectKeyScopes $WAFRULESREAD; private static ProjectKeyScopes $WAFRULESWRITE; private static ProjectKeyScopes $EVENTSREAD; + private static ProjectKeyScopes $PROXYINVALIDATIONSWRITE; private static ProjectKeyScopes $APPSREAD; private static ProjectKeyScopes $APPSWRITE; private static ProjectKeyScopes $OAUTH2READ; @@ -366,6 +368,13 @@ public static function ROWSWRITE(): ProjectKeyScopes } return self::$ROWSWRITE; } + public static function EMBEDDINGSWRITE(): ProjectKeyScopes + { + if (!isset(self::$EMBEDDINGSWRITE)) { + self::$EMBEDDINGSWRITE = new ProjectKeyScopes('embeddings.write'); + } + return self::$EMBEDDINGSWRITE; + } public static function COLLECTIONSREAD(): ProjectKeyScopes { if (!isset(self::$COLLECTIONSREAD)) { @@ -814,6 +823,13 @@ public static function EVENTSREAD(): ProjectKeyScopes } return self::$EVENTSREAD; } + public static function PROXYINVALIDATIONSWRITE(): ProjectKeyScopes + { + if (!isset(self::$PROXYINVALIDATIONSWRITE)) { + self::$PROXYINVALIDATIONSWRITE = new ProjectKeyScopes('proxy.invalidations.write'); + } + return self::$PROXYINVALIDATIONSWRITE; + } public static function APPSREAD(): ProjectKeyScopes { if (!isset(self::$APPSREAD)) { @@ -894,6 +910,7 @@ public static function from(string $value): self 'indexes.write' => self::INDEXESWRITE(), 'rows.read' => self::ROWSREAD(), 'rows.write' => self::ROWSWRITE(), + 'embeddings.write' => self::EMBEDDINGSWRITE(), 'collections.read' => self::COLLECTIONSREAD(), 'collections.write' => self::COLLECTIONSWRITE(), 'attributes.read' => self::ATTRIBUTESREAD(), @@ -958,6 +975,7 @@ public static function from(string $value): self 'wafRules.read' => self::WAFRULESREAD(), 'wafRules.write' => self::WAFRULESWRITE(), 'events.read' => self::EVENTSREAD(), + 'proxy.invalidations.write' => self::PROXYINVALIDATIONSWRITE(), 'apps.read' => self::APPSREAD(), 'apps.write' => self::APPSWRITE(), 'oauth2.read' => self::OAUTH2READ(), diff --git a/src/Appwrite/Enums/ProjectPolicyId.php b/src/Appwrite/Enums/ProjectPolicyId.php index 64562d88..7ee3c25d 100644 --- a/src/Appwrite/Enums/ProjectPolicyId.php +++ b/src/Appwrite/Enums/ProjectPolicyId.php @@ -16,6 +16,7 @@ class ProjectPolicyId implements JsonSerializable private static ProjectPolicyId $SESSIONLIMIT; private static ProjectPolicyId $USERLIMIT; private static ProjectPolicyId $MEMBERSHIPPRIVACY; + private static ProjectPolicyId $MFAFACTORS; private static ProjectPolicyId $DENYALIASEDEMAIL; private static ProjectPolicyId $DENYDISPOSABLEEMAIL; private static ProjectPolicyId $DENYFREEEMAIL; @@ -108,6 +109,13 @@ public static function MEMBERSHIPPRIVACY(): ProjectPolicyId } return self::$MEMBERSHIPPRIVACY; } + public static function MFAFACTORS(): ProjectPolicyId + { + if (!isset(self::$MFAFACTORS)) { + self::$MFAFACTORS = new ProjectPolicyId('mfa-factors'); + } + return self::$MFAFACTORS; + } public static function DENYALIASEDEMAIL(): ProjectPolicyId { if (!isset(self::$DENYALIASEDEMAIL)) { @@ -150,6 +158,7 @@ public static function from(string $value): self 'session-limit' => self::SESSIONLIMIT(), 'user-limit' => self::USERLIMIT(), 'membership-privacy' => self::MEMBERSHIPPRIVACY(), + 'mfa-factors' => self::MFAFACTORS(), 'deny-aliased-email' => self::DENYALIASEDEMAIL(), 'deny-disposable-email' => self::DENYDISPOSABLEEMAIL(), 'deny-free-email' => self::DENYFREEEMAIL(), diff --git a/src/Appwrite/Enums/Runtime.php b/src/Appwrite/Enums/Runtime.php index e6f9f263..d082cb86 100644 --- a/src/Appwrite/Enums/Runtime.php +++ b/src/Appwrite/Enums/Runtime.php @@ -16,6 +16,7 @@ class Runtime implements JsonSerializable private static Runtime $NODE23; private static Runtime $NODE24; private static Runtime $NODE25; + private static Runtime $NODE26; private static Runtime $PHP80; private static Runtime $PHP81; private static Runtime $PHP82; @@ -185,6 +186,13 @@ public static function NODE25(): Runtime } return self::$NODE25; } + public static function NODE26(): Runtime + { + if (!isset(self::$NODE26)) { + self::$NODE26 = new Runtime('node-26'); + } + return self::$NODE26; + } public static function PHP80(): Runtime { if (!isset(self::$PHP80)) { @@ -766,6 +774,7 @@ public static function from(string $value): self 'node-23' => self::NODE23(), 'node-24' => self::NODE24(), 'node-25' => self::NODE25(), + 'node-26' => self::NODE26(), 'php-8.0' => self::PHP80(), 'php-8.1' => self::PHP81(), 'php-8.2' => self::PHP82(), diff --git a/src/Appwrite/Models/BillingPlan.php b/src/Appwrite/Models/BillingPlan.php index 951b4073..3b7f783a 100644 --- a/src/Appwrite/Models/BillingPlan.php +++ b/src/Appwrite/Models/BillingPlan.php @@ -24,7 +24,6 @@ * @param int $storage storage * @param int $imageTransformations image transformations * @param int $screenshotsGenerated screenshots generated - * @param int $members members * @param int $webhooks webhooks * @param int $wafRules maximum waf rules per project * @param int $projects projects @@ -48,7 +47,6 @@ * @param int $topics topics for messaging * @param int $authPhone sms authentications per month * @param int $domains custom domains - * @param int $activityLogs activity log days * @param int $usageLogs usage history days * @param int $projectInactivityDays number of days of console inactivity before a project is paused. 0 means pausing is disabled. * @param int $alertLimit alert threshold percentage @@ -71,15 +69,17 @@ * @param bool $supportsFreeEmailValidation does plan support blocking free email addresses. * @param bool $supportsCorporateEmailValidation does plan support restricting sign-ups to corporate email addresses only. * @param bool $supportsProjectSpecificRoles does plan support project-specific member roles. - * @param bool $backupsEnabled does plan support backup policies. * @param bool $usagePerProject whether usage addons are calculated per project. * @param BillingPlanSupportedAddons $supportedAddons supported addons for this plan - * @param int $backupPolicies how many policies does plan support * @param int $deploymentSize maximum function and site deployment size in mb * @param int $buildSize maximum function and site deployment size in mb * @param bool $databasesAllowEncrypt does the plan support encrypted string attributes or not. * @param BillingPlanGroup $group group of this billing plan for variants + * @param int|null $members members + * @param int|null $activityLogs activity log days * @param array|null $usageLogsIntervals usage log time intervals allowed for this plan (e.g. 15m, 1h, 1d). + * @param bool|null $backupsEnabled does plan support backup policies. + * @param int|null $backupPolicies how many policies does plan support * @param BillingPlanLimits|null $limits plan specific limits * @param Program|null $program details of the program this plan is a part of. * @param BillingPlanDedicatedDatabaseLimits|null $dedicatedDatabases dedicated database limits available to this plan. @@ -95,7 +95,6 @@ public function __construct( public int $storage, public int $imageTransformations, public int $screenshotsGenerated, - public int $members, public int $webhooks, public int $wafRules, public int $projects, @@ -119,7 +118,6 @@ public function __construct( public int $topics, public int $authPhone, public int $domains, - public int $activityLogs, public int $usageLogs, public int $projectInactivityDays, public int $alertLimit, @@ -142,15 +140,17 @@ public function __construct( public bool $supportsFreeEmailValidation, public bool $supportsCorporateEmailValidation, public bool $supportsProjectSpecificRoles, - public bool $backupsEnabled, public bool $usagePerProject, public BillingPlanSupportedAddons $supportedAddons, - public int $backupPolicies, public int $deploymentSize, public int $buildSize, public bool $databasesAllowEncrypt, public BillingPlanGroup $group, + public ?int $members = null, + public ?int $activityLogs = null, public ?array $usageLogsIntervals = null, + public ?bool $backupsEnabled = null, + public ?int $backupPolicies = null, public ?BillingPlanLimits $limits = null, public ?Program $program = null, public ?BillingPlanDedicatedDatabaseLimits $dedicatedDatabases = null @@ -192,9 +192,6 @@ public static function from(array $data): static if (!array_key_exists('screenshotsGenerated', $data)) { throw new \InvalidArgumentException('Missing required field "screenshotsGenerated" for ' . static::class . '.'); } - if (!array_key_exists('members', $data)) { - throw new \InvalidArgumentException('Missing required field "members" for ' . static::class . '.'); - } if (!array_key_exists('webhooks', $data)) { throw new \InvalidArgumentException('Missing required field "webhooks" for ' . static::class . '.'); } @@ -264,9 +261,6 @@ public static function from(array $data): static if (!array_key_exists('domains', $data)) { throw new \InvalidArgumentException('Missing required field "domains" for ' . static::class . '.'); } - if (!array_key_exists('activityLogs', $data)) { - throw new \InvalidArgumentException('Missing required field "activityLogs" for ' . static::class . '.'); - } if (!array_key_exists('usageLogs', $data)) { throw new \InvalidArgumentException('Missing required field "usageLogs" for ' . static::class . '.'); } @@ -333,18 +327,12 @@ public static function from(array $data): static if (!array_key_exists('supportsProjectSpecificRoles', $data)) { throw new \InvalidArgumentException('Missing required field "supportsProjectSpecificRoles" for ' . static::class . '.'); } - if (!array_key_exists('backupsEnabled', $data)) { - throw new \InvalidArgumentException('Missing required field "backupsEnabled" for ' . static::class . '.'); - } if (!array_key_exists('usagePerProject', $data)) { throw new \InvalidArgumentException('Missing required field "usagePerProject" for ' . static::class . '.'); } if (!array_key_exists('supportedAddons', $data)) { throw new \InvalidArgumentException('Missing required field "supportedAddons" for ' . static::class . '.'); } - if (!array_key_exists('backupPolicies', $data)) { - throw new \InvalidArgumentException('Missing required field "backupPolicies" for ' . static::class . '.'); - } if (!array_key_exists('deploymentSize', $data)) { throw new \InvalidArgumentException('Missing required field "deploymentSize" for ' . static::class . '.'); } @@ -369,7 +357,6 @@ public static function from(array $data): static storage: $data['storage'], imageTransformations: $data['imageTransformations'], screenshotsGenerated: $data['screenshotsGenerated'], - members: $data['members'], webhooks: $data['webhooks'], wafRules: $data['wafRules'], projects: $data['projects'], @@ -393,7 +380,6 @@ functions: $data['functions'], topics: $data['topics'], authPhone: $data['authPhone'], domains: $data['domains'], - activityLogs: $data['activityLogs'], usageLogs: $data['usageLogs'], projectInactivityDays: $data['projectInactivityDays'], alertLimit: $data['alertLimit'], @@ -416,15 +402,17 @@ functions: $data['functions'], supportsFreeEmailValidation: $data['supportsFreeEmailValidation'], supportsCorporateEmailValidation: $data['supportsCorporateEmailValidation'], supportsProjectSpecificRoles: $data['supportsProjectSpecificRoles'], - backupsEnabled: $data['backupsEnabled'], usagePerProject: $data['usagePerProject'], supportedAddons: static::hydrateTypedValue(BillingPlanSupportedAddons::class, $data['supportedAddons']), - backupPolicies: $data['backupPolicies'], deploymentSize: $data['deploymentSize'], buildSize: $data['buildSize'], databasesAllowEncrypt: $data['databasesAllowEncrypt'], group: static::hydrateTypedValue(BillingPlanGroup::class, $data['group']), + members: array_key_exists('members', $data) ? $data['members'] : null, + activityLogs: array_key_exists('activityLogs', $data) ? $data['activityLogs'] : null, usageLogsIntervals: array_key_exists('usageLogsIntervals', $data) ? $data['usageLogsIntervals'] : null, + backupsEnabled: array_key_exists('backupsEnabled', $data) ? $data['backupsEnabled'] : null, + backupPolicies: array_key_exists('backupPolicies', $data) ? $data['backupPolicies'] : null, limits: array_key_exists('limits', $data) ? static::hydrateTypedValue(BillingPlanLimits::class, $data['limits'], true) : null, program: array_key_exists('program', $data) ? static::hydrateTypedValue(Program::class, $data['program'], true) : null, dedicatedDatabases: array_key_exists('dedicatedDatabases', $data) ? static::hydrateTypedValue(BillingPlanDedicatedDatabaseLimits::class, $data['dedicatedDatabases'], true) : null diff --git a/src/Appwrite/Models/BillingPlanAddon.php b/src/Appwrite/Models/BillingPlanAddon.php index 7e79ea14..f34340e5 100644 --- a/src/Appwrite/Models/BillingPlanAddon.php +++ b/src/Appwrite/Models/BillingPlanAddon.php @@ -12,12 +12,12 @@ /** * BillingPlanAddon constructor. * - * @param BillingPlanAddonDetails $seats addon seats - * @param BillingPlanAddonDetails $projects addon projects + * @param BillingPlanAddonDetails|null $seats addon seats + * @param BillingPlanAddonDetails|null $projects addon projects */ public function __construct( - public BillingPlanAddonDetails $seats, - public BillingPlanAddonDetails $projects + public ?BillingPlanAddonDetails $seats = null, + public ?BillingPlanAddonDetails $projects = null ) { } @@ -26,16 +26,10 @@ public function __construct( */ public static function from(array $data): static { - if (!array_key_exists('seats', $data)) { - throw new \InvalidArgumentException('Missing required field "seats" for ' . static::class . '.'); - } - if (!array_key_exists('projects', $data)) { - throw new \InvalidArgumentException('Missing required field "projects" for ' . static::class . '.'); - } return new static( - seats: static::hydrateTypedValue(BillingPlanAddonDetails::class, $data['seats']), - projects: static::hydrateTypedValue(BillingPlanAddonDetails::class, $data['projects']) + seats: array_key_exists('seats', $data) ? static::hydrateTypedValue(BillingPlanAddonDetails::class, $data['seats'], true) : null, + projects: array_key_exists('projects', $data) ? static::hydrateTypedValue(BillingPlanAddonDetails::class, $data['projects'], true) : null ); } diff --git a/src/Appwrite/Models/BillingPlanAddonDetails.php b/src/Appwrite/Models/BillingPlanAddonDetails.php index 60f94449..79b8500c 100644 --- a/src/Appwrite/Models/BillingPlanAddonDetails.php +++ b/src/Appwrite/Models/BillingPlanAddonDetails.php @@ -16,20 +16,20 @@ * @param int $planIncluded addon plan included value * @param int $limit addon limit * @param string $type addon type - * @param string $currency price currency * @param float $price price * @param int $value resource value * @param string $invoiceDesc description on invoice + * @param string|null $currency price currency */ public function __construct( public bool $supported, public int $planIncluded, public int $limit, public string $type, - public string $currency, public float $price, public int $value, - public string $invoiceDesc + public string $invoiceDesc, + public ?string $currency = null ) { } @@ -50,9 +50,6 @@ public static function from(array $data): static if (!array_key_exists('type', $data)) { throw new \InvalidArgumentException('Missing required field "type" for ' . static::class . '.'); } - if (!array_key_exists('currency', $data)) { - throw new \InvalidArgumentException('Missing required field "currency" for ' . static::class . '.'); - } if (!array_key_exists('price', $data)) { throw new \InvalidArgumentException('Missing required field "price" for ' . static::class . '.'); } @@ -68,10 +65,10 @@ public static function from(array $data): static planIncluded: $data['planIncluded'], limit: $data['limit'], type: $data['type'], - currency: $data['currency'], price: $data['price'], value: $data['value'], - invoiceDesc: $data['invoiceDesc'] + invoiceDesc: $data['invoiceDesc'], + currency: array_key_exists('currency', $data) ? $data['currency'] : null ); } diff --git a/src/Appwrite/Models/Database.php b/src/Appwrite/Models/Database.php index fb6f77f3..b5e4baa0 100644 --- a/src/Appwrite/Models/Database.php +++ b/src/Appwrite/Models/Database.php @@ -22,7 +22,7 @@ * @param bool $enabled if database is enabled. can be 'enabled' or 'disabled'. when disabled, the database is inaccessible to users, but remains accessible to server sdks using api keys. * @param DatabaseType $type database type. * @param DatabaseStatus|null $status dedicated database lifecycle status. null when the database has no valid dedicated backing. - * @param string|null $engine underlying engine of the dedicated backing: postgresql, mysql, mariadb, or mongodb. a managed product (tablesdb, documentsdb, vectorsdb) reports the engine it runs on, so its type and engine can differ. null when the database has no dedicated backing. + * @param string|null $engine underlying engine of the dedicated backing: postgresql, mysql, or mongodb. a managed product (tablesdb, documentsdb, vectorsdb) reports the engine it runs on, so its type and engine can differ. null when the database has no dedicated backing. * @param string|null $specification compute specification identifier of the dedicated backing, e.g. s-2vcpu-2gb. null when the database has no dedicated backing. * @param int|null $replicas number of secondary high availability replicas, excluding the primary. null when backing configuration is unavailable. * @param list|null $policies database backup policies. diff --git a/src/Appwrite/Models/DatabaseMigration.php b/src/Appwrite/Models/DatabaseMigration.php new file mode 100644 index 00000000..d8e19ed8 --- /dev/null +++ b/src/Appwrite/Models/DatabaseMigration.php @@ -0,0 +1,152 @@ + $data + */ + public static function from(array $data): static + { + if (!array_key_exists('$id', $data)) { + throw new \InvalidArgumentException('Missing required field "$id" for ' . static::class . '.'); + } + if (!array_key_exists('$createdAt', $data)) { + throw new \InvalidArgumentException('Missing required field "$createdAt" for ' . static::class . '.'); + } + if (!array_key_exists('$updatedAt', $data)) { + throw new \InvalidArgumentException('Missing required field "$updatedAt" for ' . static::class . '.'); + } + if (!array_key_exists('projectId', $data)) { + throw new \InvalidArgumentException('Missing required field "projectId" for ' . static::class . '.'); + } + if (!array_key_exists('databaseId', $data)) { + throw new \InvalidArgumentException('Missing required field "databaseId" for ' . static::class . '.'); + } + if (!array_key_exists('specification', $data)) { + throw new \InvalidArgumentException('Missing required field "specification" for ' . static::class . '.'); + } + if (!array_key_exists('phase', $data)) { + throw new \InvalidArgumentException('Missing required field "phase" for ' . static::class . '.'); + } + if (!array_key_exists('attempt', $data)) { + throw new \InvalidArgumentException('Missing required field "attempt" for ' . static::class . '.'); + } + if (!array_key_exists('lastError', $data)) { + throw new \InvalidArgumentException('Missing required field "lastError" for ' . static::class . '.'); + } + if (!array_key_exists('lagDocuments', $data)) { + throw new \InvalidArgumentException('Missing required field "lagDocuments" for ' . static::class . '.'); + } + if (!array_key_exists('verifiedAt', $data)) { + throw new \InvalidArgumentException('Missing required field "verifiedAt" for ' . static::class . '.'); + } + if (!array_key_exists('cutoverAt', $data)) { + throw new \InvalidArgumentException('Missing required field "cutoverAt" for ' . static::class . '.'); + } + if (!array_key_exists('soakUntil', $data)) { + throw new \InvalidArgumentException('Missing required field "soakUntil" for ' . static::class . '.'); + } + if (!array_key_exists('autoCutover', $data)) { + throw new \InvalidArgumentException('Missing required field "autoCutover" for ' . static::class . '.'); + } + if (!array_key_exists('cutoverRequested', $data)) { + throw new \InvalidArgumentException('Missing required field "cutoverRequested" for ' . static::class . '.'); + } + if (!array_key_exists('paused', $data)) { + throw new \InvalidArgumentException('Missing required field "paused" for ' . static::class . '.'); + } + + return new static( + id: $data['$id'], + createdAt: $data['$createdAt'], + updatedAt: $data['$updatedAt'], + projectId: $data['projectId'], + databaseId: $data['databaseId'], + specification: $data['specification'], + phase: $data['phase'], + attempt: $data['attempt'], + lastError: $data['lastError'], + lagDocuments: $data['lagDocuments'], + verifiedAt: $data['verifiedAt'], + cutoverAt: $data['cutoverAt'], + soakUntil: $data['soakUntil'], + autoCutover: $data['autoCutover'], + cutoverRequested: $data['cutoverRequested'], + paused: $data['paused'] + ); + } + + /** + * @return array + */ + public function toArray(): array + { + $result = [ + '$id' => static::serializeValue($this->id), + '$createdAt' => static::serializeValue($this->createdAt), + '$updatedAt' => static::serializeValue($this->updatedAt), + 'projectId' => static::serializeValue($this->projectId), + 'databaseId' => static::serializeValue($this->databaseId), + 'specification' => static::serializeValue($this->specification), + 'phase' => static::serializeValue($this->phase), + 'attempt' => static::serializeValue($this->attempt), + 'lastError' => static::serializeValue($this->lastError), + 'lagDocuments' => static::serializeValue($this->lagDocuments), + 'verifiedAt' => static::serializeValue($this->verifiedAt), + 'cutoverAt' => static::serializeValue($this->cutoverAt), + 'soakUntil' => static::serializeValue($this->soakUntil), + 'autoCutover' => static::serializeValue($this->autoCutover), + 'cutoverRequested' => static::serializeValue($this->cutoverRequested), + 'paused' => static::serializeValue($this->paused) + ]; + + return $result; + } +} diff --git a/src/Appwrite/Models/DatabaseMigrationList.php b/src/Appwrite/Models/DatabaseMigrationList.php new file mode 100644 index 00000000..d8016eaf --- /dev/null +++ b/src/Appwrite/Models/DatabaseMigrationList.php @@ -0,0 +1,59 @@ + $migrations list of migrations. + */ + public function __construct( + public int $total, + public array $migrations + ) { + } + + /** + * @param array $data + */ + public static function from(array $data): static + { + if (!array_key_exists('total', $data)) { + throw new \InvalidArgumentException('Missing required field "total" for ' . static::class . '.'); + } + if (!array_key_exists('migrations', $data)) { + throw new \InvalidArgumentException('Missing required field "migrations" for ' . static::class . '.'); + } + + return new static( + total: $data['total'], + migrations: is_array($data['migrations']) + ? array_map( + static fn (mixed $item): mixed => static::hydrateTypedValue(DatabaseMigration::class, $item), + $data['migrations'] + ) + : $data['migrations'] + ); + } + + /** + * @return array + */ + public function toArray(): array + { + $result = [ + 'total' => static::serializeValue($this->total), + 'migrations' => static::serializeValue($this->migrations) + ]; + + return $result; + } +} diff --git a/src/Appwrite/Models/DatabaseStatus.php b/src/Appwrite/Models/DatabaseStatus.php index 59b30ffd..5408a15e 100644 --- a/src/Appwrite/Models/DatabaseStatus.php +++ b/src/Appwrite/Models/DatabaseStatus.php @@ -12,14 +12,20 @@ /** * DatabaseStatus constructor. * - * @param string $health overall health status: healthy, degraded, or unhealthy. + * @param string $health overall health status: healthy, degraded, unhealthy, or unknown when nothing could be measured. * @param bool $ready whether the database is ready to accept connections. - * @param string $engine database engine: postgresql, mysql, mariadb, or mongodb. + * @param string $engine database engine: postgresql, mysql, or mongodb. * @param string $version database engine version. * @param int $uptime database uptime in seconds. * @param DatabaseStatusConnections $connections connection statistics. - * @param list $replicas list of database replicas and their status. + * @param string $syncMode requested replication sync mode. possible values: async, sync, quorum. compare with effectivesyncmode for what the primary is enforcing. + * @param bool $syncDegraded whether the enforced replication is weaker than the requested syncmode. + * @param int $syncAcknowledgements number of standby acknowledgements the primary waits for before a write is committed. + * @param int $syncStandbyCount number of standbys registered with the primary for synchronous replication. + * @param list $replicas list of database replicas and their status. every configured member appears, including one the backend has not brought up, which is reported as not healthy. * @param list $volumes storage volume information. + * @param string|null $effectiveSyncMode replication sync mode the primary is actually enforcing. null when high availability is disabled or the state could not be read. + * @param bool|null $syncStateConfirmed whether the other sync fields are an engine reading rather than a recorded estimate. true when the primary answered what it is enforcing, including when that answer contradicted the record, in which case the contradicted values are replaced by the ones the engine reports. false when the reading could not be taken: the probe did not answer, there was no engine to ask, or the values describe a configuration change just applied rather than anything measured. absent when no engine was asked at all, so an unprobed database is distinguishable from an unconfirmed one. false never means a standby was found lagging, because it is the absence of a reading rather than a negative one, so draw no conclusion about replication health from it or from a response that omits it. */ public function __construct( public string $health, @@ -28,8 +34,14 @@ public function __construct( public string $version, public int $uptime, public DatabaseStatusConnections $connections, + public string $syncMode, + public bool $syncDegraded, + public int $syncAcknowledgements, + public int $syncStandbyCount, public array $replicas, - public array $volumes + public array $volumes, + public ?string $effectiveSyncMode = null, + public ?bool $syncStateConfirmed = null ) { } @@ -56,6 +68,18 @@ public static function from(array $data): static if (!array_key_exists('connections', $data)) { throw new \InvalidArgumentException('Missing required field "connections" for ' . static::class . '.'); } + if (!array_key_exists('syncMode', $data)) { + throw new \InvalidArgumentException('Missing required field "syncMode" for ' . static::class . '.'); + } + if (!array_key_exists('syncDegraded', $data)) { + throw new \InvalidArgumentException('Missing required field "syncDegraded" for ' . static::class . '.'); + } + if (!array_key_exists('syncAcknowledgements', $data)) { + throw new \InvalidArgumentException('Missing required field "syncAcknowledgements" for ' . static::class . '.'); + } + if (!array_key_exists('syncStandbyCount', $data)) { + throw new \InvalidArgumentException('Missing required field "syncStandbyCount" for ' . static::class . '.'); + } if (!array_key_exists('replicas', $data)) { throw new \InvalidArgumentException('Missing required field "replicas" for ' . static::class . '.'); } @@ -70,6 +94,10 @@ public static function from(array $data): static version: $data['version'], uptime: $data['uptime'], connections: static::hydrateTypedValue(DatabaseStatusConnections::class, $data['connections']), + syncMode: $data['syncMode'], + syncDegraded: $data['syncDegraded'], + syncAcknowledgements: $data['syncAcknowledgements'], + syncStandbyCount: $data['syncStandbyCount'], replicas: is_array($data['replicas']) ? array_map( static fn (mixed $item): mixed => static::hydrateTypedValue(DatabaseStatusReplica::class, $item), @@ -81,7 +109,9 @@ public static function from(array $data): static static fn (mixed $item): mixed => static::hydrateTypedValue(DatabaseStatusVolume::class, $item), $data['volumes'] ) - : $data['volumes'] + : $data['volumes'], + effectiveSyncMode: array_key_exists('effectiveSyncMode', $data) ? $data['effectiveSyncMode'] : null, + syncStateConfirmed: array_key_exists('syncStateConfirmed', $data) ? $data['syncStateConfirmed'] : null ); } @@ -97,6 +127,12 @@ public function toArray(): array 'version' => static::serializeValue($this->version), 'uptime' => static::serializeValue($this->uptime), 'connections' => static::serializeValue($this->connections), + 'syncMode' => static::serializeValue($this->syncMode), + 'effectiveSyncMode' => static::serializeValue($this->effectiveSyncMode), + 'syncDegraded' => static::serializeValue($this->syncDegraded), + 'syncAcknowledgements' => static::serializeValue($this->syncAcknowledgements), + 'syncStandbyCount' => static::serializeValue($this->syncStandbyCount), + 'syncStateConfirmed' => static::serializeValue($this->syncStateConfirmed), 'replicas' => static::serializeValue($this->replicas), 'volumes' => static::serializeValue($this->volumes) ]; diff --git a/src/Appwrite/Models/DatabaseStatusConnections.php b/src/Appwrite/Models/DatabaseStatusConnections.php index b1917238..da68a5b2 100644 --- a/src/Appwrite/Models/DatabaseStatusConnections.php +++ b/src/Appwrite/Models/DatabaseStatusConnections.php @@ -13,7 +13,7 @@ * DatabaseStatusConnections constructor. * * @param int $current current number of active connections. - * @param int $max maximum allowed connections. + * @param int $max the engine's own max_connections. on a pooled database this is the backend limit the pooler multiplexes onto, not the ceiling a client pool may reach — that is networkmaxconnections on the database resource. */ public function __construct( public int $current, diff --git a/src/Appwrite/Models/DatabaseStatusReplica.php b/src/Appwrite/Models/DatabaseStatusReplica.php index acf6559c..8c00b586 100644 --- a/src/Appwrite/Models/DatabaseStatusReplica.php +++ b/src/Appwrite/Models/DatabaseStatusReplica.php @@ -12,8 +12,8 @@ /** * DatabaseStatusReplica constructor. * - * @param int $index statefulset pod index (0 = primary, 1+ = replicas). - * @param string $role replica role: primary or replica. + * @param int $index member index within the database. read `role` for which member accepts writes: a failover moves the primary without renumbering the indexes. + * @param string $role member role. possible values: primary (accepts reads and writes), replica (read-only follower), unknown (placement not established; reported while a transition is moving or restarting the topology, so no member can be named the write target). * @param bool $healthy whether the replica is healthy. * @param float|null $lagSeconds replication lag in seconds (null for primary). */ diff --git a/src/Appwrite/Models/DedicatedDatabase.php b/src/Appwrite/Models/DedicatedDatabase.php index 963e95fa..ce4706f6 100644 --- a/src/Appwrite/Models/DedicatedDatabase.php +++ b/src/Appwrite/Models/DedicatedDatabase.php @@ -18,12 +18,12 @@ * @param string $projectId project id that owns this database. * @param string $name database display name. * @param string $api product api that owns this database: tablesdb, documentsdb, vectorsdb, mysql, postgresql, or mongodb. - * @param string $engine database engine: postgresql, mysql, mariadb, or mongodb. + * @param string $engine database engine: postgresql, mysql, or mongodb. null until the backing reports one. * @param string $version database engine version. * @param string $specification specification identifier. * @param string $backend database backend provider. possible values: prisma, edge. * @param string $hostname database hostname for connections. - * @param int $connectionPort database port for connections. + * @param int $connectionPort database port for connections. derived from the engine when the backing has not reported one yet. * @param string $connectionUser database username for connections. * @param string $connectionPassword database password for connections. * @param string $connectionString full database connection string (uri format). @@ -40,8 +40,7 @@ * @param string $nodePool kubernetes node pool where the database is scheduled. * @param int $replicas number of high availability replicas. high availability is enabled when greater than 0. * @param string $syncMode replication sync mode: async, sync, or quorum. - * @param int $crossRegionReplicas number of cross-region replicas. cross-region availability is enabled when greater than 0. - * @param int $networkMaxConnections maximum concurrent connections. + * @param int $networkMaxConnections maximum concurrent client connections. this is the limit a client pool may reach; the engine's own max_connections reported by the status endpoint is a smaller backend limit the pooler multiplexes onto and does not constrain a client pool. * @param int $networkIdleTimeoutSeconds connection idle timeout in seconds. * @param array $networkIPAllowlist ip addresses/cidr ranges allowed to connect. * @param bool $backupEnabled whether automatic backups are enabled. @@ -91,7 +90,6 @@ public function __construct( public string $nodePool, public int $replicas, public string $syncMode, - public int $crossRegionReplicas, public int $networkMaxConnections, public int $networkIdleTimeoutSeconds, public array $networkIPAllowlist, @@ -204,9 +202,6 @@ public static function from(array $data): static if (!array_key_exists('syncMode', $data)) { throw new \InvalidArgumentException('Missing required field "syncMode" for ' . static::class . '.'); } - if (!array_key_exists('crossRegionReplicas', $data)) { - throw new \InvalidArgumentException('Missing required field "crossRegionReplicas" for ' . static::class . '.'); - } if (!array_key_exists('networkMaxConnections', $data)) { throw new \InvalidArgumentException('Missing required field "networkMaxConnections" for ' . static::class . '.'); } @@ -291,7 +286,6 @@ public static function from(array $data): static nodePool: $data['nodePool'], replicas: $data['replicas'], syncMode: $data['syncMode'], - crossRegionReplicas: $data['crossRegionReplicas'], networkMaxConnections: $data['networkMaxConnections'], networkIdleTimeoutSeconds: $data['networkIdleTimeoutSeconds'], networkIPAllowlist: $data['networkIPAllowlist'], @@ -351,7 +345,6 @@ public function toArray(): array 'nodePool' => static::serializeValue($this->nodePool), 'replicas' => static::serializeValue($this->replicas), 'syncMode' => static::serializeValue($this->syncMode), - 'crossRegionReplicas' => static::serializeValue($this->crossRegionReplicas), 'networkMaxConnections' => static::serializeValue($this->networkMaxConnections), 'networkIdleTimeoutSeconds' => static::serializeValue($this->networkIdleTimeoutSeconds), 'networkIPAllowlist' => static::serializeValue($this->networkIPAllowlist), diff --git a/src/Appwrite/Models/DedicatedDatabaseMember.php b/src/Appwrite/Models/DedicatedDatabaseMember.php index ba44af70..fb6dec1d 100644 --- a/src/Appwrite/Models/DedicatedDatabaseMember.php +++ b/src/Appwrite/Models/DedicatedDatabaseMember.php @@ -13,15 +13,15 @@ * DedicatedDatabaseMember constructor. * * @param string $id member identifier. - * @param string $role member role. possible values: primary (accepts reads and writes), replica (read-only follower). - * @param string $status member pod status. possible values: provisioning (pod missing or pending), starting (running but not ready), active (running and ready), failed (failed phase or crashloopbackoff container), or the lowercased pod phase reported by the cluster. - * @param float $lagSeconds replication lag in seconds. + * @param string $role member role. possible values: primary (accepts reads and writes), replica (read-only follower), unknown (placement not established; reported while a transition is moving or restarting the topology and this member has not been probed, so no member can be named the write target). + * @param string $status member pod status. possible values: pending (configured but absent from the backend topology, so nothing is bringing it up), provisioning (pod missing or pending), starting (running but not ready), active (running and ready), failed (failed phase or crashloopbackoff container), or the lowercased pod phase reported by the cluster. + * @param float|null $lagSeconds replication lag in seconds. null when the lag is not known: a primary has none to report, and a member the backend has not probed has none yet. */ public function __construct( public string $id, public string $role, public string $status, - public float $lagSeconds + public ?float $lagSeconds = null ) { } @@ -39,15 +39,12 @@ public static function from(array $data): static if (!array_key_exists('status', $data)) { throw new \InvalidArgumentException('Missing required field "status" for ' . static::class . '.'); } - if (!array_key_exists('lagSeconds', $data)) { - throw new \InvalidArgumentException('Missing required field "lagSeconds" for ' . static::class . '.'); - } return new static( id: $data['$id'], role: $data['role'], status: $data['status'], - lagSeconds: $data['lagSeconds'] + lagSeconds: array_key_exists('lagSeconds', $data) ? $data['lagSeconds'] : null ); } diff --git a/src/Appwrite/Models/DedicatedDatabaseOperation.php b/src/Appwrite/Models/DedicatedDatabaseOperation.php new file mode 100644 index 00000000..628b96a7 --- /dev/null +++ b/src/Appwrite/Models/DedicatedDatabaseOperation.php @@ -0,0 +1,108 @@ + $data + */ + public static function from(array $data): static + { + if (!array_key_exists('$id', $data)) { + throw new \InvalidArgumentException('Missing required field "$id" for ' . static::class . '.'); + } + if (!array_key_exists('$createdAt', $data)) { + throw new \InvalidArgumentException('Missing required field "$createdAt" for ' . static::class . '.'); + } + if (!array_key_exists('databaseId', $data)) { + throw new \InvalidArgumentException('Missing required field "databaseId" for ' . static::class . '.'); + } + if (!array_key_exists('type', $data)) { + throw new \InvalidArgumentException('Missing required field "type" for ' . static::class . '.'); + } + if (!array_key_exists('status', $data)) { + throw new \InvalidArgumentException('Missing required field "status" for ' . static::class . '.'); + } + if (!array_key_exists('attempts', $data)) { + throw new \InvalidArgumentException('Missing required field "attempts" for ' . static::class . '.'); + } + if (!array_key_exists('errorCode', $data)) { + throw new \InvalidArgumentException('Missing required field "errorCode" for ' . static::class . '.'); + } + if (!array_key_exists('errorMessage', $data)) { + throw new \InvalidArgumentException('Missing required field "errorMessage" for ' . static::class . '.'); + } + + return new static( + id: $data['$id'], + createdAt: $data['$createdAt'], + databaseId: $data['databaseId'], + type: $data['type'], + status: $data['status'], + attempts: $data['attempts'], + errorCode: $data['errorCode'], + errorMessage: $data['errorMessage'], + requestedAt: array_key_exists('requestedAt', $data) ? $data['requestedAt'] : null, + startedAt: array_key_exists('startedAt', $data) ? $data['startedAt'] : null, + completedAt: array_key_exists('completedAt', $data) ? $data['completedAt'] : null + ); + } + + /** + * @return array + */ + public function toArray(): array + { + $result = [ + '$id' => static::serializeValue($this->id), + '$createdAt' => static::serializeValue($this->createdAt), + 'databaseId' => static::serializeValue($this->databaseId), + 'type' => static::serializeValue($this->type), + 'status' => static::serializeValue($this->status), + 'attempts' => static::serializeValue($this->attempts), + 'requestedAt' => static::serializeValue($this->requestedAt), + 'startedAt' => static::serializeValue($this->startedAt), + 'completedAt' => static::serializeValue($this->completedAt), + 'errorCode' => static::serializeValue($this->errorCode), + 'errorMessage' => static::serializeValue($this->errorMessage) + ]; + + return $result; + } +} diff --git a/src/Appwrite/Models/DedicatedDatabaseOperationList.php b/src/Appwrite/Models/DedicatedDatabaseOperationList.php new file mode 100644 index 00000000..0ee0c79d --- /dev/null +++ b/src/Appwrite/Models/DedicatedDatabaseOperationList.php @@ -0,0 +1,59 @@ + $operations list of operations. + */ + public function __construct( + public int $total, + public array $operations + ) { + } + + /** + * @param array $data + */ + public static function from(array $data): static + { + if (!array_key_exists('total', $data)) { + throw new \InvalidArgumentException('Missing required field "total" for ' . static::class . '.'); + } + if (!array_key_exists('operations', $data)) { + throw new \InvalidArgumentException('Missing required field "operations" for ' . static::class . '.'); + } + + return new static( + total: $data['total'], + operations: is_array($data['operations']) + ? array_map( + static fn (mixed $item): mixed => static::hydrateTypedValue(DedicatedDatabaseOperation::class, $item), + $data['operations'] + ) + : $data['operations'] + ); + } + + /** + * @return array + */ + public function toArray(): array + { + $result = [ + 'total' => static::serializeValue($this->total), + 'operations' => static::serializeValue($this->operations) + ]; + + return $result; + } +} diff --git a/src/Appwrite/Models/DedicatedDatabaseReplicas.php b/src/Appwrite/Models/DedicatedDatabaseReplicas.php index 37a760d2..f3728359 100644 --- a/src/Appwrite/Models/DedicatedDatabaseReplicas.php +++ b/src/Appwrite/Models/DedicatedDatabaseReplicas.php @@ -13,13 +13,23 @@ * DedicatedDatabaseReplicas constructor. * * @param int $replicas number of configured replicas. zero means high availability is disabled. - * @param string $syncMode replication sync mode. possible values: async (asynchronous, fastest), sync (synchronous, strong consistency), quorum (quorum-based, majority of replicas must confirm). + * @param string $syncMode requested replication sync mode. possible values: async (asynchronous, fastest), sync (synchronous, strong consistency), quorum (quorum-based, majority of replicas must confirm). this is what was asked for; compare it with effectivesyncmode for what the primary is enforcing. + * @param bool $syncDegraded whether the enforced replication is weaker than the requested syncmode. + * @param int $syncAcknowledgements number of standby acknowledgements the primary waits for before a write is committed. zero means writes are acknowledged locally. + * @param int $syncStandbyCount number of standbys registered with the primary for synchronous replication. * @param list $members per-pod statuses for the primary and every replica. + * @param string|null $effectiveSyncMode replication sync mode the primary is actually enforcing. null when high availability is disabled or the state could not be read. a value below the requested syncmode means writes are being acknowledged with weaker durability than configured. + * @param bool|null $syncStateConfirmed whether the other sync fields are an engine reading rather than a recorded estimate. true when the primary answered what it is enforcing, including when that answer contradicted the record, in which case the contradicted values are replaced by the ones the engine reports. false when the reading could not be taken: the probe did not answer, there was no engine to ask, or the values describe a configuration change just applied rather than anything measured. absent when no engine was asked at all, so an unprobed database is distinguishable from an unconfirmed one. false never means a standby was found lagging, because it is the absence of a reading rather than a negative one, so draw no conclusion about replication health from it or from a response that omits it. */ public function __construct( public int $replicas, public string $syncMode, - public array $members + public bool $syncDegraded, + public int $syncAcknowledgements, + public int $syncStandbyCount, + public array $members, + public ?string $effectiveSyncMode = null, + public ?bool $syncStateConfirmed = null ) { } @@ -34,6 +44,15 @@ public static function from(array $data): static if (!array_key_exists('syncMode', $data)) { throw new \InvalidArgumentException('Missing required field "syncMode" for ' . static::class . '.'); } + if (!array_key_exists('syncDegraded', $data)) { + throw new \InvalidArgumentException('Missing required field "syncDegraded" for ' . static::class . '.'); + } + if (!array_key_exists('syncAcknowledgements', $data)) { + throw new \InvalidArgumentException('Missing required field "syncAcknowledgements" for ' . static::class . '.'); + } + if (!array_key_exists('syncStandbyCount', $data)) { + throw new \InvalidArgumentException('Missing required field "syncStandbyCount" for ' . static::class . '.'); + } if (!array_key_exists('members', $data)) { throw new \InvalidArgumentException('Missing required field "members" for ' . static::class . '.'); } @@ -41,12 +60,17 @@ public static function from(array $data): static return new static( replicas: $data['replicas'], syncMode: $data['syncMode'], + syncDegraded: $data['syncDegraded'], + syncAcknowledgements: $data['syncAcknowledgements'], + syncStandbyCount: $data['syncStandbyCount'], members: is_array($data['members']) ? array_map( static fn (mixed $item): mixed => static::hydrateTypedValue(DedicatedDatabaseMember::class, $item), $data['members'] ) - : $data['members'] + : $data['members'], + effectiveSyncMode: array_key_exists('effectiveSyncMode', $data) ? $data['effectiveSyncMode'] : null, + syncStateConfirmed: array_key_exists('syncStateConfirmed', $data) ? $data['syncStateConfirmed'] : null ); } @@ -58,6 +82,11 @@ public function toArray(): array $result = [ 'replicas' => static::serializeValue($this->replicas), 'syncMode' => static::serializeValue($this->syncMode), + 'effectiveSyncMode' => static::serializeValue($this->effectiveSyncMode), + 'syncDegraded' => static::serializeValue($this->syncDegraded), + 'syncAcknowledgements' => static::serializeValue($this->syncAcknowledgements), + 'syncStandbyCount' => static::serializeValue($this->syncStandbyCount), + 'syncStateConfirmed' => static::serializeValue($this->syncStateConfirmed), 'members' => static::serializeValue($this->members) ]; diff --git a/src/Appwrite/Models/DedicatedDatabaseSpecificationPricing.php b/src/Appwrite/Models/DedicatedDatabaseSpecificationPricing.php index 37a3b4c2..815757e2 100644 --- a/src/Appwrite/Models/DedicatedDatabaseSpecificationPricing.php +++ b/src/Appwrite/Models/DedicatedDatabaseSpecificationPricing.php @@ -15,14 +15,12 @@ * @param float $storageOverageRate price per gb of storage above the included amount, per month, in usd. * @param float $bandwidthOverageRate price per gb of bandwidth above the included amount, per month, in usd. * @param float $replicaRate high availability replica price as a fraction of the specification cost. - * @param float $crossRegionReplicaRate cross-region replica price as a fraction of the specification cost. * @param float $pitrRate point-in-time recovery price as a fraction of the specification cost. */ public function __construct( public float $storageOverageRate, public float $bandwidthOverageRate, public float $replicaRate, - public float $crossRegionReplicaRate, public float $pitrRate ) { } @@ -41,9 +39,6 @@ public static function from(array $data): static if (!array_key_exists('replicaRate', $data)) { throw new \InvalidArgumentException('Missing required field "replicaRate" for ' . static::class . '.'); } - if (!array_key_exists('crossRegionReplicaRate', $data)) { - throw new \InvalidArgumentException('Missing required field "crossRegionReplicaRate" for ' . static::class . '.'); - } if (!array_key_exists('pitrRate', $data)) { throw new \InvalidArgumentException('Missing required field "pitrRate" for ' . static::class . '.'); } @@ -52,7 +47,6 @@ public static function from(array $data): static storageOverageRate: $data['storageOverageRate'], bandwidthOverageRate: $data['bandwidthOverageRate'], replicaRate: $data['replicaRate'], - crossRegionReplicaRate: $data['crossRegionReplicaRate'], pitrRate: $data['pitrRate'] ); } @@ -66,7 +60,6 @@ public function toArray(): array 'storageOverageRate' => static::serializeValue($this->storageOverageRate), 'bandwidthOverageRate' => static::serializeValue($this->bandwidthOverageRate), 'replicaRate' => static::serializeValue($this->replicaRate), - 'crossRegionReplicaRate' => static::serializeValue($this->crossRegionReplicaRate), 'pitrRate' => static::serializeValue($this->pitrRate) ]; diff --git a/src/Appwrite/Models/Embedding.php b/src/Appwrite/Models/Embedding.php new file mode 100644 index 00000000..c38c4787 --- /dev/null +++ b/src/Appwrite/Models/Embedding.php @@ -0,0 +1,68 @@ + $data + */ + public static function from(array $data): static + { + if (!array_key_exists('model', $data)) { + throw new \InvalidArgumentException('Missing required field "model" for ' . static::class . '.'); + } + if (!array_key_exists('dimension', $data)) { + throw new \InvalidArgumentException('Missing required field "dimension" for ' . static::class . '.'); + } + if (!array_key_exists('embedding', $data)) { + throw new \InvalidArgumentException('Missing required field "embedding" for ' . static::class . '.'); + } + if (!array_key_exists('error', $data)) { + throw new \InvalidArgumentException('Missing required field "error" for ' . static::class . '.'); + } + + return new static( + model: $data['model'], + dimension: $data['dimension'], + embedding: $data['embedding'], + error: $data['error'] + ); + } + + /** + * @return array + */ + public function toArray(): array + { + $result = [ + 'model' => static::serializeValue($this->model), + 'dimension' => static::serializeValue($this->dimension), + 'embedding' => static::serializeValue($this->embedding), + 'error' => static::serializeValue($this->error) + ]; + + return $result; + } +} diff --git a/src/Appwrite/Models/EmbeddingList.php b/src/Appwrite/Models/EmbeddingList.php new file mode 100644 index 00000000..d666e5d5 --- /dev/null +++ b/src/Appwrite/Models/EmbeddingList.php @@ -0,0 +1,59 @@ + $embeddings list of embeddings. + */ + public function __construct( + public int $total, + public array $embeddings + ) { + } + + /** + * @param array $data + */ + public static function from(array $data): static + { + if (!array_key_exists('total', $data)) { + throw new \InvalidArgumentException('Missing required field "total" for ' . static::class . '.'); + } + if (!array_key_exists('embeddings', $data)) { + throw new \InvalidArgumentException('Missing required field "embeddings" for ' . static::class . '.'); + } + + return new static( + total: $data['total'], + embeddings: is_array($data['embeddings']) + ? array_map( + static fn (mixed $item): mixed => static::hydrateTypedValue(Embedding::class, $item), + $data['embeddings'] + ) + : $data['embeddings'] + ); + } + + /** + * @return array + */ + public function toArray(): array + { + $result = [ + 'total' => static::serializeValue($this->total), + 'embeddings' => static::serializeValue($this->embeddings) + ]; + + return $result; + } +} diff --git a/src/Appwrite/Models/File.php b/src/Appwrite/Models/File.php index 0853e506..4ce7c537 100644 --- a/src/Appwrite/Models/File.php +++ b/src/Appwrite/Models/File.php @@ -18,6 +18,8 @@ * @param string $updatedAt file update date in iso 8601 format. * @param array $permissions file permissions. [learn more about permissions](https://appwrite.io/docs/permissions). * @param string $name file name. + * @param string $folder virtual folder containing the file, with a trailing slash. empty for the bucket root. + * @param string $key full virtual path of the file: the folder followed by the file name. * @param string $signature file md5 signature. * @param string $mimeType file mime type. * @param int $sizeOriginal file original size in bytes. @@ -34,6 +36,8 @@ public function __construct( public string $updatedAt, public array $permissions, public string $name, + public string $folder, + public string $key, public string $signature, public string $mimeType, public int $sizeOriginal, @@ -68,6 +72,12 @@ public static function from(array $data): static if (!array_key_exists('name', $data)) { throw new \InvalidArgumentException('Missing required field "name" for ' . static::class . '.'); } + if (!array_key_exists('folder', $data)) { + throw new \InvalidArgumentException('Missing required field "folder" for ' . static::class . '.'); + } + if (!array_key_exists('key', $data)) { + throw new \InvalidArgumentException('Missing required field "key" for ' . static::class . '.'); + } if (!array_key_exists('signature', $data)) { throw new \InvalidArgumentException('Missing required field "signature" for ' . static::class . '.'); } @@ -100,6 +110,8 @@ public static function from(array $data): static updatedAt: $data['$updatedAt'], permissions: $data['$permissions'], name: $data['name'], + folder: $data['folder'], + key: $data['key'], signature: $data['signature'], mimeType: $data['mimeType'], sizeOriginal: $data['sizeOriginal'], @@ -123,6 +135,8 @@ public function toArray(): array '$updatedAt' => static::serializeValue($this->updatedAt), '$permissions' => static::serializeValue($this->permissions), 'name' => static::serializeValue($this->name), + 'folder' => static::serializeValue($this->folder), + 'key' => static::serializeValue($this->key), 'signature' => static::serializeValue($this->signature), 'mimeType' => static::serializeValue($this->mimeType), 'sizeOriginal' => static::serializeValue($this->sizeOriginal), diff --git a/src/Appwrite/Models/MfaChallengeSecret.php b/src/Appwrite/Models/MfaChallengeSecret.php new file mode 100644 index 00000000..579080ae --- /dev/null +++ b/src/Appwrite/Models/MfaChallengeSecret.php @@ -0,0 +1,75 @@ + $data + */ + public static function from(array $data): static + { + if (!array_key_exists('$id', $data)) { + throw new \InvalidArgumentException('Missing required field "$id" for ' . static::class . '.'); + } + if (!array_key_exists('$createdAt', $data)) { + throw new \InvalidArgumentException('Missing required field "$createdAt" for ' . static::class . '.'); + } + if (!array_key_exists('userId', $data)) { + throw new \InvalidArgumentException('Missing required field "userId" for ' . static::class . '.'); + } + if (!array_key_exists('expire', $data)) { + throw new \InvalidArgumentException('Missing required field "expire" for ' . static::class . '.'); + } + if (!array_key_exists('code', $data)) { + throw new \InvalidArgumentException('Missing required field "code" for ' . static::class . '.'); + } + + return new static( + id: $data['$id'], + createdAt: $data['$createdAt'], + userId: $data['userId'], + expire: $data['expire'], + code: $data['code'] + ); + } + + /** + * @return array + */ + public function toArray(): array + { + $result = [ + '$id' => static::serializeValue($this->id), + '$createdAt' => static::serializeValue($this->createdAt), + 'userId' => static::serializeValue($this->userId), + 'expire' => static::serializeValue($this->expire), + 'code' => static::serializeValue($this->code) + ]; + + return $result; + } +} diff --git a/src/Appwrite/Models/MfaFactors.php b/src/Appwrite/Models/MfaFactors.php index bdfd41a7..eca5d042 100644 --- a/src/Appwrite/Models/MfaFactors.php +++ b/src/Appwrite/Models/MfaFactors.php @@ -16,12 +16,14 @@ * @param bool $phone can phone (sms) be used for mfa challenge for this account. * @param bool $email can email be used for mfa challenge for this account. * @param bool $recoveryCode can recovery code be used for mfa challenge for this account. + * @param bool $custom can custom factor be used for mfa challenge for this account. */ public function __construct( public bool $totp, public bool $phone, public bool $email, - public bool $recoveryCode + public bool $recoveryCode, + public bool $custom ) { } @@ -42,12 +44,16 @@ public static function from(array $data): static if (!array_key_exists('recoveryCode', $data)) { throw new \InvalidArgumentException('Missing required field "recoveryCode" for ' . static::class . '.'); } + if (!array_key_exists('custom', $data)) { + throw new \InvalidArgumentException('Missing required field "custom" for ' . static::class . '.'); + } return new static( totp: $data['totp'], phone: $data['phone'], email: $data['email'], - recoveryCode: $data['recoveryCode'] + recoveryCode: $data['recoveryCode'], + custom: $data['custom'] ); } @@ -60,7 +66,8 @@ public function toArray(): array 'totp' => static::serializeValue($this->totp), 'phone' => static::serializeValue($this->phone), 'email' => static::serializeValue($this->email), - 'recoveryCode' => static::serializeValue($this->recoveryCode) + 'recoveryCode' => static::serializeValue($this->recoveryCode), + 'custom' => static::serializeValue($this->custom) ]; return $result; diff --git a/src/Appwrite/Models/Organization.php b/src/Appwrite/Models/Organization.php index 8842b34f..5eeef10d 100644 --- a/src/Appwrite/Models/Organization.php +++ b/src/Appwrite/Models/Organization.php @@ -18,7 +18,6 @@ * @param string $name team name. * @param int $total total number of team members. * @param Preferences $prefs team preferences as a key-value object - * @param int $billingBudget project budget limit * @param array $budgetAlerts project budget limit * @param string $billingPlan organization's billing plan id. * @param string $billingPlanId organization's billing plan id. @@ -27,26 +26,27 @@ * @param string $billingStartDate billing cycle start date. * @param string $billingCurrentInvoiceDate current invoice cycle start date. * @param string $billingNextInvoiceDate next invoice cycle start date. - * @param string $billingTrialStartDate start date of trial. * @param int $billingTrialDays number of trial days. * @param string $billingAggregationId current active aggregation id. * @param string $billingInvoiceId current active aggregation id. * @param string $paymentMethodId default payment method. - * @param string $billingAddressId default payment method. - * @param string $backupPaymentMethodId backup payment method. * @param string $status team status. - * @param string $remarks remarks on team status. - * @param string $agreementBAA organization agreements - * @param string $programManagerName program manager's name. - * @param string $programManagerCalendar program manager's calendar link. - * @param string $programDiscordChannelName program's discord channel name. - * @param string $programDiscordChannelUrl program's discord channel url. - * @param string $billingPlanDowngrade billing plan selected for downgrade. - * @param string $billingTaxId tax id * @param bool $markedForDeletion marked for deletion * @param string $platform product with which the organization is associated (appwrite or imagine) * @param array $projects selected projects + * @param int|null $billingBudget project budget limit. null when no budget is set. + * @param string|null $billingTrialStartDate start date of trial. + * @param string|null $billingAddressId default payment method. + * @param string|null $backupPaymentMethodId backup payment method. + * @param string|null $remarks remarks on team status. + * @param string|null $agreementBAA organization agreements + * @param string|null $programManagerName program manager's name. + * @param string|null $programManagerCalendar program manager's calendar link. + * @param string|null $programDiscordChannelName program's discord channel name. + * @param string|null $programDiscordChannelUrl program's discord channel url. * @param BillingLimits|null $billingLimits billing limits reached + * @param string|null $billingPlanDowngrade billing plan selected for downgrade. + * @param string|null $billingTaxId tax id */ public function __construct( public string $id, @@ -55,7 +55,6 @@ public function __construct( public string $name, public int $total, public Preferences $prefs, - public int $billingBudget, public array $budgetAlerts, public string $billingPlan, public string $billingPlanId, @@ -64,26 +63,27 @@ public function __construct( public string $billingStartDate, public string $billingCurrentInvoiceDate, public string $billingNextInvoiceDate, - public string $billingTrialStartDate, public int $billingTrialDays, public string $billingAggregationId, public string $billingInvoiceId, public string $paymentMethodId, - public string $billingAddressId, - public string $backupPaymentMethodId, public string $status, - public string $remarks, - public string $agreementBAA, - public string $programManagerName, - public string $programManagerCalendar, - public string $programDiscordChannelName, - public string $programDiscordChannelUrl, - public string $billingPlanDowngrade, - public string $billingTaxId, public bool $markedForDeletion, public string $platform, public array $projects, - public ?BillingLimits $billingLimits = null + public ?int $billingBudget = null, + public ?string $billingTrialStartDate = null, + public ?string $billingAddressId = null, + public ?string $backupPaymentMethodId = null, + public ?string $remarks = null, + public ?string $agreementBAA = null, + public ?string $programManagerName = null, + public ?string $programManagerCalendar = null, + public ?string $programDiscordChannelName = null, + public ?string $programDiscordChannelUrl = null, + public ?BillingLimits $billingLimits = null, + public ?string $billingPlanDowngrade = null, + public ?string $billingTaxId = null ) { } @@ -110,9 +110,6 @@ public static function from(array $data): static if (!array_key_exists('prefs', $data)) { throw new \InvalidArgumentException('Missing required field "prefs" for ' . static::class . '.'); } - if (!array_key_exists('billingBudget', $data)) { - throw new \InvalidArgumentException('Missing required field "billingBudget" for ' . static::class . '.'); - } if (!array_key_exists('budgetAlerts', $data)) { throw new \InvalidArgumentException('Missing required field "budgetAlerts" for ' . static::class . '.'); } @@ -137,9 +134,6 @@ public static function from(array $data): static if (!array_key_exists('billingNextInvoiceDate', $data)) { throw new \InvalidArgumentException('Missing required field "billingNextInvoiceDate" for ' . static::class . '.'); } - if (!array_key_exists('billingTrialStartDate', $data)) { - throw new \InvalidArgumentException('Missing required field "billingTrialStartDate" for ' . static::class . '.'); - } if (!array_key_exists('billingTrialDays', $data)) { throw new \InvalidArgumentException('Missing required field "billingTrialDays" for ' . static::class . '.'); } @@ -152,39 +146,9 @@ public static function from(array $data): static if (!array_key_exists('paymentMethodId', $data)) { throw new \InvalidArgumentException('Missing required field "paymentMethodId" for ' . static::class . '.'); } - if (!array_key_exists('billingAddressId', $data)) { - throw new \InvalidArgumentException('Missing required field "billingAddressId" for ' . static::class . '.'); - } - if (!array_key_exists('backupPaymentMethodId', $data)) { - throw new \InvalidArgumentException('Missing required field "backupPaymentMethodId" for ' . static::class . '.'); - } if (!array_key_exists('status', $data)) { throw new \InvalidArgumentException('Missing required field "status" for ' . static::class . '.'); } - if (!array_key_exists('remarks', $data)) { - throw new \InvalidArgumentException('Missing required field "remarks" for ' . static::class . '.'); - } - if (!array_key_exists('agreementBAA', $data)) { - throw new \InvalidArgumentException('Missing required field "agreementBAA" for ' . static::class . '.'); - } - if (!array_key_exists('programManagerName', $data)) { - throw new \InvalidArgumentException('Missing required field "programManagerName" for ' . static::class . '.'); - } - if (!array_key_exists('programManagerCalendar', $data)) { - throw new \InvalidArgumentException('Missing required field "programManagerCalendar" for ' . static::class . '.'); - } - if (!array_key_exists('programDiscordChannelName', $data)) { - throw new \InvalidArgumentException('Missing required field "programDiscordChannelName" for ' . static::class . '.'); - } - if (!array_key_exists('programDiscordChannelUrl', $data)) { - throw new \InvalidArgumentException('Missing required field "programDiscordChannelUrl" for ' . static::class . '.'); - } - if (!array_key_exists('billingPlanDowngrade', $data)) { - throw new \InvalidArgumentException('Missing required field "billingPlanDowngrade" for ' . static::class . '.'); - } - if (!array_key_exists('billingTaxId', $data)) { - throw new \InvalidArgumentException('Missing required field "billingTaxId" for ' . static::class . '.'); - } if (!array_key_exists('markedForDeletion', $data)) { throw new \InvalidArgumentException('Missing required field "markedForDeletion" for ' . static::class . '.'); } @@ -202,7 +166,6 @@ public static function from(array $data): static name: $data['name'], total: $data['total'], prefs: static::hydrateTypedValue(Preferences::class, $data['prefs']), - billingBudget: $data['billingBudget'], budgetAlerts: $data['budgetAlerts'], billingPlan: $data['billingPlan'], billingPlanId: $data['billingPlanId'], @@ -211,26 +174,27 @@ public static function from(array $data): static billingStartDate: $data['billingStartDate'], billingCurrentInvoiceDate: $data['billingCurrentInvoiceDate'], billingNextInvoiceDate: $data['billingNextInvoiceDate'], - billingTrialStartDate: $data['billingTrialStartDate'], billingTrialDays: $data['billingTrialDays'], billingAggregationId: $data['billingAggregationId'], billingInvoiceId: $data['billingInvoiceId'], paymentMethodId: $data['paymentMethodId'], - billingAddressId: $data['billingAddressId'], - backupPaymentMethodId: $data['backupPaymentMethodId'], status: $data['status'], - remarks: $data['remarks'], - agreementBAA: $data['agreementBAA'], - programManagerName: $data['programManagerName'], - programManagerCalendar: $data['programManagerCalendar'], - programDiscordChannelName: $data['programDiscordChannelName'], - programDiscordChannelUrl: $data['programDiscordChannelUrl'], - billingPlanDowngrade: $data['billingPlanDowngrade'], - billingTaxId: $data['billingTaxId'], markedForDeletion: $data['markedForDeletion'], platform: $data['platform'], projects: $data['projects'], - billingLimits: array_key_exists('billingLimits', $data) ? static::hydrateTypedValue(BillingLimits::class, $data['billingLimits'], true) : null + billingBudget: array_key_exists('billingBudget', $data) ? $data['billingBudget'] : null, + billingTrialStartDate: array_key_exists('billingTrialStartDate', $data) ? $data['billingTrialStartDate'] : null, + billingAddressId: array_key_exists('billingAddressId', $data) ? $data['billingAddressId'] : null, + backupPaymentMethodId: array_key_exists('backupPaymentMethodId', $data) ? $data['backupPaymentMethodId'] : null, + remarks: array_key_exists('remarks', $data) ? $data['remarks'] : null, + agreementBAA: array_key_exists('agreementBAA', $data) ? $data['agreementBAA'] : null, + programManagerName: array_key_exists('programManagerName', $data) ? $data['programManagerName'] : null, + programManagerCalendar: array_key_exists('programManagerCalendar', $data) ? $data['programManagerCalendar'] : null, + programDiscordChannelName: array_key_exists('programDiscordChannelName', $data) ? $data['programDiscordChannelName'] : null, + programDiscordChannelUrl: array_key_exists('programDiscordChannelUrl', $data) ? $data['programDiscordChannelUrl'] : null, + billingLimits: array_key_exists('billingLimits', $data) ? static::hydrateTypedValue(BillingLimits::class, $data['billingLimits'], true) : null, + billingPlanDowngrade: array_key_exists('billingPlanDowngrade', $data) ? $data['billingPlanDowngrade'] : null, + billingTaxId: array_key_exists('billingTaxId', $data) ? $data['billingTaxId'] : null ); } diff --git a/src/Appwrite/Models/PolicyMfaFactors.php b/src/Appwrite/Models/PolicyMfaFactors.php new file mode 100644 index 00000000..89b49271 --- /dev/null +++ b/src/Appwrite/Models/PolicyMfaFactors.php @@ -0,0 +1,75 @@ + $data + */ + public static function from(array $data): static + { + if (!array_key_exists('$id', $data)) { + throw new \InvalidArgumentException('Missing required field "$id" for ' . static::class . '.'); + } + if (!array_key_exists('totp', $data)) { + throw new \InvalidArgumentException('Missing required field "totp" for ' . static::class . '.'); + } + if (!array_key_exists('email', $data)) { + throw new \InvalidArgumentException('Missing required field "email" for ' . static::class . '.'); + } + if (!array_key_exists('phone', $data)) { + throw new \InvalidArgumentException('Missing required field "phone" for ' . static::class . '.'); + } + if (!array_key_exists('custom', $data)) { + throw new \InvalidArgumentException('Missing required field "custom" for ' . static::class . '.'); + } + + return new static( + id: $data['$id'], + totp: $data['totp'], + email: $data['email'], + phone: $data['phone'], + custom: $data['custom'] + ); + } + + /** + * @return array + */ + public function toArray(): array + { + $result = [ + '$id' => static::serializeValue($this->id), + 'totp' => static::serializeValue($this->totp), + 'email' => static::serializeValue($this->email), + 'phone' => static::serializeValue($this->phone), + 'custom' => static::serializeValue($this->custom) + ]; + + return $result; + } +} diff --git a/src/Appwrite/Models/Project.php b/src/Appwrite/Models/Project.php index f9013ca9..c63ab5bb 100644 --- a/src/Appwrite/Models/Project.php +++ b/src/Appwrite/Models/Project.php @@ -45,6 +45,7 @@ * @param string|null $oAuth2ServerAuthorizationUrl oauth2 server authorization url * @param array|null $oAuth2ServerScopes oauth2 server allowed scopes * @param array|null $oAuth2ServerDefaultScopes oauth2 server scopes used when an authorization request omits the scope parameter + * @param array|null $oAuth2ServerInstallationScopes scopes an application may request when installed on a team * @param array|null $oAuth2ServerAuthorizationDetailsTypes oauth2 server accepted rfc 9396 authorization_details types * @param int|null $oAuth2ServerAccessTokenDuration oauth2 server access token duration in seconds for confidential clients * @param int|null $oAuth2ServerRefreshTokenDuration oauth2 server refresh token duration in seconds for confidential clients @@ -92,6 +93,7 @@ public function __construct( public ?string $oAuth2ServerAuthorizationUrl = null, public ?array $oAuth2ServerScopes = null, public ?array $oAuth2ServerDefaultScopes = null, + public ?array $oAuth2ServerInstallationScopes = null, public ?array $oAuth2ServerAuthorizationDetailsTypes = null, public ?int $oAuth2ServerAccessTokenDuration = null, public ?int $oAuth2ServerRefreshTokenDuration = null, @@ -256,6 +258,7 @@ public static function from(array $data): static oAuth2ServerAuthorizationUrl: array_key_exists('oAuth2ServerAuthorizationUrl', $data) ? $data['oAuth2ServerAuthorizationUrl'] : null, oAuth2ServerScopes: array_key_exists('oAuth2ServerScopes', $data) ? $data['oAuth2ServerScopes'] : null, oAuth2ServerDefaultScopes: array_key_exists('oAuth2ServerDefaultScopes', $data) ? $data['oAuth2ServerDefaultScopes'] : null, + oAuth2ServerInstallationScopes: array_key_exists('oAuth2ServerInstallationScopes', $data) ? $data['oAuth2ServerInstallationScopes'] : null, oAuth2ServerAuthorizationDetailsTypes: array_key_exists('oAuth2ServerAuthorizationDetailsTypes', $data) ? $data['oAuth2ServerAuthorizationDetailsTypes'] : null, oAuth2ServerAccessTokenDuration: array_key_exists('oAuth2ServerAccessTokenDuration', $data) ? $data['oAuth2ServerAccessTokenDuration'] : null, oAuth2ServerRefreshTokenDuration: array_key_exists('oAuth2ServerRefreshTokenDuration', $data) ? $data['oAuth2ServerRefreshTokenDuration'] : null, @@ -310,6 +313,7 @@ public function toArray(): array 'oAuth2ServerAuthorizationUrl' => static::serializeValue($this->oAuth2ServerAuthorizationUrl), 'oAuth2ServerScopes' => static::serializeValue($this->oAuth2ServerScopes), 'oAuth2ServerDefaultScopes' => static::serializeValue($this->oAuth2ServerDefaultScopes), + 'oAuth2ServerInstallationScopes' => static::serializeValue($this->oAuth2ServerInstallationScopes), 'oAuth2ServerAuthorizationDetailsTypes' => static::serializeValue($this->oAuth2ServerAuthorizationDetailsTypes), 'oAuth2ServerAccessTokenDuration' => static::serializeValue($this->oAuth2ServerAccessTokenDuration), 'oAuth2ServerRefreshTokenDuration' => static::serializeValue($this->oAuth2ServerRefreshTokenDuration), diff --git a/src/Appwrite/Models/ProxyInvalidation.php b/src/Appwrite/Models/ProxyInvalidation.php new file mode 100644 index 00000000..6b35dfeb --- /dev/null +++ b/src/Appwrite/Models/ProxyInvalidation.php @@ -0,0 +1,68 @@ + $data + */ + public static function from(array $data): static + { + if (!array_key_exists('domain', $data)) { + throw new \InvalidArgumentException('Missing required field "domain" for ' . static::class . '.'); + } + if (!array_key_exists('type', $data)) { + throw new \InvalidArgumentException('Missing required field "type" for ' . static::class . '.'); + } + if (!array_key_exists('reference', $data)) { + throw new \InvalidArgumentException('Missing required field "reference" for ' . static::class . '.'); + } + if (!array_key_exists('status', $data)) { + throw new \InvalidArgumentException('Missing required field "status" for ' . static::class . '.'); + } + + return new static( + domain: $data['domain'], + type: $data['type'], + reference: $data['reference'], + status: $data['status'] + ); + } + + /** + * @return array + */ + public function toArray(): array + { + $result = [ + 'domain' => static::serializeValue($this->domain), + 'type' => static::serializeValue($this->type), + 'reference' => static::serializeValue($this->reference), + 'status' => static::serializeValue($this->status) + ]; + + return $result; + } +} diff --git a/src/Appwrite/Models/UsageBillingPlan.php b/src/Appwrite/Models/UsageBillingPlan.php index 790e95f0..827e4d60 100644 --- a/src/Appwrite/Models/UsageBillingPlan.php +++ b/src/Appwrite/Models/UsageBillingPlan.php @@ -14,28 +14,28 @@ * * @param AdditionalResource $bandwidth bandwidth additional resources * @param AdditionalResource $executions executions additional resources - * @param AdditionalResource $member member additional resources * @param AdditionalResource $realtime realtime additional resources * @param AdditionalResource $realtimeMessages realtime messages additional resources - * @param AdditionalResource $realtimeBandwidth realtime bandwidth additional resources * @param AdditionalResource $storage storage additional resources * @param AdditionalResource $users user additional resources * @param AdditionalResource $gBHours gbhour additional resources * @param AdditionalResource $imageTransformations image transformation additional resources - * @param AdditionalResource $credits credits additional resources + * @param AdditionalResource|null $member member additional resources + * @param AdditionalResource|null $realtimeBandwidth realtime bandwidth additional resources + * @param AdditionalResource|null $credits credits additional resources */ public function __construct( public AdditionalResource $bandwidth, public AdditionalResource $executions, - public AdditionalResource $member, public AdditionalResource $realtime, public AdditionalResource $realtimeMessages, - public AdditionalResource $realtimeBandwidth, public AdditionalResource $storage, public AdditionalResource $users, public AdditionalResource $gBHours, public AdditionalResource $imageTransformations, - public AdditionalResource $credits + public ?AdditionalResource $member = null, + public ?AdditionalResource $realtimeBandwidth = null, + public ?AdditionalResource $credits = null ) { } @@ -50,18 +50,12 @@ public static function from(array $data): static if (!array_key_exists('executions', $data)) { throw new \InvalidArgumentException('Missing required field "executions" for ' . static::class . '.'); } - if (!array_key_exists('member', $data)) { - throw new \InvalidArgumentException('Missing required field "member" for ' . static::class . '.'); - } if (!array_key_exists('realtime', $data)) { throw new \InvalidArgumentException('Missing required field "realtime" for ' . static::class . '.'); } if (!array_key_exists('realtimeMessages', $data)) { throw new \InvalidArgumentException('Missing required field "realtimeMessages" for ' . static::class . '.'); } - if (!array_key_exists('realtimeBandwidth', $data)) { - throw new \InvalidArgumentException('Missing required field "realtimeBandwidth" for ' . static::class . '.'); - } if (!array_key_exists('storage', $data)) { throw new \InvalidArgumentException('Missing required field "storage" for ' . static::class . '.'); } @@ -74,22 +68,19 @@ public static function from(array $data): static if (!array_key_exists('imageTransformations', $data)) { throw new \InvalidArgumentException('Missing required field "imageTransformations" for ' . static::class . '.'); } - if (!array_key_exists('credits', $data)) { - throw new \InvalidArgumentException('Missing required field "credits" for ' . static::class . '.'); - } return new static( bandwidth: static::hydrateTypedValue(AdditionalResource::class, $data['bandwidth']), executions: static::hydrateTypedValue(AdditionalResource::class, $data['executions']), - member: static::hydrateTypedValue(AdditionalResource::class, $data['member']), realtime: static::hydrateTypedValue(AdditionalResource::class, $data['realtime']), realtimeMessages: static::hydrateTypedValue(AdditionalResource::class, $data['realtimeMessages']), - realtimeBandwidth: static::hydrateTypedValue(AdditionalResource::class, $data['realtimeBandwidth']), storage: static::hydrateTypedValue(AdditionalResource::class, $data['storage']), users: static::hydrateTypedValue(AdditionalResource::class, $data['users']), gBHours: static::hydrateTypedValue(AdditionalResource::class, $data['GBHours']), imageTransformations: static::hydrateTypedValue(AdditionalResource::class, $data['imageTransformations']), - credits: static::hydrateTypedValue(AdditionalResource::class, $data['credits']) + member: array_key_exists('member', $data) ? static::hydrateTypedValue(AdditionalResource::class, $data['member'], true) : null, + realtimeBandwidth: array_key_exists('realtimeBandwidth', $data) ? static::hydrateTypedValue(AdditionalResource::class, $data['realtimeBandwidth'], true) : null, + credits: array_key_exists('credits', $data) ? static::hydrateTypedValue(AdditionalResource::class, $data['credits'], true) : null ); } diff --git a/src/Appwrite/Services/Account.php b/src/Appwrite/Services/Account.php index 441e05da..fb80d415 100644 --- a/src/Appwrite/Services/Account.php +++ b/src/Appwrite/Services/Account.php @@ -477,51 +477,6 @@ public function deleteIdentity(string $identityId): string } - /** - * Use this endpoint to create a JSON Web Token. You can use the resulting JWT - * to authenticate on behalf of the current user when working with the - * Appwrite server-side API and SDKs. The JWT secret is valid for 15 minutes - * from its creation and will be invalid if the user will logout in that time - * frame. - * - * @param ?int $duration - * @throws AppwriteException - * @return \Appwrite\Models\Jwt - */ - public function createJWT(?int $duration = null): \Appwrite\Models\Jwt - { - $apiPath = str_replace( - [], - [], - '/account/jwts' - ); - - $apiParams = []; - - if (!is_null($duration)) { - $apiParams['duration'] = $duration; - } - - $apiHeaders = []; - $apiHeaders['X-Appwrite-Project'] = $this->client->getConfig('project'); - $apiHeaders['content-type'] = 'application/json'; - $apiHeaders['accept'] = 'application/json'; - - $response = $this->client->call( - Client::METHOD_POST, - $apiPath, - $apiHeaders, - $apiParams - ); - - if (!is_array($response)) { - throw new \UnexpectedValueException('Expected array response when hydrating a response model.'); - } - - return \Appwrite\Models\Jwt::from($response); - - } - /** * Get the list of latest security activity logs for the currently logged in * user. Each log returns user IP address, location and date and time of log. diff --git a/src/Appwrite/Services/Activities.php b/src/Appwrite/Services/Activities.php index 9111586a..925c4c23 100644 --- a/src/Appwrite/Services/Activities.php +++ b/src/Appwrite/Services/Activities.php @@ -17,11 +17,11 @@ public function __construct(Client $client) /** * List all events for selected filters. * - * @param ?string $queries + * @param ?array $queries * @throws AppwriteException * @return \Appwrite\Models\ActivityEventList */ - public function listEvents(?string $queries = null): \Appwrite\Models\ActivityEventList + public function listEvents(?array $queries = null): \Appwrite\Models\ActivityEventList { $apiPath = str_replace( [], diff --git a/src/Appwrite/Services/Apps.php b/src/Appwrite/Services/Apps.php index d0a9c896..69b47975 100644 --- a/src/Appwrite/Services/Apps.php +++ b/src/Appwrite/Services/Apps.php @@ -454,7 +454,8 @@ public function delete(string $appId): string /** * List installations of an application. Requires an app key sent in the - * `X-Appwrite-Key` header alongside the `X-Appwrite-App` header. + * `X-Appwrite-Key` header alongside the `X-Appwrite-App` header, or a caller + * with update access to the app. * * @param string $appId * @param ?array $queries @@ -502,7 +503,8 @@ public function listInstallations(string $appId, ?array $queries = null, ?bool $ /** * Get an installation of an application by its unique ID. Requires an app key - * sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header. + * sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header, + * or a caller with update access to the app. * * @param string $appId * @param string $installationId @@ -540,14 +542,53 @@ public function getInstallation(string $appId, string $installationId): \Appwrit } + /** + * Delete an installation of an application by its unique ID. Requires a + * caller with update access to the app. Previously issued installation access + * tokens are revoked. + * + * @param string $appId + * @param string $installationId + * @throws AppwriteException + * @return string + */ + public function deleteInstallation(string $appId, string $installationId): string + { + $apiPath = str_replace( + ['{appId}', '{installationId}'], + [$appId, $installationId], + '/apps/{appId}/installations/{installationId}' + ); + + $apiParams = []; + $apiParams['appId'] = $appId; + $apiParams['installationId'] = $installationId; + + $apiHeaders = []; + $apiHeaders['X-Appwrite-Project'] = $this->client->getConfig('project'); + $apiHeaders['content-type'] = 'application/json'; + $apiHeaders['accept'] = 'application/json'; + + $response = $this->client->call( + Client::METHOD_DELETE, + $apiPath, + $apiHeaders, + $apiParams + ); + + return $response; + + } + /** * Create a token for an installation of an application. Requires an app key - * sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header. - * The returned token carries the scopes and authorization details granted to - * the installation, and can be used as an `Authorization: Bearer` header - * everywhere OAuth2 access tokens are accepted. Multiple tokens can be active - * for the same installation at once; each token stays valid until it expires - * or the installation is updated or deleted. + * sent in the `X-Appwrite-Key` header alongside the `X-Appwrite-App` header, + * or a caller with update access to the app. The returned token carries the + * scopes and authorization details granted to the installation, and can be + * used as an `Authorization: Bearer` header everywhere OAuth2 access tokens + * are accepted. Multiple tokens can be active for the same installation at + * once; each token stays valid until it expires or the installation is + * updated or deleted. * * @param string $appId * @param string $installationId diff --git a/src/Appwrite/Services/Backups.php b/src/Appwrite/Services/Backups.php index 527176ed..ce513ff4 100644 --- a/src/Appwrite/Services/Backups.php +++ b/src/Appwrite/Services/Backups.php @@ -386,9 +386,10 @@ public function deletePolicy(string $policyId): string * Create and trigger a new restoration for a backup on a project. * * For a backup of one database, the restoration resolves its destination - * before it is queued. Pass `newResourceId` to restore into that database ID, - * including the archived database ID to overwrite it. When `newResourceId` is - * omitted, a new database ID is generated and returned in `options`. + * before it is queued. When `newResourceId` is omitted, the archived database + * is restored in place and its own ID is returned in `options`. Pass a + * different `newResourceId` to restore alongside it as a new database + * instead. * * The restoration migration records the archived database in `resourceId` and * `resourceType`, and the resolved database in `destinationResourceId` and @@ -405,9 +406,14 @@ public function deletePolicy(string $policyId): string * operational `resourceType` of a table migration is not rewritten to * `tablesdb`. * - * When restoring a DocumentsDB or VectorsDB database to a new resource from a - * dedicated source, the restore provisions a fresh dedicated backing database - * at the source database's own specification. + * When restoring a DocumentsDB or VectorsDB database from a dedicated source, + * the restore provisions a fresh dedicated backing database at the source + * database's own specification and lands the data there. An in-place restore + * swaps the database onto that backing only once the restore has succeeded, + * and retires the backing it displaced only once that swap is confirmed, so + * the source keeps serving its own data until the restored data is in place + * and any failure leaves it untouched. A serverless source has no dedicated + * backing to clone and restores onto the archived database instead. * * * @param string $archiveId diff --git a/src/Appwrite/Services/Embeddings.php b/src/Appwrite/Services/Embeddings.php new file mode 100644 index 00000000..f1f65d2c --- /dev/null +++ b/src/Appwrite/Services/Embeddings.php @@ -0,0 +1,63 @@ +client->getConfig('project'); + $apiHeaders['content-type'] = 'application/json'; + $apiHeaders['accept'] = 'application/json'; + + $response = $this->client->call( + Client::METHOD_POST, + $apiPath, + $apiHeaders, + $apiParams + ); + + if (!is_array($response)) { + throw new \UnexpectedValueException('Expected array response when hydrating a response model.'); + } + + return \Appwrite\Models\EmbeddingList::from($response); + + } +} diff --git a/src/Appwrite/Services/Project.php b/src/Appwrite/Services/Project.php index 8ba6055b..e398c92e 100644 --- a/src/Appwrite/Services/Project.php +++ b/src/Appwrite/Services/Project.php @@ -176,54 +176,6 @@ public function listKeys(?array $queries = null, ?bool $total = null): \Appwrite } - /** - * Create a new API key. It's recommended to have multiple API keys with - * strict scopes for separate functions within your project. - * - * You can also create an ephemeral API key if you need a short-lived key - * instead. - * - * @param string $keyId - * @param string $name - * @param array $scopes - * @param ?string $expire - * @throws AppwriteException - * @return \Appwrite\Models\Key - */ - public function createKey(string $keyId, string $name, array $scopes, ?string $expire = null): \Appwrite\Models\Key - { - $apiPath = str_replace( - [], - [], - '/project/keys' - ); - - $apiParams = []; - $apiParams['keyId'] = $keyId; - $apiParams['name'] = $name; - $apiParams['scopes'] = $scopes; - $apiParams['expire'] = $expire; - - $apiHeaders = []; - $apiHeaders['X-Appwrite-Project'] = $this->client->getConfig('project'); - $apiHeaders['content-type'] = 'application/json'; - $apiHeaders['accept'] = 'application/json'; - - $response = $this->client->call( - Client::METHOD_POST, - $apiPath, - $apiHeaders, - $apiParams - ); - - if (!is_array($response)) { - throw new \UnexpectedValueException('Expected array response when hydrating a response model.'); - } - - return \Appwrite\Models\Key::from($response); - - } - /** * Create a new ephemeral API key. It's recommended to have multiple API keys * with strict scopes for separate functions within your project. @@ -688,10 +640,11 @@ public function listOAuth2Providers(?array $queries = null, ?bool $total = null) * @param ?string $userCodeFormat * @param ?int $deviceCodeDuration * @param ?array $defaultScopes + * @param ?array $installationScopes * @throws AppwriteException * @return \Appwrite\Models\Project */ - public function updateOAuth2Server(bool $enabled, string $authorizationUrl, ?array $scopes = null, ?array $authorizationDetailsTypes = null, ?int $accessTokenDuration = null, ?int $refreshTokenDuration = null, ?int $publicAccessTokenDuration = null, ?int $publicRefreshTokenDuration = null, ?int $installationAccessTokenDuration = null, ?bool $confidentialPkce = null, ?string $verificationUrl = null, ?int $userCodeLength = null, ?string $userCodeFormat = null, ?int $deviceCodeDuration = null, ?array $defaultScopes = null): \Appwrite\Models\Project + public function updateOAuth2Server(bool $enabled, string $authorizationUrl, ?array $scopes = null, ?array $authorizationDetailsTypes = null, ?int $accessTokenDuration = null, ?int $refreshTokenDuration = null, ?int $publicAccessTokenDuration = null, ?int $publicRefreshTokenDuration = null, ?int $installationAccessTokenDuration = null, ?bool $confidentialPkce = null, ?string $verificationUrl = null, ?int $userCodeLength = null, ?string $userCodeFormat = null, ?int $deviceCodeDuration = null, ?array $defaultScopes = null, ?array $installationScopes = null): \Appwrite\Models\Project { $apiPath = str_replace( [], @@ -731,6 +684,10 @@ public function updateOAuth2Server(bool $enabled, string $authorizationUrl, ?arr $apiParams['defaultScopes'] = $defaultScopes; } + if (!is_null($installationScopes)) { + $apiParams['installationScopes'] = $installationScopes; + } + $apiHeaders = []; $apiHeaders['X-Appwrite-Project'] = $this->client->getConfig('project'); $apiHeaders['content-type'] = 'application/json'; @@ -3633,6 +3590,66 @@ public function updateMembershipPrivacyPolicy(?bool $userId = null, ?bool $userE } + /** + * Updating this policy allows you to control which factors users can use to + * complete an MFA challenge. Disabled factors cannot be used to create a + * challenge and are reported as unavailable when listing factors. The custom + * factor is disabled by default; enable it to deliver challenge codes through + * your own channel. Recovery codes always remain available as a fallback. + * + * @param ?bool $totp + * @param ?bool $email + * @param ?bool $phone + * @param ?bool $custom + * @throws AppwriteException + * @return \Appwrite\Models\Project + */ + public function updateMFAFactorsPolicy(?bool $totp = null, ?bool $email = null, ?bool $phone = null, ?bool $custom = null): \Appwrite\Models\Project + { + $apiPath = str_replace( + [], + [], + '/project/policies/mfa-factors' + ); + + $apiParams = []; + + if (!is_null($totp)) { + $apiParams['totp'] = $totp; + } + + if (!is_null($email)) { + $apiParams['email'] = $email; + } + + if (!is_null($phone)) { + $apiParams['phone'] = $phone; + } + + if (!is_null($custom)) { + $apiParams['custom'] = $custom; + } + + $apiHeaders = []; + $apiHeaders['X-Appwrite-Project'] = $this->client->getConfig('project'); + $apiHeaders['content-type'] = 'application/json'; + $apiHeaders['accept'] = 'application/json'; + + $response = $this->client->call( + Client::METHOD_PATCH, + $apiPath, + $apiHeaders, + $apiParams + ); + + if (!is_array($response)) { + throw new \UnexpectedValueException('Expected array response when hydrating a response model.'); + } + + return \Appwrite\Models\Project::from($response); + + } + /** * Updating this policy allows you to control if new passwords are checked * against most common passwords dictionary. When enabled, and user changes @@ -3946,11 +3963,11 @@ public function updateSessionInvalidationPolicy(bool $enabled): \Appwrite\Models * Update the maximum number of sessions allowed per user. When the limit is * hit, the oldest session will be deleted to make room for new one. * - * @param ?int $total + * @param int $total * @throws AppwriteException * @return \Appwrite\Models\Project */ - public function updateSessionLimitPolicy(?int $total): \Appwrite\Models\Project + public function updateSessionLimitPolicy(int $total): \Appwrite\Models\Project { $apiPath = str_replace( [], @@ -4027,9 +4044,9 @@ public function updateUserLimitPolicy(?int $total): \Appwrite\Models\Project * * @param ProjectPolicyId $policyId * @throws AppwriteException - * @return \Appwrite\Models\PolicyPasswordDictionary|\Appwrite\Models\PolicyPasswordHistory|\Appwrite\Models\PolicyPasswordStrength|\Appwrite\Models\PolicyPasswordPersonalData|\Appwrite\Models\PolicySessionAlert|\Appwrite\Models\PolicySessionDuration|\Appwrite\Models\PolicySessionInvalidation|\Appwrite\Models\PolicySessionLimit|\Appwrite\Models\PolicyUserLimit|\Appwrite\Models\PolicyMembershipPrivacy|\Appwrite\Models\PolicyDenyAliasedEmail|\Appwrite\Models\PolicyDenyDisposableEmail|\Appwrite\Models\PolicyDenyFreeEmail|\Appwrite\Models\PolicyDenyCorporateEmail + * @return \Appwrite\Models\PolicyPasswordDictionary|\Appwrite\Models\PolicyPasswordHistory|\Appwrite\Models\PolicyPasswordStrength|\Appwrite\Models\PolicyPasswordPersonalData|\Appwrite\Models\PolicySessionAlert|\Appwrite\Models\PolicySessionDuration|\Appwrite\Models\PolicySessionInvalidation|\Appwrite\Models\PolicySessionLimit|\Appwrite\Models\PolicyUserLimit|\Appwrite\Models\PolicyMembershipPrivacy|\Appwrite\Models\PolicyMfaFactors|\Appwrite\Models\PolicyDenyAliasedEmail|\Appwrite\Models\PolicyDenyDisposableEmail|\Appwrite\Models\PolicyDenyFreeEmail|\Appwrite\Models\PolicyDenyCorporateEmail */ - public function getPolicy(ProjectPolicyId $policyId): \Appwrite\Models\PolicyPasswordDictionary|\Appwrite\Models\PolicyPasswordHistory|\Appwrite\Models\PolicyPasswordStrength|\Appwrite\Models\PolicyPasswordPersonalData|\Appwrite\Models\PolicySessionAlert|\Appwrite\Models\PolicySessionDuration|\Appwrite\Models\PolicySessionInvalidation|\Appwrite\Models\PolicySessionLimit|\Appwrite\Models\PolicyUserLimit|\Appwrite\Models\PolicyMembershipPrivacy|\Appwrite\Models\PolicyDenyAliasedEmail|\Appwrite\Models\PolicyDenyDisposableEmail|\Appwrite\Models\PolicyDenyFreeEmail|\Appwrite\Models\PolicyDenyCorporateEmail + public function getPolicy(ProjectPolicyId $policyId): \Appwrite\Models\PolicyPasswordDictionary|\Appwrite\Models\PolicyPasswordHistory|\Appwrite\Models\PolicyPasswordStrength|\Appwrite\Models\PolicyPasswordPersonalData|\Appwrite\Models\PolicySessionAlert|\Appwrite\Models\PolicySessionDuration|\Appwrite\Models\PolicySessionInvalidation|\Appwrite\Models\PolicySessionLimit|\Appwrite\Models\PolicyUserLimit|\Appwrite\Models\PolicyMembershipPrivacy|\Appwrite\Models\PolicyMfaFactors|\Appwrite\Models\PolicyDenyAliasedEmail|\Appwrite\Models\PolicyDenyDisposableEmail|\Appwrite\Models\PolicyDenyFreeEmail|\Appwrite\Models\PolicyDenyCorporateEmail { $apiPath = str_replace( ['{policyId}'], @@ -4095,6 +4112,10 @@ public function getPolicy(ProjectPolicyId $policyId): \Appwrite\Models\PolicyPas return \Appwrite\Models\PolicyMembershipPrivacy::from($response); } + if (($response['$id'] ?? null) === 'mfa-factors') { + return \Appwrite\Models\PolicyMfaFactors::from($response); + } + if (($response['$id'] ?? null) === 'deny-aliased-email') { return \Appwrite\Models\PolicyDenyAliasedEmail::from($response); } diff --git a/src/Appwrite/Services/Proxy.php b/src/Appwrite/Services/Proxy.php index 074f7fa9..691efb29 100644 --- a/src/Appwrite/Services/Proxy.php +++ b/src/Appwrite/Services/Proxy.php @@ -6,6 +6,7 @@ use Appwrite\Client; use Appwrite\Service; use Appwrite\InputFile; +use Appwrite\Enums\InvalidationType; use Appwrite\Enums\StatusCode; use Appwrite\Enums\ProxyResourceType; @@ -16,6 +17,55 @@ public function __construct(Client $client) parent::__construct($client); } + /** + * Create a new CDN cache invalidation for a domain. Executes a hard purge of + * cached content. + * + * Depending on type, the invalidation purges a single cache tag, a single URL + * path, or all cached content for the domain. + * + * @param string $domain + * @param InvalidationType $type + * @param ?string $reference + * @throws AppwriteException + * @return \Appwrite\Models\ProxyInvalidation + */ + public function createInvalidation(string $domain, InvalidationType $type, ?string $reference = null): \Appwrite\Models\ProxyInvalidation + { + $apiPath = str_replace( + [], + [], + '/proxy/invalidations' + ); + + $apiParams = []; + $apiParams['domain'] = $domain; + $apiParams['type'] = $type; + + if (!is_null($reference)) { + $apiParams['reference'] = $reference; + } + + $apiHeaders = []; + $apiHeaders['X-Appwrite-Project'] = $this->client->getConfig('project'); + $apiHeaders['content-type'] = 'application/json'; + $apiHeaders['accept'] = 'application/json'; + + $response = $this->client->call( + Client::METHOD_POST, + $apiPath, + $apiHeaders, + $apiParams + ); + + if (!is_array($response)) { + throw new \UnexpectedValueException('Expected array response when hydrating a response model.'); + } + + return \Appwrite\Models\ProxyInvalidation::from($response); + + } + /** * Get a list of all the proxy rules. You can use the query params to filter * your results. diff --git a/src/Appwrite/Services/Storage.php b/src/Appwrite/Services/Storage.php index b71fff62..1c273971 100644 --- a/src/Appwrite/Services/Storage.php +++ b/src/Appwrite/Services/Storage.php @@ -380,10 +380,11 @@ public function listFiles(string $bucketId, ?array $queries = null, ?string $sea * @param string $fileId * @param InputFile $file * @param ?array $permissions + * @param ?string $folder * @throws AppwriteException * @return \Appwrite\Models\File */ - public function createFile(string $bucketId, string $fileId, InputFile $file, ?array $permissions = null, ?callable $onProgress = null): \Appwrite\Models\File + public function createFile(string $bucketId, string $fileId, InputFile $file, ?array $permissions = null, ?string $folder = null, ?callable $onProgress = null): \Appwrite\Models\File { $apiPath = str_replace( ['{bucketId}'], @@ -400,6 +401,10 @@ public function createFile(string $bucketId, string $fileId, InputFile $file, ?a $apiParams['permissions'] = $permissions; } + if (!is_null($folder)) { + $apiParams['folder'] = $folder; + } + $apiHeaders = []; $apiHeaders['X-Appwrite-Project'] = $this->client->getConfig('project'); $apiHeaders['content-type'] = 'multipart/form-data'; diff --git a/src/Appwrite/Services/TablesDB.php b/src/Appwrite/Services/TablesDB.php index f2c996ff..c8a8d30e 100644 --- a/src/Appwrite/Services/TablesDB.php +++ b/src/Appwrite/Services/TablesDB.php @@ -78,10 +78,11 @@ public function list(?array $queries = null, ?string $search = null, ?bool $tota * @param ?bool $enabled * @param ?string $specification * @param ?int $replicas + * @param ?string $syncMode * @throws AppwriteException * @return \Appwrite\Models\Database */ - public function create(string $databaseId, string $name, ?bool $enabled = null, ?string $specification = null, ?int $replicas = null): \Appwrite\Models\Database + public function create(string $databaseId, string $name, ?bool $enabled = null, ?string $specification = null, ?int $replicas = null, ?string $syncMode = null): \Appwrite\Models\Database { $apiPath = str_replace( [], @@ -104,6 +105,7 @@ public function create(string $databaseId, string $name, ?bool $enabled = null, if (!is_null($replicas)) { $apiParams['replicas'] = $replicas; } + $apiParams['syncMode'] = $syncMode; $apiHeaders = []; $apiHeaders['X-Appwrite-Project'] = $this->client->getConfig('project'); @@ -448,11 +450,13 @@ public function get(string $databaseId): \Appwrite\Models\Database * @param string $databaseId * @param ?string $name * @param ?bool $enabled + * @param ?string $specification * @param ?int $replicas + * @param ?string $syncMode * @throws AppwriteException * @return \Appwrite\Models\Database */ - public function update(string $databaseId, ?string $name = null, ?bool $enabled = null, ?int $replicas = null): \Appwrite\Models\Database + public function update(string $databaseId, ?string $name = null, ?bool $enabled = null, ?string $specification = null, ?int $replicas = null, ?string $syncMode = null): \Appwrite\Models\Database { $apiPath = str_replace( ['{databaseId}'], @@ -470,7 +474,9 @@ public function update(string $databaseId, ?string $name = null, ?bool $enabled if (!is_null($enabled)) { $apiParams['enabled'] = $enabled; } + $apiParams['specification'] = $specification; $apiParams['replicas'] = $replicas; + $apiParams['syncMode'] = $syncMode; $apiHeaders = []; $apiHeaders['X-Appwrite-Project'] = $this->client->getConfig('project'); @@ -529,7 +535,9 @@ public function delete(string $databaseId): string /** * Trigger a manual failover for a dedicated database with high availability * enabled. Promotes a replica to primary. The failover runs asynchronously; - * poll the database document for status updates. + * poll the database document for status updates. A database left + * mid-operation by a failover that did not finish also accepts this call as a + * repair, provided `targetReplicaId` names the member to promote. * * @param string $databaseId * @param ?string $targetReplicaId @@ -568,6 +576,267 @@ public function createFailover(string $databaseId, ?string $targetReplicaId = nu } + /** + * List the dedicated migrations for a TablesDB database. A database has at + * most one in-flight migration. + * + * @param string $databaseId + * @throws AppwriteException + * @return \Appwrite\Models\DatabaseMigrationList + */ + public function listMigrations(string $databaseId): \Appwrite\Models\DatabaseMigrationList + { + $apiPath = str_replace( + ['{databaseId}'], + [$databaseId], + '/tablesdb/{databaseId}/migrations' + ); + + $apiParams = []; + $apiParams['databaseId'] = $databaseId; + + $apiHeaders = []; + $apiHeaders['X-Appwrite-Project'] = $this->client->getConfig('project'); + $apiHeaders['accept'] = 'application/json'; + + $response = $this->client->call( + Client::METHOD_GET, + $apiPath, + $apiHeaders, + $apiParams + ); + + if (!is_array($response)) { + throw new \UnexpectedValueException('Expected array response when hydrating a response model.'); + } + + return \Appwrite\Models\DatabaseMigrationList::from($response); + + } + + /** + * Start migrating a serverless TablesDB database onto a dedicated MySQL + * compute. Data is copied to the target while the source stays live, with a + * brief read-only window during cutover. + * + * @param string $databaseId + * @param string $specification + * @param ?bool $autoCutover + * @throws AppwriteException + * @return \Appwrite\Models\DatabaseMigration + */ + public function createMigration(string $databaseId, string $specification, ?bool $autoCutover = null): \Appwrite\Models\DatabaseMigration + { + $apiPath = str_replace( + ['{databaseId}'], + [$databaseId], + '/tablesdb/{databaseId}/migrations' + ); + + $apiParams = []; + $apiParams['databaseId'] = $databaseId; + $apiParams['specification'] = $specification; + + if (!is_null($autoCutover)) { + $apiParams['autoCutover'] = $autoCutover; + } + + $apiHeaders = []; + $apiHeaders['X-Appwrite-Project'] = $this->client->getConfig('project'); + $apiHeaders['content-type'] = 'application/json'; + $apiHeaders['accept'] = 'application/json'; + + $response = $this->client->call( + Client::METHOD_POST, + $apiPath, + $apiHeaders, + $apiParams + ); + + if (!is_array($response)) { + throw new \UnexpectedValueException('Expected array response when hydrating a response model.'); + } + + return \Appwrite\Models\DatabaseMigration::from($response); + + } + + /** + * Get a single dedicated migration for a TablesDB database by its ID. + * + * @param string $databaseId + * @param string $migrationId + * @throws AppwriteException + * @return \Appwrite\Models\DatabaseMigration + */ + public function getMigration(string $databaseId, string $migrationId): \Appwrite\Models\DatabaseMigration + { + $apiPath = str_replace( + ['{databaseId}', '{migrationId}'], + [$databaseId, $migrationId], + '/tablesdb/{databaseId}/migrations/{migrationId}' + ); + + $apiParams = []; + $apiParams['databaseId'] = $databaseId; + $apiParams['migrationId'] = $migrationId; + + $apiHeaders = []; + $apiHeaders['X-Appwrite-Project'] = $this->client->getConfig('project'); + $apiHeaders['accept'] = 'application/json'; + + $response = $this->client->call( + Client::METHOD_GET, + $apiPath, + $apiHeaders, + $apiParams + ); + + if (!is_array($response)) { + throw new \UnexpectedValueException('Expected array response when hydrating a response model.'); + } + + return \Appwrite\Models\DatabaseMigration::from($response); + + } + + /** + * Abort an in-flight TablesDB dedicated migration. Only allowed before + * cutover; once the migration has cut over it cannot be aborted. + * + * @param string $databaseId + * @param string $migrationId + * @throws AppwriteException + * @return string + */ + public function deleteMigration(string $databaseId, string $migrationId): string + { + $apiPath = str_replace( + ['{databaseId}', '{migrationId}'], + [$databaseId, $migrationId], + '/tablesdb/{databaseId}/migrations/{migrationId}' + ); + + $apiParams = []; + $apiParams['databaseId'] = $databaseId; + $apiParams['migrationId'] = $migrationId; + + $apiHeaders = []; + $apiHeaders['X-Appwrite-Project'] = $this->client->getConfig('project'); + $apiHeaders['content-type'] = 'application/json'; + $apiHeaders['accept'] = 'application/json'; + + $response = $this->client->call( + Client::METHOD_DELETE, + $apiPath, + $apiHeaders, + $apiParams + ); + + return $response; + + } + + /** + * Cut a verified TablesDB migration over to its dedicated compute. Only + * applies to a migration created with `autoCutover` disabled, which waits at + * `ready_to_cutover` until this is called. The routing flip happens shortly + * after this returns, with a brief read-only window. One call buys one + * attempt: a cutover that fails a check returns the migration to `verifying` + * and parks it again, so call this once more to retry. + * + * @param string $databaseId + * @param string $migrationId + * @throws AppwriteException + * @return \Appwrite\Models\DatabaseMigration + */ + public function cutoverMigration(string $databaseId, string $migrationId): \Appwrite\Models\DatabaseMigration + { + $apiPath = str_replace( + ['{databaseId}', '{migrationId}'], + [$databaseId, $migrationId], + '/tablesdb/{databaseId}/migrations/{migrationId}/cutover' + ); + + $apiParams = []; + $apiParams['databaseId'] = $databaseId; + $apiParams['migrationId'] = $migrationId; + + $apiHeaders = []; + $apiHeaders['X-Appwrite-Project'] = $this->client->getConfig('project'); + $apiHeaders['content-type'] = 'application/json'; + $apiHeaders['accept'] = 'application/json'; + + $response = $this->client->call( + Client::METHOD_POST, + $apiPath, + $apiHeaders, + $apiParams + ); + + if (!is_array($response)) { + throw new \UnexpectedValueException('Expected array response when hydrating a response model.'); + } + + return \Appwrite\Models\DatabaseMigration::from($response); + + } + + /** + * List the lifecycle operations recorded for a dedicated database, newest + * first. Every provision, update, restore, backup and replication action is + * recorded here with its outcome, including an attempt that was abandoned + * because another worker took over the database. + * + * @param string $databaseId + * @param ?string $status + * @param ?int $limit + * @param ?int $offset + * @throws AppwriteException + * @return \Appwrite\Models\DedicatedDatabaseOperationList + */ + public function listOperations(string $databaseId, ?string $status = null, ?int $limit = null, ?int $offset = null): \Appwrite\Models\DedicatedDatabaseOperationList + { + $apiPath = str_replace( + ['{databaseId}'], + [$databaseId], + '/tablesdb/{databaseId}/operations' + ); + + $apiParams = []; + $apiParams['databaseId'] = $databaseId; + + if (!is_null($status)) { + $apiParams['status'] = $status; + } + + if (!is_null($limit)) { + $apiParams['limit'] = $limit; + } + + if (!is_null($offset)) { + $apiParams['offset'] = $offset; + } + + $apiHeaders = []; + $apiHeaders['X-Appwrite-Project'] = $this->client->getConfig('project'); + $apiHeaders['accept'] = 'application/json'; + + $response = $this->client->call( + Client::METHOD_GET, + $apiPath, + $apiHeaders, + $apiParams + ); + + if (!is_array($response)) { + throw new \UnexpectedValueException('Expected array response when hydrating a response model.'); + } + + return \Appwrite\Models\DedicatedDatabaseOperationList::from($response); + + } + /** * Get high availability status for a dedicated database. Returns replica * statuses, replication lag, and sync mode. diff --git a/src/Appwrite/Services/Users.php b/src/Appwrite/Services/Users.php index 81e7f97f..a7ee2eda 100644 --- a/src/Appwrite/Services/Users.php +++ b/src/Appwrite/Services/Users.php @@ -1004,6 +1004,46 @@ public function deleteMFAAuthenticator(string $userId, AuthenticatorType $type): } + /** + * Get a custom MFA challenge for a user, including the code to be delivered + * through your own channel. + * + * @param string $userId + * @param string $challengeId + * @throws AppwriteException + * @return \Appwrite\Models\MfaChallengeSecret + */ + public function getMFAChallenge(string $userId, string $challengeId): \Appwrite\Models\MfaChallengeSecret + { + $apiPath = str_replace( + ['{userId}', '{challengeId}'], + [$userId, $challengeId], + '/users/{userId}/mfa/challenges/{challengeId}' + ); + + $apiParams = []; + $apiParams['userId'] = $userId; + $apiParams['challengeId'] = $challengeId; + + $apiHeaders = []; + $apiHeaders['X-Appwrite-Project'] = $this->client->getConfig('project'); + $apiHeaders['accept'] = 'application/json'; + + $response = $this->client->call( + Client::METHOD_GET, + $apiPath, + $apiHeaders, + $apiParams + ); + + if (!is_array($response)) { + throw new \UnexpectedValueException('Expected array response when hydrating a response model.'); + } + + return \Appwrite\Models\MfaChallengeSecret::from($response); + + } + /** * List the factors available on the account to be used as a MFA challange. * diff --git a/tests/Appwrite/Services/AccountTest.php b/tests/Appwrite/Services/AccountTest.php index eabfc829..28c9db86 100644 --- a/tests/Appwrite/Services/AccountTest.php +++ b/tests/Appwrite/Services/AccountTest.php @@ -374,24 +374,6 @@ public function testMethodDeleteIdentity(): void $this->assertSame($data, $response); } - public function testMethodCreateJWT(): void - { - $data = array( - "jwt" => "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" - ); - - $this->client - ->allows()->call(Mockery::any(), Mockery::any(), Mockery::any(), Mockery::any()) - ->andReturn($data); - $this->client - ->allows()->getConfig(Mockery::any()) - ->andReturn(''); - - $response = $this->account->createJWT(); - - $this->assertInstanceOf(\Appwrite\Models\Jwt::class, $response); - } - public function testMethodListLogs(): void { $data = array( @@ -646,7 +628,8 @@ public function testMethodListMFAFactors(): void "totp" => true, "phone" => true, "email" => true, - "recoveryCode" => true + "recoveryCode" => true, + "custom" => true ); $this->client diff --git a/tests/Appwrite/Services/AppsTest.php b/tests/Appwrite/Services/AppsTest.php index 63767c7a..2e29a9d9 100644 --- a/tests/Appwrite/Services/AppsTest.php +++ b/tests/Appwrite/Services/AppsTest.php @@ -382,6 +382,25 @@ public function testMethodGetInstallation(): void $this->assertInstanceOf(\Appwrite\Models\AppInstallation::class, $response); } + public function testMethodDeleteInstallation(): void + { + $data = ''; + + $this->client + ->allows()->call(Mockery::any(), Mockery::any(), Mockery::any(), Mockery::any()) + ->andReturn($data); + $this->client + ->allows()->getConfig(Mockery::any()) + ->andReturn(''); + + $response = $this->apps->deleteInstallation( + "", + "" + ); + + $this->assertSame($data, $response); + } + public function testMethodCreateInstallationToken(): void { $data = array( diff --git a/tests/Appwrite/Services/EmbeddingsTest.php b/tests/Appwrite/Services/EmbeddingsTest.php new file mode 100644 index 00000000..e7717667 --- /dev/null +++ b/tests/Appwrite/Services/EmbeddingsTest.php @@ -0,0 +1,50 @@ +client = Mockery::mock(Client::class); + $this->embeddings = new Embeddings($this->client); + } + + public function testMethodCreateTextEmbeddings(): void + { + $data = array( + "total" => 5, + "embeddings" => array( + array( + "model" => "nomic-embed-text", + "dimension" => 768, + "embedding" => array(), + "error" => "Error message" + ) + ) + ); + + $this->client + ->allows()->call(Mockery::any(), Mockery::any(), Mockery::any(), Mockery::any()) + ->andReturn($data); + $this->client + ->allows()->getConfig(Mockery::any()) + ->andReturn(''); + + $response = $this->embeddings->createTextEmbeddings( + array() + ); + + $this->assertInstanceOf(\Appwrite\Models\EmbeddingList::class, $response); + } + +} diff --git a/tests/Appwrite/Services/OrganizationTest.php b/tests/Appwrite/Services/OrganizationTest.php index 40c2dd57..9d24d59c 100644 --- a/tests/Appwrite/Services/OrganizationTest.php +++ b/tests/Appwrite/Services/OrganizationTest.php @@ -29,7 +29,6 @@ public function testMethodGet(): void "name" => "VIP", "total" => 7, "prefs" => array(), - "billingBudget" => 50, "budgetAlerts" => array(), "billingPlan" => "tier-1", "billingPlanId" => "tier-1", @@ -44,7 +43,6 @@ public function testMethodGet(): void "storage" => 25, "imageTransformations" => 100, "screenshotsGenerated" => 50, - "members" => 25, "webhooks" => 25, "wafRules" => 2, "projects" => 2, @@ -68,7 +66,6 @@ public function testMethodGet(): void "topics" => 1, "authPhone" => 10, "domains" => 5, - "activityLogs" => 7, "usageLogs" => 30, "projectInactivityDays" => 7, "alertLimit" => 80, @@ -89,14 +86,6 @@ public function testMethodGet(): void "value" => 25, "invoiceDesc" => "[INVOICEDESC]" ), - "member" => array( - "name" => "[NAME]", - "unit" => "GB", - "currency" => "USD", - "price" => 5, - "value" => 25, - "invoiceDesc" => "[INVOICEDESC]" - ), "realtime" => array( "name" => "[NAME]", "unit" => "GB", @@ -113,14 +102,6 @@ public function testMethodGet(): void "value" => 25, "invoiceDesc" => "[INVOICEDESC]" ), - "realtimeBandwidth" => array( - "name" => "[NAME]", - "unit" => "GB", - "currency" => "USD", - "price" => 5, - "value" => 25, - "invoiceDesc" => "[INVOICEDESC]" - ), "storage" => array( "name" => "[NAME]", "unit" => "GB", @@ -152,38 +133,9 @@ public function testMethodGet(): void "price" => 5, "value" => 25, "invoiceDesc" => "[INVOICEDESC]" - ), - "credits" => array( - "name" => "[NAME]", - "unit" => "GB", - "currency" => "USD", - "price" => 5, - "value" => 25, - "invoiceDesc" => "[INVOICEDESC]" - ) - ), - "addons" => array( - "seats" => array( - "supported" => true, - "planIncluded" => 1, - "limit" => 5, - "type" => "numeric", - "currency" => "USD", - "price" => 5, - "value" => 25, - "invoiceDesc" => "[INVOICEDESC]" - ), - "projects" => array( - "supported" => true, - "planIncluded" => 1, - "limit" => 5, - "type" => "numeric", - "currency" => "USD", - "price" => 5, - "value" => 25, - "invoiceDesc" => "[INVOICEDESC]" ) ), + "addons" => array(), "budgetCapEnabled" => true, "customSmtp" => true, "emailBranding" => true, @@ -201,14 +153,12 @@ public function testMethodGet(): void "supportsFreeEmailValidation" => true, "supportsCorporateEmailValidation" => true, "supportsProjectSpecificRoles" => true, - "backupsEnabled" => true, "usagePerProject" => true, "supportedAddons" => array( "baa" => true, "premiumGeoDB" => true, "premiumGeoDBOrg" => true ), - "backupPolicies" => true, "deploymentSize" => 30, "buildSize" => 2000, "databasesAllowEncrypt" => true, @@ -218,22 +168,11 @@ public function testMethodGet(): void "billingStartDate" => "2020-10-15T06:38:00.000+00:00", "billingCurrentInvoiceDate" => "2020-10-15T06:38:00.000+00:00", "billingNextInvoiceDate" => "2020-10-15T06:38:00.000+00:00", - "billingTrialStartDate" => "2020-10-15T06:38:00.000+00:00", "billingTrialDays" => 14, "billingAggregationId" => "adbc3de4rddfsd", "billingInvoiceId" => "adbc3de4rddfsd", "paymentMethodId" => "adbc3de4rddfsd", - "billingAddressId" => "adbc3de4rddfsd", - "backupPaymentMethodId" => "adbc3de4rddfsd", "status" => "active", - "remarks" => "Pending initial payment", - "agreementBAA" => "[AGREEMENTBAA]", - "programManagerName" => "[PROGRAMMANAGERNAME]", - "programManagerCalendar" => "[PROGRAMMANAGERCALENDAR]", - "programDiscordChannelName" => "[PROGRAMDISCORDCHANNELNAME]", - "programDiscordChannelUrl" => "[PROGRAMDISCORDCHANNELURL]", - "billingPlanDowngrade" => "tier-1", - "billingTaxId" => "[BILLINGTAXID]", "markedForDeletion" => true, "platform" => "imagine", "projects" => array() @@ -260,7 +199,6 @@ public function testMethodUpdate(): void "name" => "VIP", "total" => 7, "prefs" => array(), - "billingBudget" => 50, "budgetAlerts" => array(), "billingPlan" => "tier-1", "billingPlanId" => "tier-1", @@ -275,7 +213,6 @@ public function testMethodUpdate(): void "storage" => 25, "imageTransformations" => 100, "screenshotsGenerated" => 50, - "members" => 25, "webhooks" => 25, "wafRules" => 2, "projects" => 2, @@ -299,7 +236,6 @@ public function testMethodUpdate(): void "topics" => 1, "authPhone" => 10, "domains" => 5, - "activityLogs" => 7, "usageLogs" => 30, "projectInactivityDays" => 7, "alertLimit" => 80, @@ -320,14 +256,6 @@ public function testMethodUpdate(): void "value" => 25, "invoiceDesc" => "[INVOICEDESC]" ), - "member" => array( - "name" => "[NAME]", - "unit" => "GB", - "currency" => "USD", - "price" => 5, - "value" => 25, - "invoiceDesc" => "[INVOICEDESC]" - ), "realtime" => array( "name" => "[NAME]", "unit" => "GB", @@ -344,14 +272,6 @@ public function testMethodUpdate(): void "value" => 25, "invoiceDesc" => "[INVOICEDESC]" ), - "realtimeBandwidth" => array( - "name" => "[NAME]", - "unit" => "GB", - "currency" => "USD", - "price" => 5, - "value" => 25, - "invoiceDesc" => "[INVOICEDESC]" - ), "storage" => array( "name" => "[NAME]", "unit" => "GB", @@ -383,38 +303,9 @@ public function testMethodUpdate(): void "price" => 5, "value" => 25, "invoiceDesc" => "[INVOICEDESC]" - ), - "credits" => array( - "name" => "[NAME]", - "unit" => "GB", - "currency" => "USD", - "price" => 5, - "value" => 25, - "invoiceDesc" => "[INVOICEDESC]" - ) - ), - "addons" => array( - "seats" => array( - "supported" => true, - "planIncluded" => 1, - "limit" => 5, - "type" => "numeric", - "currency" => "USD", - "price" => 5, - "value" => 25, - "invoiceDesc" => "[INVOICEDESC]" - ), - "projects" => array( - "supported" => true, - "planIncluded" => 1, - "limit" => 5, - "type" => "numeric", - "currency" => "USD", - "price" => 5, - "value" => 25, - "invoiceDesc" => "[INVOICEDESC]" ) ), + "addons" => array(), "budgetCapEnabled" => true, "customSmtp" => true, "emailBranding" => true, @@ -432,14 +323,12 @@ public function testMethodUpdate(): void "supportsFreeEmailValidation" => true, "supportsCorporateEmailValidation" => true, "supportsProjectSpecificRoles" => true, - "backupsEnabled" => true, "usagePerProject" => true, "supportedAddons" => array( "baa" => true, "premiumGeoDB" => true, "premiumGeoDBOrg" => true ), - "backupPolicies" => true, "deploymentSize" => 30, "buildSize" => 2000, "databasesAllowEncrypt" => true, @@ -449,22 +338,11 @@ public function testMethodUpdate(): void "billingStartDate" => "2020-10-15T06:38:00.000+00:00", "billingCurrentInvoiceDate" => "2020-10-15T06:38:00.000+00:00", "billingNextInvoiceDate" => "2020-10-15T06:38:00.000+00:00", - "billingTrialStartDate" => "2020-10-15T06:38:00.000+00:00", "billingTrialDays" => 14, "billingAggregationId" => "adbc3de4rddfsd", "billingInvoiceId" => "adbc3de4rddfsd", "paymentMethodId" => "adbc3de4rddfsd", - "billingAddressId" => "adbc3de4rddfsd", - "backupPaymentMethodId" => "adbc3de4rddfsd", "status" => "active", - "remarks" => "Pending initial payment", - "agreementBAA" => "[AGREEMENTBAA]", - "programManagerName" => "[PROGRAMMANAGERNAME]", - "programManagerCalendar" => "[PROGRAMMANAGERCALENDAR]", - "programDiscordChannelName" => "[PROGRAMDISCORDCHANNELNAME]", - "programDiscordChannelUrl" => "[PROGRAMDISCORDCHANNELURL]", - "billingPlanDowngrade" => "tier-1", - "billingTaxId" => "[BILLINGTAXID]", "markedForDeletion" => true, "platform" => "imagine", "projects" => array() diff --git a/tests/Appwrite/Services/ProjectTest.php b/tests/Appwrite/Services/ProjectTest.php index 0213e34c..deb9bf61 100644 --- a/tests/Appwrite/Services/ProjectTest.php +++ b/tests/Appwrite/Services/ProjectTest.php @@ -245,36 +245,6 @@ public function testMethodListKeys(): void $this->assertInstanceOf(\Appwrite\Models\KeyList::class, $response); } - public function testMethodCreateKey(): void - { - $data = array( - "\$id" => "5e5ea5c16897e", - "\$createdAt" => "2020-10-15T06:38:00.000+00:00", - "\$updatedAt" => "2020-10-15T06:38:00.000+00:00", - "name" => "My API Key", - "expire" => "2020-10-15T06:38:00.000+00:00", - "scopes" => array(), - "secret" => "919c2d18fb5d4...a2ae413da83346ad2", - "accessedAt" => "2020-10-15T06:38:00.000+00:00", - "sdks" => array() - ); - - $this->client - ->allows()->call(Mockery::any(), Mockery::any(), Mockery::any(), Mockery::any()) - ->andReturn($data); - $this->client - ->allows()->getConfig(Mockery::any()) - ->andReturn(''); - - $response = $this->project->createKey( - "", - "", - array(ProjectKeyScopes::PROJECTREAD()) - ); - - $this->assertInstanceOf(\Appwrite\Models\Key::class, $response); - } - public function testMethodCreateEphemeralKey(): void { $data = array( @@ -2413,6 +2383,89 @@ public function testMethodUpdateMembershipPrivacyPolicy(): void $this->assertInstanceOf(\Appwrite\Models\Project::class, $response); } + public function testMethodUpdateMFAFactorsPolicy(): void + { + $data = array( + "\$id" => "5e5ea5c16897e", + "\$createdAt" => "2020-10-15T06:38:00.000+00:00", + "\$updatedAt" => "2020-10-15T06:38:00.000+00:00", + "name" => "New Project", + "teamId" => "1592981250", + "region" => "fra", + "devKeys" => array( + array( + "\$id" => "5e5ea5c16897e", + "\$createdAt" => "2020-10-15T06:38:00.000+00:00", + "\$updatedAt" => "2020-10-15T06:38:00.000+00:00", + "name" => "Dev API Key", + "expire" => "2020-10-15T06:38:00.000+00:00", + "secret" => "919c2d18fb5d4...a2ae413da83346ad2", + "accessedAt" => "2020-10-15T06:38:00.000+00:00", + "sdks" => array() + ) + ), + "smtpEnabled" => true, + "smtpSenderName" => "John Appwrite", + "smtpSenderEmail" => "john@appwrite.io", + "smtpReplyToName" => "Support Team", + "smtpReplyToEmail" => "support@appwrite.io", + "smtpHost" => "mail.appwrite.io", + "smtpPort" => 25, + "smtpUsername" => "emailuser", + "smtpPassword" => "smtp-password", + "smtpSecure" => "tls", + "pingCount" => 1, + "pingedAt" => "2020-10-15T06:38:00.000+00:00", + "labels" => array(), + "status" => "active", + "onboarding" => array(), + "authMethods" => array( + array( + "\$id" => "email-password", + "enabled" => true + ) + ), + "services" => array( + array( + "\$id" => "account", + "enabled" => true + ) + ), + "protocols" => array( + array( + "\$id" => "rest", + "enabled" => true + ) + ), + "blocks" => array( + array( + "\$createdAt" => "2020-10-15T06:38:00.000+00:00", + "resourceType" => "project", + "resourceId" => "5e5ea5c16897e", + "mode" => "readOnly", + "projectName" => "My Project", + "region" => "fra", + "organizationName" => "Acme Inc.", + "organizationId" => "5e5ea5c16897e", + "billingPlan" => "pro" + ) + ), + "consoleAccessedAt" => "2020-10-15T06:38:00.000+00:00", + "wafEnabled" => true + ); + + $this->client + ->allows()->call(Mockery::any(), Mockery::any(), Mockery::any(), Mockery::any()) + ->andReturn($data); + $this->client + ->allows()->getConfig(Mockery::any()) + ->andReturn(''); + + $response = $this->project->updateMFAFactorsPolicy(); + + $this->assertInstanceOf(\Appwrite\Models\Project::class, $response); + } + public function testMethodUpdatePasswordDictionaryPolicy(): void { $data = array( diff --git a/tests/Appwrite/Services/ProxyTest.php b/tests/Appwrite/Services/ProxyTest.php index a7c27ae8..d4b3eab1 100644 --- a/tests/Appwrite/Services/ProxyTest.php +++ b/tests/Appwrite/Services/ProxyTest.php @@ -6,6 +6,7 @@ use Appwrite\InputFile; use Mockery; use PHPUnit\Framework\TestCase; +use Appwrite\Enums\InvalidationType; use Appwrite\Enums\StatusCode; use Appwrite\Enums\ProxyResourceType; @@ -20,6 +21,30 @@ protected function setUp(): void $this->proxy = new Proxy($this->client); } + public function testMethodCreateInvalidation(): void + { + $data = array( + "domain" => "appwrite.company.com", + "type" => "tag", + "reference" => "products", + "status" => "success" + ); + + $this->client + ->allows()->call(Mockery::any(), Mockery::any(), Mockery::any(), Mockery::any()) + ->andReturn($data); + $this->client + ->allows()->getConfig(Mockery::any()) + ->andReturn(''); + + $response = $this->proxy->createInvalidation( + "", + InvalidationType::TAG() + ); + + $this->assertInstanceOf(\Appwrite\Models\ProxyInvalidation::class, $response); + } + public function testMethodListRules(): void { $data = array( diff --git a/tests/Appwrite/Services/StorageTest.php b/tests/Appwrite/Services/StorageTest.php index 95c1ea7e..3fd5a6f6 100644 --- a/tests/Appwrite/Services/StorageTest.php +++ b/tests/Appwrite/Services/StorageTest.php @@ -188,6 +188,8 @@ public function testMethodListFiles(): void "\$updatedAt" => "2020-10-15T06:38:00.000+00:00", "\$permissions" => array(), "name" => "Pink.png", + "folder" => "photos/2026/", + "key" => "photos/2026/Pink.png", "signature" => "5d529fd02b544198ae075bd57c1762bb", "mimeType" => "image/png", "sizeOriginal" => 17890, @@ -223,6 +225,8 @@ public function testMethodCreateFile(): void "\$updatedAt" => "2020-10-15T06:38:00.000+00:00", "\$permissions" => array(), "name" => "Pink.png", + "folder" => "photos/2026/", + "key" => "photos/2026/Pink.png", "signature" => "5d529fd02b544198ae075bd57c1762bb", "mimeType" => "image/png", "sizeOriginal" => 17890, @@ -258,6 +262,8 @@ public function testMethodGetFile(): void "\$updatedAt" => "2020-10-15T06:38:00.000+00:00", "\$permissions" => array(), "name" => "Pink.png", + "folder" => "photos/2026/", + "key" => "photos/2026/Pink.png", "signature" => "5d529fd02b544198ae075bd57c1762bb", "mimeType" => "image/png", "sizeOriginal" => 17890, @@ -292,6 +298,8 @@ public function testMethodUpdateFile(): void "\$updatedAt" => "2020-10-15T06:38:00.000+00:00", "\$permissions" => array(), "name" => "Pink.png", + "folder" => "photos/2026/", + "key" => "photos/2026/Pink.png", "signature" => "5d529fd02b544198ae075bd57c1762bb", "mimeType" => "image/png", "sizeOriginal" => 17890, diff --git a/tests/Appwrite/Services/TablesDBTest.php b/tests/Appwrite/Services/TablesDBTest.php index 1c75086f..31b4e262 100644 --- a/tests/Appwrite/Services/TablesDBTest.php +++ b/tests/Appwrite/Services/TablesDBTest.php @@ -97,7 +97,6 @@ public function testMethodListSpecifications(): void "storageOverageRate" => 0.125, "bandwidthOverageRate" => 0.08, "replicaRate" => 1, - "crossRegionReplicaRate" => 1, "pitrRate" => 0.2 ) ); @@ -357,7 +356,6 @@ public function testMethodCreateFailover(): void "nodePool" => "db-pool-4vcpu-8gb", "replicas" => 2, "syncMode" => "async", - "crossRegionReplicas" => 1, "networkMaxConnections" => 500, "networkIdleTimeoutSeconds" => 900, "networkIPAllowlist" => array(), @@ -392,17 +390,218 @@ public function testMethodCreateFailover(): void $this->assertInstanceOf(\Appwrite\Models\DedicatedDatabase::class, $response); } + public function testMethodListMigrations(): void + { + $data = array( + "total" => 5, + "migrations" => array( + array( + "\$id" => "5e5ea5c16897e", + "\$createdAt" => "2020-10-15T06:38:00.000+00:00", + "\$updatedAt" => "2020-10-15T06:38:00.000+00:00", + "projectId" => "5e5ea5c16897e", + "databaseId" => "5e5ea5c16897e", + "specification" => "s-2vcpu-4gb", + "phase" => "pending", + "attempt" => 0, + "lastError" => "[LASTERROR]", + "lagDocuments" => 0, + "verifiedAt" => "2020-10-15T06:38:00.000+00:00", + "cutoverAt" => "2020-10-15T06:38:00.000+00:00", + "soakUntil" => "2020-10-15T06:38:00.000+00:00", + "autoCutover" => true, + "cutoverRequested" => true, + "paused" => true + ) + ) + ); + + $this->client + ->allows()->call(Mockery::any(), Mockery::any(), Mockery::any(), Mockery::any()) + ->andReturn($data); + $this->client + ->allows()->getConfig(Mockery::any()) + ->andReturn(''); + + $response = $this->tablesDB->listMigrations( + "" + ); + + $this->assertInstanceOf(\Appwrite\Models\DatabaseMigrationList::class, $response); + } + + public function testMethodCreateMigration(): void + { + $data = array( + "\$id" => "5e5ea5c16897e", + "\$createdAt" => "2020-10-15T06:38:00.000+00:00", + "\$updatedAt" => "2020-10-15T06:38:00.000+00:00", + "projectId" => "5e5ea5c16897e", + "databaseId" => "5e5ea5c16897e", + "specification" => "s-2vcpu-4gb", + "phase" => "pending", + "attempt" => 0, + "lastError" => "[LASTERROR]", + "lagDocuments" => 0, + "verifiedAt" => "2020-10-15T06:38:00.000+00:00", + "cutoverAt" => "2020-10-15T06:38:00.000+00:00", + "soakUntil" => "2020-10-15T06:38:00.000+00:00", + "autoCutover" => true, + "cutoverRequested" => true, + "paused" => true + ); + + $this->client + ->allows()->call(Mockery::any(), Mockery::any(), Mockery::any(), Mockery::any()) + ->andReturn($data); + $this->client + ->allows()->getConfig(Mockery::any()) + ->andReturn(''); + + $response = $this->tablesDB->createMigration( + "", + "s-1vcpu-1gb" + ); + + $this->assertInstanceOf(\Appwrite\Models\DatabaseMigration::class, $response); + } + + public function testMethodGetMigration(): void + { + $data = array( + "\$id" => "5e5ea5c16897e", + "\$createdAt" => "2020-10-15T06:38:00.000+00:00", + "\$updatedAt" => "2020-10-15T06:38:00.000+00:00", + "projectId" => "5e5ea5c16897e", + "databaseId" => "5e5ea5c16897e", + "specification" => "s-2vcpu-4gb", + "phase" => "pending", + "attempt" => 0, + "lastError" => "[LASTERROR]", + "lagDocuments" => 0, + "verifiedAt" => "2020-10-15T06:38:00.000+00:00", + "cutoverAt" => "2020-10-15T06:38:00.000+00:00", + "soakUntil" => "2020-10-15T06:38:00.000+00:00", + "autoCutover" => true, + "cutoverRequested" => true, + "paused" => true + ); + + $this->client + ->allows()->call(Mockery::any(), Mockery::any(), Mockery::any(), Mockery::any()) + ->andReturn($data); + $this->client + ->allows()->getConfig(Mockery::any()) + ->andReturn(''); + + $response = $this->tablesDB->getMigration( + "", + "" + ); + + $this->assertInstanceOf(\Appwrite\Models\DatabaseMigration::class, $response); + } + + public function testMethodDeleteMigration(): void + { + $data = ''; + + $this->client + ->allows()->call(Mockery::any(), Mockery::any(), Mockery::any(), Mockery::any()) + ->andReturn($data); + $this->client + ->allows()->getConfig(Mockery::any()) + ->andReturn(''); + + $response = $this->tablesDB->deleteMigration( + "", + "" + ); + + $this->assertSame($data, $response); + } + + public function testMethodCutoverMigration(): void + { + $data = array( + "\$id" => "5e5ea5c16897e", + "\$createdAt" => "2020-10-15T06:38:00.000+00:00", + "\$updatedAt" => "2020-10-15T06:38:00.000+00:00", + "projectId" => "5e5ea5c16897e", + "databaseId" => "5e5ea5c16897e", + "specification" => "s-2vcpu-4gb", + "phase" => "pending", + "attempt" => 0, + "lastError" => "[LASTERROR]", + "lagDocuments" => 0, + "verifiedAt" => "2020-10-15T06:38:00.000+00:00", + "cutoverAt" => "2020-10-15T06:38:00.000+00:00", + "soakUntil" => "2020-10-15T06:38:00.000+00:00", + "autoCutover" => true, + "cutoverRequested" => true, + "paused" => true + ); + + $this->client + ->allows()->call(Mockery::any(), Mockery::any(), Mockery::any(), Mockery::any()) + ->andReturn($data); + $this->client + ->allows()->getConfig(Mockery::any()) + ->andReturn(''); + + $response = $this->tablesDB->cutoverMigration( + "", + "" + ); + + $this->assertInstanceOf(\Appwrite\Models\DatabaseMigration::class, $response); + } + + public function testMethodListOperations(): void + { + $data = array( + "total" => 5, + "operations" => array( + array( + "\$id" => "5e5ea5c16897e", + "\$createdAt" => "2020-10-15T06:38:00.000+00:00", + "databaseId" => "5e5ea5c16897e", + "type" => "update", + "status" => "completed", + "attempts" => 1, + "errorCode" => "Interrupted", + "errorMessage" => "[ERRORMESSAGE]" + ) + ) + ); + + $this->client + ->allows()->call(Mockery::any(), Mockery::any(), Mockery::any(), Mockery::any()) + ->andReturn($data); + $this->client + ->allows()->getConfig(Mockery::any()) + ->andReturn(''); + + $response = $this->tablesDB->listOperations( + "" + ); + + $this->assertInstanceOf(\Appwrite\Models\DedicatedDatabaseOperationList::class, $response); + } + public function testMethodGetReplicas(): void { $data = array( "replicas" => 2, "syncMode" => "async", + "syncDegraded" => true, + "syncAcknowledgements" => 1, + "syncStandbyCount" => 2, "members" => array( array( "\$id" => "1", "role" => "replica", - "status" => "active", - "lagSeconds" => 0.5 + "status" => "active" ) ) ); @@ -433,6 +632,10 @@ public function testMethodGetStatus(): void "current" => 12, "max" => 100 ), + "syncMode" => "async", + "syncDegraded" => true, + "syncAcknowledgements" => 1, + "syncStandbyCount" => 2, "replicas" => array( array( "index" => 0, diff --git a/tests/Appwrite/Services/UsersTest.php b/tests/Appwrite/Services/UsersTest.php index 9f9b0171..6206a767 100644 --- a/tests/Appwrite/Services/UsersTest.php +++ b/tests/Appwrite/Services/UsersTest.php @@ -886,13 +886,39 @@ public function testMethodDeleteMFAAuthenticator(): void $this->assertSame($data, $response); } + public function testMethodGetMFAChallenge(): void + { + $data = array( + "\$id" => "bb8ea5c16897e", + "\$createdAt" => "2020-10-15T06:38:00.000+00:00", + "userId" => "5e5ea5c168bb8", + "expire" => "2020-10-15T06:38:00.000+00:00", + "code" => "446372" + ); + + $this->client + ->allows()->call(Mockery::any(), Mockery::any(), Mockery::any(), Mockery::any()) + ->andReturn($data); + $this->client + ->allows()->getConfig(Mockery::any()) + ->andReturn(''); + + $response = $this->users->getMFAChallenge( + "", + "" + ); + + $this->assertInstanceOf(\Appwrite\Models\MfaChallengeSecret::class, $response); + } + public function testMethodListMFAFactors(): void { $data = array( "totp" => true, "phone" => true, "email" => true, - "recoveryCode" => true + "recoveryCode" => true, + "custom" => true ); $this->client