Merge branch '1.8.x' into optional-assistant

This commit is contained in:
Darshan 2025-12-31 19:09:54 +05:30
commit 92a3eca194
654 changed files with 17822 additions and 11418 deletions

12
.env
View file

@ -21,13 +21,13 @@ _APP_OPTIONS_ROUTER_PROTECTION=disabled
_APP_OPTIONS_FORCE_HTTPS=disabled
_APP_OPTIONS_ROUTER_FORCE_HTTPS=disabled
_APP_OPENSSL_KEY_V1=your-secret-key
_APP_DNS=8.8.8.8
_APP_DNS=172.16.238.100 # CoreDNS
_APP_DOMAIN=appwrite.test
_APP_CONSOLE_DOMAIN=localhost
_APP_DOMAIN_FUNCTIONS=functions.localhost
_APP_DOMAIN_SITES=sites.localhost
_APP_DOMAIN_TARGET_CNAME=test.localhost
_APP_DOMAIN_TARGET_A=127.0.0.1
_APP_DOMAIN_TARGET_CNAME=cname.localhost
_APP_DOMAIN_TARGET_A=203.0.0.1
_APP_DOMAIN_TARGET_AAAA=::1
_APP_DOMAIN_TARGET_CAA=digicert.com
_APP_RULES_FORMAT=md5
@ -69,8 +69,8 @@ _APP_STORAGE_ANTIVIRUS_PORT=3310
_APP_SMTP_HOST=maildev
_APP_SMTP_PORT=1025
_APP_SMTP_SECURE=
_APP_SMTP_USERNAME=
_APP_SMTP_PASSWORD=
_APP_SMTP_USERNAME=user
_APP_SMTP_PASSWORD=password
_APP_SMS_PROVIDER=sms://username:password@mock
_APP_SMS_FROM=+123456789
_APP_SMS_PROJECTS_DENY_LIST=
@ -101,6 +101,7 @@ _APP_USAGE_AGGREGATION_INTERVAL=30
_APP_STATS_RESOURCES_INTERVAL=30
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
_APP_INTERVAL_DOMAIN_VERIFICATION=60
_APP_USAGE_STATS=enabled
_APP_LOGGING_CONFIG=
_APP_LOGGING_CONFIG_REALTIME=
@ -125,3 +126,4 @@ _APP_WEBHOOK_MAX_FAILED_ATTEMPTS=10
_APP_PROJECT_REGIONS=default
_APP_FUNCTIONS_CREATION_ABUSE_LIMIT=5000
_APP_STATS_USAGE_DUAL_WRITING_DBS=database_db_main
_APP_TRUSTED_HEADERS=x-forwarded-for

73
AGENTS.md Normal file
View file

@ -0,0 +1,73 @@
# AGENTS.md
Appwrite is an end-to-end backend server for web, mobile, native, and backend apps. This guide provides context and instructions for AI coding agents working on the Appwrite codebase.
## Project Overview
Appwrite is a self-hosted Backend-as-a-Service (BaaS) platform that provides developers with a set of APIs and tools to build secure, scalable applications. The project uses a hybrid monolithic-microservice architecture built with PHP, running on Swoole for high performance.
**Key Technologies:**
- **Backend:** PHP 8.3+, Swoole
- **Libraries:** Utopia PHP
- **Database:** MariaDB, Redis
- **Cache:** Redis
- **Queue:** Redis
- **Containers:** Docker
## Development Commands
```bash
# Run Appwrite
docker compose up -d --force-recreate --build
# Run specific test
docker compose exec appwrite test /usr/src/code/tests/e2e/Services/[ServiceName] --filter=[FunctionName]
# Format code
composer format
```
## Code Style Guidelines
- Follow [PSR-12](https://www.php-fig.org/psr/psr-12/) coding standard
- Use PSR-4 autoloading
- Strict type declarations where applicable
- Comprehensive PHPDoc comments
### Naming Conventions
#### `resourceType` Naming Rule
When a collection has a combination of `resourceType`, `resourceId`, and/or `resourceInternalId`, the value of `resourceType` MUST always be **plural** - for example: `functions`, `sites`, `deployments`.
Examples:
```php
'resourceType' => 'functions'
'resourceType' => 'sites'
'resourceType' => 'deployments'
```
## Security Considerations
### Critical Security Practices
- **Never hardcode credentials** - Use environment variables
- **Rate limiting** - Respect abuse prevention mechanisms
## Dependencies
Avoid introducing new dependencies other than utopia-php.
## Pull Request Guidelines
### Before Submitting
- Run `composer format`
- Update documentation if adding features
- Add/update tests for your changes
- Check that Docker build succeeds
`docs/specs/authentication.drawio.svg`
## Known Issues and Gotchas
- **Hot Reload:** Code changes require container restart in some cases
- **Logging:** There is no central place for logs, so when debugging, ensure to check all possibly relevant containers

View file

@ -57,6 +57,7 @@ RUN mkdir -p /storage/uploads && \
# Executables
RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/install && \
chmod +x /usr/local/bin/interval && \
chmod +x /usr/local/bin/maintenance && \
chmod +x /usr/local/bin/migrate && \
chmod +x /usr/local/bin/realtime && \

View file

@ -6,7 +6,7 @@ use Utopia\Database\Helpers\ID;
$providers = Config::getParam('oAuthProviders', []);
return [
$platformCollections = [
'projects' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('projects'),
@ -643,6 +643,39 @@ return [
'array' => false,
'filters' => [],
],
[
'$id' => 'resourceType',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'resourceId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'resourceInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('name'),
'type' => Database::VAR_STRING,
@ -718,6 +751,13 @@ return [
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_resource',
'type' => Database::INDEX_KEY,
'attributes' => ['resourceType', 'resourceInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => '_key_accessedAt',
'type' => Database::INDEX_KEY,
@ -1317,6 +1357,17 @@ return [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('logs'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 1000000,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
@ -1892,3 +1943,31 @@ return [
'indexes' => []
],
];
// Organization API keys subquery
$platformCollections['teams']['attributes'][] = [
'$id' => ID::custom('keys'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['subQueryOrganizationKeys'],
];
// Account API keys subquery
$platformCollections['users']['attributes'][] = [
'$id' => ID::custom('keys'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['subQueryAccountKeys'],
];
return $platformCollections;

View file

@ -187,7 +187,7 @@
<img
height="26px"
src="{{logoUrl}}"
alt="Appwrite logo"
alt="{{platform}} logo"
/>
</td>
</tr>
@ -225,7 +225,7 @@
<tr>
<td style="padding-left: 4px; padding-right: 4px">
<a
href="{{twitterUrl}}"
href="{{twitter}}"
class="social-icon"
title="Twitter"
>
@ -234,7 +234,7 @@
</td>
<td style="padding-left: 4px; padding-right: 4px">
<a
href="{{discordUrl}}"
href="{{discord}}"
class="social-icon"
>
<img src="https://cloud.appwrite.io/images/mails/discord.png" height="24" width="24" />
@ -242,7 +242,7 @@
</td>
<td style="padding-left: 4px; padding-right: 4px">
<a
href="{{githubUrl}}"
href="{{github}}"
class="social-icon"
>
<img src="https://cloud.appwrite.io/images/mails/github.png" height="24" width="24" />
@ -252,15 +252,15 @@
</table>
<table style="width: auto; margin: 0 auto; margin-top: 60px">
<tr>
<td><a href="{{termsUrl}}">Terms</a></td>
<td><a href="{{terms}}">Terms</a></td>
<td style="color: #e8e9f0">
<div style="margin: 0 8px">|</div>
</td>
<td><a href="{{privacyUrl}}">Privacy</a></td>
<td><a href="{{privacy}}">Privacy</a></td>
</tr>
</table>
<p style="text-align: center" align="center">
&copy; {{year}} Appwrite | 251 Little Falls Drive, Wilmington 19808,
&copy; {{year}} {{platform}} | 251 Little Falls Drive, Wilmington 19808,
Delaware, United States
</p>
</div>

View file

@ -462,4 +462,15 @@ return [
'mock' => true,
'class' => 'Appwrite\\Auth\\OAuth2\\Mock',
],
'mock-unverified' => [
'name' => 'MockUnverified',
'developers' => 'https://appwrite.io',
'icon' => 'icon-appwrite',
'enabled' => true,
'sandbox' => false,
'form' => false,
'beta' => false,
'mock' => true,
'class' => 'Appwrite\\Auth\\OAuth2\\MockUnverified',
],
];

View file

@ -22,4 +22,5 @@ return [
'termsUrl' => APP_EMAIL_TERMS_URL,
'privacyUrl' => APP_EMAIL_PRIVACY_URL,
'websiteUrl' => 'https://' . APP_DOMAIN,
'emailSenderName' => APP_EMAIL_PLATFORM_NAME,
];

View file

@ -58,6 +58,8 @@ $admins = [
'projects.write',
'keys.read',
'keys.write',
'devKeys.read',
'devKeys.write',
'webhooks.read',
'webhooks.write',
'locale.read',

View file

@ -1,8 +1,8 @@
<?php
return [
APP_PLATFORM_CLIENT => [
'key' => APP_PLATFORM_CLIENT,
APP_SDK_PLATFORM_CLIENT => [
'key' => APP_SDK_PLATFORM_CLIENT,
'name' => 'Client',
'description' => 'Client libraries for integrating with Appwrite to build client-based applications and websites. Read the [getting started for web](https://appwrite.io/docs/getting-started-for-web) or [getting started for Flutter](https://appwrite.io/docs/getting-started-for-flutter) tutorials to start building your first application.',
'enabled' => true,
@ -18,7 +18,7 @@ return [
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_PLATFORM_CLIENT,
'family' => APP_SDK_PLATFORM_CLIENT,
'prism' => 'javascript',
'source' => \realpath(__DIR__ . '/../sdks/client-web'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-web.git',
@ -67,7 +67,7 @@ return [
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_PLATFORM_CLIENT,
'family' => APP_SDK_PLATFORM_CLIENT,
'prism' => 'dart',
'source' => \realpath(__DIR__ . '/../sdks/client-flutter'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-flutter.git',
@ -86,7 +86,7 @@ return [
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_PLATFORM_CLIENT,
'family' => APP_SDK_PLATFORM_CLIENT,
'prism' => 'swift',
'source' => \realpath(__DIR__ . '/../sdks/client-apple'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-apple.git',
@ -104,7 +104,7 @@ return [
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_PLATFORM_CLIENT,
'family' => APP_SDK_PLATFORM_CLIENT,
'prism' => '',
'source' => false,
'gitUrl' => 'git@github.com:appwrite/sdk-for-objective-c.git',
@ -116,6 +116,7 @@ return [
[
'key' => 'android',
'name' => 'Android',
'namespace' => 'io.appwrite',
'version' => '11.4.0',
'url' => 'https://github.com/appwrite/sdk-for-android',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
@ -123,7 +124,7 @@ return [
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_PLATFORM_CLIENT,
'family' => APP_SDK_PLATFORM_CLIENT,
'prism' => 'kotlin',
'source' => \realpath(__DIR__ . '/../sdks/client-android'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-android.git',
@ -146,7 +147,7 @@ return [
'beta' => true,
'dev' => false,
'hidden' => false,
'family' => APP_PLATFORM_CLIENT,
'family' => APP_SDK_PLATFORM_CLIENT,
'prism' => 'javascript',
'source' => \realpath(__DIR__ . '/../sdks/client-react-native'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-react-native.git',
@ -165,7 +166,7 @@ return [
'beta' => false,
'dev' => false,
'hidden' => true,
'family' => APP_PLATFORM_CLIENT,
'family' => APP_SDK_PLATFORM_CLIENT,
'prism' => 'graphql',
'source' => \realpath(__DIR__ . '/../sdks/client-graphql'),
'gitUrl' => '',
@ -185,7 +186,7 @@ return [
'beta' => false,
'dev' => false,
'hidden' => true,
'family' => APP_PLATFORM_CLIENT,
'family' => APP_SDK_PLATFORM_CLIENT,
'prism' => 'http',
'source' => \realpath(__DIR__ . '/../sdks/client-rest'),
'gitUrl' => '',
@ -198,8 +199,8 @@ return [
],
],
APP_PLATFORM_CONSOLE => [
'key' => APP_PLATFORM_CONSOLE,
APP_SDK_PLATFORM_CONSOLE => [
'key' => APP_SDK_PLATFORM_CONSOLE,
'name' => 'Console',
'enabled' => false,
'beta' => false,
@ -214,7 +215,7 @@ return [
'beta' => false,
'dev' => false,
'hidden' => true,
'family' => APP_PLATFORM_CONSOLE,
'family' => APP_SDK_PLATFORM_CONSOLE,
'prism' => 'javascript',
'source' => \realpath(__DIR__ . '/../sdks/console-web'),
'gitUrl' => '',
@ -233,7 +234,7 @@ return [
'beta' => true,
'dev' => false,
'hidden' => false,
'family' => APP_PLATFORM_CONSOLE,
'family' => APP_SDK_PLATFORM_CONSOLE,
'prism' => 'bash',
'source' => \realpath(__DIR__ . '/../sdks/console-cli'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-cli.git',
@ -252,8 +253,8 @@ return [
],
],
APP_PLATFORM_SERVER => [
'key' => APP_PLATFORM_SERVER,
APP_SDK_PLATFORM_SERVER => [
'key' => APP_SDK_PLATFORM_SERVER,
'name' => 'Server',
'description' => 'Libraries for integrating with Appwrite to build server side integrations. Read the [getting started for server](https://appwrite.io/docs/getting-started-for-server) tutorial to start building your first server integration.',
'enabled' => true,
@ -262,14 +263,14 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '21.0.0',
'version' => '21.1.0',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_PLATFORM_SERVER,
'family' => APP_SDK_PLATFORM_SERVER,
'prism' => 'javascript',
'source' => \realpath(__DIR__ . '/../sdks/server-nodejs'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-node.git',
@ -281,14 +282,14 @@ return [
[
'key' => 'php',
'name' => 'PHP',
'version' => '19.0.0',
'version' => '19.1.0',
'url' => 'https://github.com/appwrite/sdk-for-php',
'package' => 'https://packagist.org/packages/appwrite/appwrite',
'enabled' => true,
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_PLATFORM_SERVER,
'family' => APP_SDK_PLATFORM_SERVER,
'prism' => 'php',
'source' => \realpath(__DIR__ . '/../sdks/server-php'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-php.git',
@ -300,14 +301,14 @@ return [
[
'key' => 'python',
'name' => 'Python',
'version' => '14.0.0',
'version' => '14.1.0',
'url' => 'https://github.com/appwrite/sdk-for-python',
'package' => 'https://pypi.org/project/appwrite/',
'enabled' => true,
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_PLATFORM_SERVER,
'family' => APP_SDK_PLATFORM_SERVER,
'prism' => 'python',
'source' => \realpath(__DIR__ . '/../sdks/server-python'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-python.git',
@ -319,14 +320,14 @@ return [
[
'key' => 'ruby',
'name' => 'Ruby',
'version' => '20.0.0',
'version' => '20.1.0',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_PLATFORM_SERVER,
'family' => APP_SDK_PLATFORM_SERVER,
'prism' => 'ruby',
'source' => \realpath(__DIR__ . '/../sdks/server-ruby'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-ruby.git',
@ -338,14 +339,14 @@ return [
[
'key' => 'go',
'name' => 'Go',
'version' => 'v0.15.0',
'version' => 'v0.16.0',
'url' => 'https://github.com/appwrite/sdk-for-go',
'package' => 'https://github.com/appwrite/sdk-for-go',
'enabled' => true,
'beta' => true,
'dev' => false,
'hidden' => false,
'family' => APP_PLATFORM_SERVER,
'family' => APP_SDK_PLATFORM_SERVER,
'prism' => 'go',
'source' => \realpath(__DIR__ . '/../sdks/server-go'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-go.git',
@ -357,14 +358,14 @@ return [
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.23.0',
'version' => '0.24.0',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => true,
'beta' => true,
'dev' => false,
'hidden' => false,
'family' => APP_PLATFORM_SERVER,
'family' => APP_SDK_PLATFORM_SERVER,
'prism' => 'csharp',
'source' => \realpath(__DIR__ . '/../sdks/server-dotnet'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-dotnet.git',
@ -376,14 +377,14 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '20.0.0',
'version' => '20.1.0',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_PLATFORM_SERVER,
'family' => APP_SDK_PLATFORM_SERVER,
'prism' => 'dart',
'source' => \realpath(__DIR__ . '/../sdks/server-dart'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-dart.git',
@ -395,14 +396,15 @@ return [
[
'key' => 'kotlin',
'name' => 'Kotlin',
'version' => '13.0.0',
'namespace' => 'io.appwrite',
'version' => '13.1.0',
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
'enabled' => true,
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_PLATFORM_SERVER,
'family' => APP_SDK_PLATFORM_SERVER,
'prism' => 'kotlin',
'source' => \realpath(__DIR__ . '/../sdks/server-kotlin'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-kotlin.git',
@ -418,14 +420,14 @@ return [
[
'key' => 'swift',
'name' => 'Swift',
'version' => '14.0.0',
'version' => '14.1.0',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_PLATFORM_SERVER,
'family' => APP_SDK_PLATFORM_SERVER,
'prism' => 'swift',
'source' => \realpath(__DIR__ . '/../sdks/server-swift'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-swift.git',
@ -444,7 +446,7 @@ return [
'beta' => false,
'dev' => false,
'hidden' => true,
'family' => APP_PLATFORM_SERVER,
'family' => APP_SDK_PLATFORM_SERVER,
'prism' => 'graphql',
'source' => \realpath(__DIR__ . '/../sdks/server-graphql'),
'gitUrl' => '',
@ -464,7 +466,7 @@ return [
'beta' => false,
'dev' => false,
'hidden' => true,
'family' => APP_PLATFORM_SERVER,
'family' => APP_SDK_PLATFORM_SERVER,
'prism' => 'http',
'source' => \realpath(__DIR__ . '/../sdks/server-rest'),
'gitUrl' => '',

View file

@ -13,19 +13,21 @@ return [
'tests' => false,
'optional' => false,
'icon' => '',
'platforms' => ['client', 'server', 'console'],
],
'web/console' => [
'key' => 'web/console',
'name' => 'Console',
'subtitle' => '',
'description' => '',
'controller' => 'web/console.php',
'controller' => '', // Uses modules
'sdk' => false,
'docs' => false,
'docsUrl' => '',
'tests' => false,
'optional' => false,
'icon' => '',
'platforms' => ['client', 'server', 'console'],
],
'account' => [
'key' => 'account',
@ -39,6 +41,7 @@ return [
'tests' => false,
'optional' => true,
'icon' => '/images/services/account.png',
'platforms' => ['client', 'server', 'console'],
],
'avatars' => [
'key' => 'avatars',
@ -52,6 +55,7 @@ return [
'tests' => false,
'optional' => true,
'icon' => '/images/services/avatars.png',
'platforms' => ['client', 'server', 'console'],
],
'databases' => [
'key' => 'databases',
@ -65,6 +69,7 @@ return [
'tests' => false,
'optional' => true,
'icon' => '/images/services/databases.png',
'platforms' => ['client', 'server', 'console'],
],
'tablesdb' => [
'key' => 'tablesdb',
@ -78,6 +83,7 @@ return [
'tests' => false,
'optional' => true,
'icon' => '/images/services/databases.png',
'platforms' => ['client', 'server', 'console'],
],
'locale' => [
'key' => 'locale',
@ -91,6 +97,7 @@ return [
'tests' => false,
'optional' => true,
'icon' => '/images/services/locale.png',
'platforms' => ['client', 'server', 'console'],
],
'health' => [
'key' => 'health',
@ -104,6 +111,7 @@ return [
'tests' => false,
'optional' => true,
'icon' => '/images/services/health.png',
'platforms' => ['client', 'server', 'console'],
],
'projects' => [
'key' => 'projects',
@ -117,6 +125,7 @@ return [
'tests' => false,
'optional' => false,
'icon' => '',
'platforms' => ['client', 'server', 'console'],
],
'project' => [
'key' => 'project',
@ -130,19 +139,21 @@ return [
'tests' => false,
'optional' => false,
'icon' => '',
'platforms' => ['client', 'server', 'console'],
],
'storage' => [
'key' => 'storage',
'name' => 'Storage',
'subtitle' => 'The Storage service allows you to manage your project files.',
'description' => '/docs/services/storage.md',
'controller' => 'api/storage.php',
'controller' => '',
'sdk' => true,
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/client/storage',
'tests' => false,
'optional' => true,
'icon' => '/images/services/storage.png',
'platforms' => ['client', 'server', 'console'],
],
'teams' => [
'key' => 'teams',
@ -156,6 +167,7 @@ return [
'tests' => false,
'optional' => true,
'icon' => '/images/services/teams.png',
'platforms' => ['client', 'server', 'console'],
],
'users' => [
'key' => 'users',
@ -169,6 +181,7 @@ return [
'tests' => false,
'optional' => true,
'icon' => '/images/services/users.png',
'platforms' => ['client', 'server', 'console'],
],
'vcs' => [
'key' => 'vcs',
@ -182,6 +195,7 @@ return [
'tests' => false,
'optional' => false,
'icon' => '',
'platforms' => ['client', 'server', 'console'],
],
'sites' => [
'key' => 'sites',
@ -195,6 +209,7 @@ return [
'tests' => false,
'optional' => true,
'icon' => '/images/services/sites.png',
'platforms' => ['client', 'server', 'console'],
],
'functions' => [
'key' => 'functions',
@ -208,6 +223,7 @@ return [
'tests' => false,
'optional' => true,
'icon' => '/images/services/functions.png',
'platforms' => ['client', 'server', 'console'],
],
'proxy' => [
'key' => 'proxy',
@ -221,6 +237,7 @@ return [
'tests' => false,
'optional' => false,
'icon' => '/images/services/proxy.png',
'platforms' => ['client', 'server', 'console'],
],
'mock' => [
'key' => 'mock',
@ -234,6 +251,7 @@ return [
'tests' => true,
'optional' => false,
'icon' => '',
'platforms' => ['client', 'server', 'console'],
],
'graphql' => [
'key' => 'graphql',
@ -247,19 +265,21 @@ return [
'tests' => true,
'optional' => true,
'icon' => '/images/services/graphql.png',
'platforms' => ['client', 'server', 'console'],
],
'console' => [
'key' => 'console',
'name' => 'Console',
'subtitle' => 'The Console service allows you to interact with console relevant informations.',
'subtitle' => 'The Console service allows you to interact with console relevant information.',
'description' => '',
'controller' => 'api/console.php',
'controller' => '', // Uses modules
'sdk' => true,
'docs' => true,
'docsUrl' => '',
'tests' => false,
'optional' => false,
'icon' => '',
'platforms' => ['client', 'server', 'console'],
],
'migrations' => [
'key' => 'migrations',
@ -273,6 +293,7 @@ return [
'tests' => true,
'optional' => false,
'icon' => '/images/services/migrations.png',
'platforms' => ['client', 'server', 'console'],
],
'messaging' => [
'key' => 'messaging',
@ -286,5 +307,6 @@ return [
'tests' => true,
'optional' => true,
'icon' => '/images/services/messaging.png',
'platforms' => ['client', 'server', 'console'],
]
];

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -357,6 +357,15 @@ return [
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_TRUSTED_HEADERS',
'description' => 'This option allows you to set the list of trusted headers, the value is a commaseparated list of HTTP header names, evaluated left-to-right for the first valid IP. Header names are treated case-insensitively.',
'introduction' => '1.8.0',
'default' => 'x-forwarded-for',
'required' => false,
'question' => '',
'filter' => ''
]
],
],

View file

@ -37,7 +37,7 @@ use libphonenumber\PhoneNumberUtil;
use MaxMind\Db\Reader;
use Utopia\Abuse\Abuse;
use Utopia\App;
use Utopia\Audit\Audit as EventAudit;
use Utopia\Audit\Audit;
use Utopia\Auth\Hashes\Sha;
use Utopia\Auth\Proofs\Code as ProofsCode;
use Utopia\Auth\Proofs\Password as ProofsPassword;
@ -68,13 +68,14 @@ use Utopia\Validator;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Assoc;
use Utopia\Validator\Boolean;
use Utopia\Validator\Range;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
$oauthDefaultSuccess = '/console/auth/oauth2/success';
$oauthDefaultFailure = '/console/auth/oauth2/failure';
function sendSessionAlert(Locale $locale, Document $user, Document $project, Document $session, Mail $queueForMails)
function sendSessionAlert(Locale $locale, Document $user, Document $project, array $platform, Document $session, Mail $queueForMails)
{
$subject = $locale->getText("emails.sessionAlert.subject");
$preview = $locale->getText("emails.sessionAlert.preview");
@ -157,13 +158,18 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc
$session->setAttribute('clientName', $clientName);
}
$projectName = $project->getAttribute('name');
if ($project->getId() === 'console') {
$projectName = $platform['platformName'];
}
$emailVariables = [
'direction' => $locale->getText('settings.direction'),
'date' => (new \DateTime())->format('F j'),
'year' => (new \DateTime())->format('YYYY'),
'time' => (new \DateTime())->format('H:i:s'),
'user' => $user->getAttribute('name'),
'project' => $project->getAttribute('name'),
'project' => $projectName,
'device' => $session->getAttribute('clientName'),
'ipAddress' => $session->getAttribute('ip'),
'country' => $locale->getText('countries.' . $session->getAttribute('countryCode'), $locale->getText('locale.country.unknown')),
@ -171,13 +177,14 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc
if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) {
$emailVariables = array_merge($emailVariables, [
'accentColor' => APP_EMAIL_ACCENT_COLOR,
'logoUrl' => APP_EMAIL_LOGO_URL,
'twitterUrl' => APP_SOCIAL_TWITTER,
'discordUrl' => APP_SOCIAL_DISCORD,
'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE,
'termsUrl' => APP_EMAIL_TERMS_URL,
'privacyUrl' => APP_EMAIL_PRIVACY_URL,
'accentColor' => $platform['accentColor'],
'logoUrl' => $platform['logoUrl'],
'twitter' => $platform['twitterUrl'],
'discord' => $platform['discordUrl'],
'github' => $platform['githubUrl'],
'terms' => $platform['termsUrl'],
'privacy' => $platform['privacyUrl'],
'platform' => $platform['platformName'],
]);
}
@ -189,12 +196,18 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc
->setBody($body)
->setBodyTemplate($bodyTemplate)
->setVariables($emailVariables)
->setRecipient($email)
->trigger();
}
;
->setRecipient($email);
$createSession = function (string $userId, string $secret, Request $request, Response $response, User $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Store $store, ProofsToken $proofForToken, ProofsCode $proofForCode) {
// since this is console project, set email sender name!
if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) {
$queueForMails->setSenderName($platform['emailSenderName']);
}
$queueForMails->trigger();
}
$createSession = function (string $userId, string $secret, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Store $store, ProofsToken $proofForToken, ProofsCode $proofForCode) {
/** @var Appwrite\Utopia\Database\Documents\User $userFromRequest */
$userFromRequest = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId));
@ -295,7 +308,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
]) !== 1;
if ($isAllowedTokenType && $hasUserEmail && $isSessionAlertsEnabled && $isNotFirstSession) {
sendSessionAlert($locale, $user, $project, $session, $queueForMails);
sendSessionAlert($locale, $user, $project, $platform, $session, $queueForMails);
}
$queueForEvents
@ -344,7 +357,7 @@ App::post('/v1/account')
group: 'account',
name: 'create',
description: '/docs/references/account/create.md',
auth: [],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -502,7 +515,7 @@ App::get('/v1/account')
group: 'account',
name: 'get',
description: '/docs/references/account/get.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -585,7 +598,7 @@ App::get('/v1/account/sessions')
group: 'sessions',
name: 'listSessions',
description: '/docs/references/account/list-sessions.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -633,7 +646,7 @@ App::delete('/v1/account/sessions')
group: 'sessions',
name: 'deleteSessions',
description: '/docs/references/account/delete-sessions.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
@ -705,7 +718,7 @@ App::get('/v1/account/sessions/:sessionId')
group: 'sessions',
name: 'getSession',
description: '/docs/references/account/get-session.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -756,7 +769,7 @@ App::delete('/v1/account/sessions/:sessionId')
group: 'sessions',
name: 'deleteSession',
description: '/docs/references/account/delete-session.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
@ -844,7 +857,7 @@ App::patch('/v1/account/sessions/:sessionId')
group: 'sessions',
name: 'updateSession',
description: '/docs/references/account/update-session.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -935,7 +948,7 @@ App::post('/v1/account/sessions/email')
group: 'sessions',
name: 'createEmailPasswordSession',
description: '/docs/references/account/create-session-email-password.md',
auth: [],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -953,6 +966,7 @@ App::post('/v1/account/sessions/email')
->inject('user')
->inject('dbForProject')
->inject('project')
->inject('platform')
->inject('locale')
->inject('geodb')
->inject('queueForEvents')
@ -961,7 +975,7 @@ App::post('/v1/account/sessions/email')
->inject('store')
->inject('proofForPassword')
->inject('proofForToken')
->action(function (string $email, string $password, Request $request, Response $response, User $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) {
->action(function (string $email, string $password, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) {
$email = \strtolower($email);
$protocol = $request->getProtocol();
@ -1062,7 +1076,7 @@ App::post('/v1/account/sessions/email')
Query::equal('userId', [$user->getId()]),
]) !== 1
) {
sendSessionAlert($locale, $user, $project, $session, $queueForMails);
sendSessionAlert($locale, $user, $project, $platform, $session, $queueForMails);
}
}
@ -1083,7 +1097,7 @@ App::post('/v1/account/sessions/anonymous')
group: 'sessions',
name: 'createAnonymousSession',
description: '/docs/references/account/create-session-anonymous.md',
auth: [],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -1232,7 +1246,7 @@ App::post('/v1/account/sessions/token')
group: 'sessions',
name: 'createSession',
description: '/docs/references/account/create-session.md',
auth: [],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -1250,6 +1264,7 @@ App::post('/v1/account/sessions/token')
->inject('user')
->inject('dbForProject')
->inject('project')
->inject('platform')
->inject('locale')
->inject('geodb')
->inject('queueForEvents')
@ -1270,7 +1285,7 @@ App::get('/v1/account/sessions/oauth2/:provider')
name: 'createOAuth2Session',
description: '/docs/references/account/create-session-oauth2.md',
type: MethodType::WEBAUTH,
auth: [],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_MOVED_PERMANENTLY,
@ -1278,7 +1293,7 @@ App::get('/v1/account/sessions/oauth2/:provider')
)
],
contentType: ContentType::HTML,
hide: [APP_PLATFORM_SERVER],
hide: [APP_SDK_PLATFORM_SERVER],
))
->label('abuse-limit', 50)
->label('abuse-key', 'ip:{ip}')
@ -1620,9 +1635,6 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$failureRedirect(Exception::USER_UNAUTHORIZED, 'OAuth provider failed to return email.');
}
/**
* Is verified is not used yet, since we don't know after an account is created anymore if it was verified or not.
*/
$isVerified = $oauth2->isEmailVerified($accessToken);
$identity = $dbForProject->findOne('identities', [
@ -1634,16 +1646,32 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$user = $dbForProject->getDocument('users', $identity->getAttribute('userId'));
}
// If user is not found, check if there is an identity with the same provider user ID
// If user is not found, check if there is a user with the same email
if ($user === false || $user->isEmpty()) {
$userWithEmail = $dbForProject->findOne('users', [
Query::equal('email', [$email]),
]);
if (!$userWithEmail->isEmpty()) {
if (!$isVerified) {
$failureRedirect(Exception::GENERAL_BAD_REQUEST);
}
$user->setAttributes($userWithEmail->getArrayCopy());
}
}
// If user is not found, check if there is an identity with the same email
if ($user === false || $user->isEmpty()) {
$identityWithMatchingEmail = $dbForProject->findOne('identities', [
Query::equal('providerEmail', [$email]),
]);
if (!$identityWithMatchingEmail->isEmpty()) {
if (!$isVerified) {
$failureRedirect(Exception::GENERAL_BAD_REQUEST);
}
$user->setAttributes($dbForProject->getDocument('users', $identityWithMatchingEmail->getAttribute('userId'))->getArrayCopy());
}
}
if ($user === false || $user->isEmpty()) { // Last option -> create the user
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
@ -1655,14 +1683,6 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
}
}
// Makes sure this email is not already used in another identity
$identityWithMatchingEmail = $dbForProject->findOne('identities', [
Query::equal('providerEmail', [$email]),
]);
if (!$identityWithMatchingEmail->isEmpty()) {
$failureRedirect(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
}
try {
$emailCanonical = new Email($email);
} catch (Throwable) {
@ -1716,7 +1736,6 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
'providerType' => MESSAGE_TYPE_EMAIL,
'identifier' => $email,
]));
} catch (Duplicate) {
$failureRedirect(Exception::USER_ALREADY_EXISTS);
}
@ -1932,7 +1951,7 @@ App::get('/v1/account/tokens/oauth2/:provider')
group: 'tokens',
name: 'createOAuth2Token',
description: '/docs/references/account/create-token-oauth2.md',
auth: [],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_MOVED_PERMANENTLY,
@ -1953,7 +1972,16 @@ App::get('/v1/account/tokens/oauth2/:provider')
->inject('project')
->inject('platform')
->action(function (string $provider, string $success, string $failure, array $scopes, Request $request, Response $response, Document $project, array $platform) use ($oauthDefaultSuccess, $oauthDefaultFailure) {
$callback = $platform['endpoint'] . '/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$port = $request->getPort();
$callbackBase = $protocol . '://' . $request->getHostname();
if ($protocol === 'https' && $port !== '443') {
$callbackBase .= ':' . $port;
} elseif ($protocol === 'http' && $port !== '80') {
$callbackBase .= ':' . $port;
}
$callback = $callbackBase . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$providerEnabled = $project->getAttribute('oAuthProviders', [])[$provider . 'Enabled'] ?? false;
if (!$providerEnabled) {
@ -2022,7 +2050,7 @@ App::post('/v1/account/tokens/magic-url')
group: 'tokens',
name: 'createMagicURLToken',
description: '/docs/references/account/create-token-magic-url.md',
auth: [],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -2242,11 +2270,16 @@ App::post('/v1/account/tokens/magic-url')
->setSmtpSenderName($senderName);
}
$projectName = $project->getAttribute('name');
if ($project->getId() === 'console') {
$projectName = $platform['platformName'];
}
$emailVariables = [
'direction' => $locale->getText('settings.direction'),
// {{user}}, {{redirect}} and {{project}} are required in default and custom templates
'user' => $user->getAttribute('name'),
'project' => $project->getAttribute('name'),
'project' => $projectName,
'redirect' => $url,
'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN',
'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN',
@ -2261,8 +2294,13 @@ App::post('/v1/account/tokens/magic-url')
->setPreview($preview)
->setBody($body)
->setVariables($emailVariables)
->setRecipient($email)
->trigger();
->setRecipient($email);
if ($project->getId() === 'console') {
$queueForMails->setSenderName($platform['emailSenderName']);
}
$queueForMails->trigger();
$token->setAttribute('secret', $tokenSecret);
@ -2291,7 +2329,7 @@ App::post('/v1/account/tokens/email')
group: 'tokens',
name: 'createEmailToken',
description: '/docs/references/account/create-token-email.md',
auth: [],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -2309,13 +2347,14 @@ App::post('/v1/account/tokens/email')
->inject('response')
->inject('user')
->inject('project')
->inject('platform')
->inject('dbForProject')
->inject('locale')
->inject('queueForEvents')
->inject('queueForMails')
->inject('proofForPassword')
->inject('proofForCode')
->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, User $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword, ProofsCode $proofForCode) {
->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, User $user, Document $project, array $platform, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword, ProofsCode $proofForCode) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
}
@ -2516,12 +2555,17 @@ App::post('/v1/account/tokens/email')
->setSmtpSenderName($senderName);
}
$projectName = $project->getAttribute('name');
if ($project->getId() === 'console') {
$projectName = $platform['platformName'];
}
$emailVariables = [
'heading' => $heading,
'direction' => $locale->getText('settings.direction'),
// {{user}}, {{project}} and {{otp}} are required in the templates
'user' => $user->getAttribute('name'),
'project' => $project->getAttribute('name'),
'project' => $projectName,
'otp' => $tokenSecret,
'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN',
'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN',
@ -2533,13 +2577,14 @@ App::post('/v1/account/tokens/email')
if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) {
$emailVariables = array_merge($emailVariables, [
'accentColor' => APP_EMAIL_ACCENT_COLOR,
'logoUrl' => APP_EMAIL_LOGO_URL,
'twitterUrl' => APP_SOCIAL_TWITTER,
'discordUrl' => APP_SOCIAL_DISCORD,
'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE,
'termsUrl' => APP_EMAIL_TERMS_URL,
'privacyUrl' => APP_EMAIL_PRIVACY_URL,
'accentColor' => $platform['accentColor'],
'logoUrl' => $platform['logoUrl'],
'twitter' => $platform['twitterUrl'],
'discord' => $platform['discordUrl'],
'github' => $platform['githubUrl'],
'terms' => $platform['termsUrl'],
'privacy' => $platform['privacyUrl'],
'platform' => $platform['platformName'],
]);
}
@ -2549,8 +2594,14 @@ App::post('/v1/account/tokens/email')
->setBody($body)
->setBodyTemplate($bodyTemplate)
->setVariables($emailVariables)
->setRecipient($email)
->trigger();
->setRecipient($email);
// since this is console project, set email sender name!
if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) {
$queueForMails->setSenderName($platform['emailSenderName']);
}
$queueForMails->trigger();
$token->setAttribute('secret', $tokenSecret);
@ -2579,7 +2630,7 @@ App::put('/v1/account/sessions/magic-url')
group: 'sessions',
name: 'updateMagicURLSession',
description: '/docs/references/account/create-session.md',
auth: [],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -2601,16 +2652,17 @@ App::put('/v1/account/sessions/magic-url')
->inject('user')
->inject('dbForProject')
->inject('project')
->inject('platform')
->inject('locale')
->inject('geodb')
->inject('queueForEvents')
->inject('queueForMails')
->inject('store')
->inject('proofForCode')
->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForCode) use ($createSession) {
->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForCode) use ($createSession) {
$proofForToken = new ProofsToken(TOKEN_LENGTH_MAGIC_URL);
$proofForToken->setHash(new Sha());
$createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForToken, $proofForCode);
$createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForToken, $proofForCode);
});
App::put('/v1/account/sessions/phone')
@ -2626,7 +2678,7 @@ App::put('/v1/account/sessions/phone')
group: 'sessions',
name: 'updatePhoneSession',
description: '/docs/references/account/create-session.md',
auth: [],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -2648,6 +2700,7 @@ App::put('/v1/account/sessions/phone')
->inject('user')
->inject('dbForProject')
->inject('project')
->inject('platform')
->inject('locale')
->inject('geodb')
->inject('queueForEvents')
@ -2671,7 +2724,7 @@ App::post('/v1/account/tokens/phone')
group: 'tokens',
name: 'createPhoneToken',
description: '/docs/references/account/create-token-phone.md',
auth: [],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -2688,6 +2741,7 @@ App::post('/v1/account/tokens/phone')
->inject('response')
->inject('user')
->inject('project')
->inject('platform')
->inject('dbForProject')
->inject('queueForEvents')
->inject('queueForMessaging')
@ -2697,7 +2751,7 @@ App::post('/v1/account/tokens/phone')
->inject('plan')
->inject('store')
->inject('proofForCode')
->action(function (string $userId, string $phone, Request $request, Response $response, User $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Store $store, ProofsCode $proofForCode) {
->action(function (string $userId, string $phone, Request $request, Response $response, User $user, Document $project, array $platform, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Store $store, ProofsCode $proofForCode) {
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
}
@ -2814,9 +2868,14 @@ App::post('/v1/account/tokens/phone')
$message = $customTemplate['message'] ?? $message;
}
$projectName = $project->getAttribute('name');
if ($project->getId() === 'console') {
$projectName = $platform['platformName'];
}
$messageContent = Template::fromString($locale->getText("sms.verification.body"));
$messageContent
->setParam('{{project}}', $project->getAttribute('name'))
->setParam('{{project}}', $projectName)
->setParam('{{secret}}', $secret);
$messageContent = \strip_tags($messageContent->render());
$message = $message->setParam('{{token}}', $messageContent);
@ -2886,7 +2945,7 @@ App::post('/v1/account/jwts')
group: 'tokens',
name: 'createJWT',
description: '/docs/references/account/create-jwt.md',
auth: [],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -2895,20 +2954,22 @@ App::post('/v1/account/jwts')
],
contentType: ContentType::JSON,
))
->label('abuse-limit', 100)
->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true)
->label('abuse-limit', APP_LIMIT_WRITE_RATE_DEFAULT * 2)
->label('abuse-time', APP_LIMIT_WRITE_RATE_PERIOD_DEFAULT)
->label('abuse-key', 'url:{url},userId:{userId}')
->inject('response')
->inject('user')
->inject('store')
->inject('proofForToken')
->action(function (Response $response, User $user, Store $store, ProofsToken $proofForToken) {
->action(function (int $duration, Response $response, User $user, Store $store, ProofsToken $proofForToken) {
$sessionId = $user->sessionVerify($store->getProperty('secret', ''), $proofForToken);
if (!$sessionId) {
throw new Exception(Exception::USER_SESSION_NOT_FOUND);
}
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 0);
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $duration, 0);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@ -2929,7 +2990,7 @@ App::get('/v1/account/prefs')
group: 'account',
name: 'getPrefs',
description: '/docs/references/account/get-prefs.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -2956,7 +3017,7 @@ App::get('/v1/account/logs')
group: 'logs',
name: 'listLogs',
description: '/docs/references/account/list-logs.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -2972,7 +3033,8 @@ App::get('/v1/account/logs')
->inject('locale')
->inject('geodb')
->inject('dbForProject')
->action(function (array $queries, bool $includeTotal, Response $response, Document $user, Locale $locale, Reader $geodb, Database $dbForProject) {
->inject('audit')
->action(function (array $queries, bool $includeTotal, Response $response, Document $user, Locale $locale, Reader $geodb, Database $dbForProject, Audit $audit) {
try {
$queries = Query::parseQueries($queries);
@ -2980,9 +3042,10 @@ App::get('/v1/account/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$audit = new EventAudit($dbForProject);
$logs = $audit->getLogsByUser($user->getSequence(), $queries);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$logs = $audit->getLogsByUser($user->getSequence(), offset: $offset, limit: $limit);
$output = [];
@ -3011,7 +3074,7 @@ App::get('/v1/account/logs')
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByUser($user->getSequence(), $queries) : 0,
'total' => $includeTotal ? $audit->countLogsByUser($user->getSequence()) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@ -3028,7 +3091,7 @@ App::patch('/v1/account/name')
group: 'account',
name: 'updateName',
description: '/docs/references/account/update-name.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -3066,7 +3129,7 @@ App::patch('/v1/account/password')
group: 'account',
name: 'updatePassword',
description: '/docs/references/account/update-password.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -3158,7 +3221,7 @@ App::patch('/v1/account/email')
group: 'account',
name: 'updateEmail',
description: '/docs/references/account/update-email.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -3269,7 +3332,7 @@ App::patch('/v1/account/phone')
group: 'account',
name: 'updatePhone',
description: '/docs/references/account/update-phone.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -3357,7 +3420,7 @@ App::patch('/v1/account/prefs')
group: 'account',
name: 'updatePrefs',
description: '/docs/references/account/update-prefs.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -3395,7 +3458,7 @@ App::patch('/v1/account/status')
group: 'account',
name: 'updateStatus',
description: '/docs/references/account/update-status.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -3446,7 +3509,7 @@ App::post('/v1/account/recovery')
group: 'recovery',
name: 'createRecovery',
description: '/docs/references/account/create-recovery.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -3464,15 +3527,16 @@ App::post('/v1/account/recovery')
->inject('user')
->inject('dbForProject')
->inject('project')
->inject('platform')
->inject('locale')
->inject('queueForMails')
->inject('queueForEvents')
->inject('proofForToken')
->action(function (string $email, string $url, Request $request, Response $response, User $user, Database $dbForProject, Document $project, Locale $locale, Mail $queueForMails, Event $queueForEvents, ProofsToken $proofForToken) {
->action(function (string $email, string $url, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Mail $queueForMails, Event $queueForEvents, ProofsToken $proofForToken) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
}
$url = htmlentities($url);
$email = \strtolower($email);
@ -3519,7 +3583,14 @@ App::post('/v1/account/recovery')
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret, 'expire' => $expire]);
$url = Template::unParseURL($url);
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]');
$projectName = $project->isEmpty()
? 'Console'
: $project->getAttribute('name', '[APP-NAME]');
if ($project->getId() === 'console') {
$projectName = $platform['platformName'];
}
$body = $locale->getText("emails.recovery.body");
$subject = $locale->getText("emails.recovery.subject");
$preview = $locale->getText("emails.recovery.preview");
@ -3597,8 +3668,13 @@ App::post('/v1/account/recovery')
->setBody($body)
->setVariables($emailVariables)
->setSubject($subject)
->setPreview($preview)
->trigger();
->setPreview($preview);
if ($project->getId() === 'console') {
$queueForMails->setSenderName($platform['emailSenderName']);
}
$queueForMails->trigger();
$recovery->setAttribute('secret', $secret);
@ -3626,7 +3702,7 @@ App::put('/v1/account/recovery')
group: 'recovery',
name: 'updateRecovery',
description: '/docs/references/account/update-recovery.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -3723,7 +3799,7 @@ App::post('/v1/account/verifications/email')
group: 'verification',
name: 'createEmailVerification',
description: '/docs/references/account/create-email-verification.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -3737,7 +3813,7 @@ App::post('/v1/account/verifications/email')
group: 'verification',
name: 'createVerification',
description: '/docs/references/account/create-email-verification.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -3758,13 +3834,14 @@ App::post('/v1/account/verifications/email')
->inject('request')
->inject('response')
->inject('project')
->inject('platform')
->inject('user')
->inject('dbForProject')
->inject('locale')
->inject('queueForEvents')
->inject('queueForMails')
->inject('proofForToken')
->action(function (string $url, Request $request, Response $response, Document $project, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsToken $proofForToken) {
->action(function (string $url, Request $request, Response $response, Document $project, array $platform, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsToken $proofForToken) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
@ -3808,7 +3885,15 @@ App::post('/v1/account/verifications/email')
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $verificationSecret, 'expire' => $expire]);
$url = Template::unParseURL($url);
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]');
$projectName = $project->isEmpty()
? 'Console'
: $project->getAttribute('name', '[APP-NAME]');
if ($project->getId() === 'console') {
$projectName = $platform['platformName'];
}
$body = $locale->getText("emails.verification.body");
$preview = $locale->getText("emails.verification.preview");
$subject = $locale->getText("emails.verification.subject");
@ -3894,13 +3979,14 @@ App::post('/v1/account/verifications/email')
if ($smtpBaseTemplate === APP_BRANDED_EMAIL_BASE_TEMPLATE) {
$emailVariables = array_merge($emailVariables, [
'accentColor' => APP_EMAIL_ACCENT_COLOR,
'logoUrl' => APP_EMAIL_LOGO_URL,
'twitterUrl' => APP_SOCIAL_TWITTER,
'discordUrl' => APP_SOCIAL_DISCORD,
'githubUrl' => APP_SOCIAL_GITHUB_APPWRITE,
'termsUrl' => APP_EMAIL_TERMS_URL,
'privacyUrl' => APP_EMAIL_PRIVACY_URL,
'accentColor' => $platform['accentColor'],
'logoUrl' => $platform['logoUrl'],
'twitter' => $platform['twitterUrl'],
'discord' => $platform['discordUrl'],
'github' => $platform['githubUrl'],
'terms' => $platform['termsUrl'],
'privacy' => $platform['privacyUrl'],
'platform' => $platform['platformName'],
]);
}
@ -3911,8 +3997,13 @@ App::post('/v1/account/verifications/email')
->setBodyTemplate($bodyTemplate)
->setVariables($emailVariables)
->setRecipient($user->getAttribute('email'))
->setName($user->getAttribute('name') ?? '')
->trigger();
->setName($user->getAttribute('name') ?? '');
if ($project->getId() === 'console') {
$queueForMails->setSenderName($platform['emailSenderName']);
}
$queueForMails->trigger();
$verification->setAttribute('secret', $verificationSecret);
@ -3940,7 +4031,7 @@ App::put('/v1/account/verifications/email')
group: 'verification',
name: 'updateEmailVerification',
description: '/docs/references/account/update-email-verification.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -3954,7 +4045,7 @@ App::put('/v1/account/verifications/email')
group: 'verification',
name: 'updateVerification',
description: '/docs/references/account/update-email-verification.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -4029,7 +4120,7 @@ App::post('/v1/account/verifications/phone')
group: 'verification',
name: 'createPhoneVerification',
description: '/docs/references/account/create-phone-verification.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -4179,7 +4270,7 @@ App::put('/v1/account/verifications/phone')
group: 'verification',
name: 'updatePhoneVerification',
description: '/docs/references/account/update-phone-verification.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -4245,7 +4336,7 @@ App::post('/v1/account/targets/push')
group: 'pushTargets',
name: 'createPushTarget',
description: '/docs/references/account/create-push-target.md',
auth: [AuthType::SESSION],
auth: [AuthType::ADMIN, AuthType::SESSION],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -4328,7 +4419,7 @@ App::put('/v1/account/targets/:targetId/push')
group: 'pushTargets',
name: 'updatePushTarget',
description: '/docs/references/account/update-push-target.md',
auth: [AuthType::SESSION],
auth: [AuthType::ADMIN, AuthType::SESSION],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -4393,7 +4484,7 @@ App::delete('/v1/account/targets/:targetId/push')
group: 'pushTargets',
name: 'deletePushTarget',
description: '/docs/references/account/delete-push-target.md',
auth: [AuthType::SESSION],
auth: [AuthType::ADMIN, AuthType::SESSION],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
@ -4444,7 +4535,7 @@ App::get('/v1/account/identities')
group: 'identities',
name: 'listIdentities',
description: '/docs/references/account/list-identities.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -4520,7 +4611,7 @@ App::delete('/v1/account/identities/:identityId')
group: 'identities',
name: 'deleteIdentity',
description: '/docs/references/account/delete-identity.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,

View file

@ -178,7 +178,7 @@ App::get('/v1/avatars/credit-cards/:code')
group: null,
name: 'getCreditCard',
description: '/docs/references/avatars/get-credit-card.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
type: MethodType::LOCATION,
responses: [
new SDKResponse(
@ -206,7 +206,7 @@ App::get('/v1/avatars/browsers/:code')
group: null,
name: 'getBrowser',
description: '/docs/references/avatars/get-browser.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
type: MethodType::LOCATION,
responses: [
new SDKResponse(
@ -234,7 +234,7 @@ App::get('/v1/avatars/flags/:code')
group: null,
name: 'getFlag',
description: '/docs/references/avatars/get-flag.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
type: MethodType::LOCATION,
responses: [
new SDKResponse(
@ -262,7 +262,7 @@ App::get('/v1/avatars/image')
group: null,
name: 'getImage',
description: '/docs/references/avatars/get-image.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
type: MethodType::LOCATION,
responses: [
new SDKResponse(
@ -333,7 +333,7 @@ App::get('/v1/avatars/favicon')
group: null,
name: 'getFavicon',
description: '/docs/references/avatars/get-favicon.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
type: MethodType::LOCATION,
responses: [
new SDKResponse(
@ -507,7 +507,7 @@ App::get('/v1/avatars/qr')
group: null,
name: 'getQR',
description: '/docs/references/avatars/get-qr.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
type: MethodType::LOCATION,
responses: [
new SDKResponse(
@ -557,7 +557,7 @@ App::get('/v1/avatars/initials')
group: null,
name: 'getInitials',
description: '/docs/references/avatars/get-initials.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
type: MethodType::LOCATION,
responses: [
new SDKResponse(
@ -652,7 +652,7 @@ App::get('/v1/avatars/screenshots')
group: null,
name: 'getScreenshot',
description: '/docs/references/avatars/get-screenshot.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
type: MethodType::LOCATION,
responses: [
new SDKResponse(

View file

@ -1,147 +0,0 @@
<?php
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Database\Document;
use Utopia\Domains\Domain;
use Utopia\System\System;
use Utopia\Validator\IP;
use Utopia\Validator\Text;
App::init()
->groups(['console'])
->inject('project')
->action(function (Document $project) {
if ($project->getId() !== 'console') {
throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN);
}
});
App::get('/v1/console/variables')
->desc('Get variables')
->groups(['api', 'projects'])
->label('scope', 'projects.read')
->label('sdk', new Method(
namespace: 'console',
group: 'console',
name: 'variables',
description: '/docs/references/console/variables.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_CONSOLE_VARIABLES,
)
],
contentType: ContentType::JSON
))
->inject('response')
->action(function (Response $response) {
$validator = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME'));
$isCNAMEValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_CNAME', '')) && $validator->isKnown() && !$validator->isTest();
$validator = new IP(IP::V4);
$isAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_A', '')) && ($validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_A')));
$validator = new IP(IP::V6);
$isAAAAValid = !empty(System::getEnv('_APP_DOMAIN_TARGET_AAAA', '')) && $validator->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA'));
$isDomainEnabled = $isAAAAValid || $isAValid || $isCNAMEValid;
$isVcsEnabled = !empty(System::getEnv('_APP_VCS_GITHUB_APP_NAME', ''))
&& !empty(System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY', ''))
&& !empty(System::getEnv('_APP_VCS_GITHUB_APP_ID', ''))
&& !empty(System::getEnv('_APP_VCS_GITHUB_CLIENT_ID', ''))
&& !empty(System::getEnv('_APP_VCS_GITHUB_CLIENT_SECRET', ''));
$isAssistantEnabled = !empty(System::getEnv('_APP_ASSISTANT_OPENAI_API_KEY', ''));
$variables = new Document([
'_APP_DOMAIN_TARGET_CNAME' => System::getEnv('_APP_DOMAIN_TARGET_CNAME'),
'_APP_DOMAIN_TARGET_AAAA' => System::getEnv('_APP_DOMAIN_TARGET_AAAA'),
'_APP_DOMAIN_TARGET_A' => System::getEnv('_APP_DOMAIN_TARGET_A'),
// Combine CAA domain with most common flags and tag (no parameters)
'_APP_DOMAIN_TARGET_CAA' => '0 issue "' . System::getEnv('_APP_DOMAIN_TARGET_CAA') . '"',
'_APP_STORAGE_LIMIT' => +System::getEnv('_APP_STORAGE_LIMIT'),
'_APP_COMPUTE_BUILD_TIMEOUT' => +System::getEnv('_APP_COMPUTE_BUILD_TIMEOUT'),
'_APP_COMPUTE_SIZE_LIMIT' => +System::getEnv('_APP_COMPUTE_SIZE_LIMIT'),
'_APP_USAGE_STATS' => System::getEnv('_APP_USAGE_STATS'),
'_APP_VCS_ENABLED' => $isVcsEnabled,
'_APP_DOMAIN_ENABLED' => $isDomainEnabled,
'_APP_ASSISTANT_ENABLED' => $isAssistantEnabled,
'_APP_DOMAIN_SITES' => System::getEnv('_APP_DOMAIN_SITES'),
'_APP_DOMAIN_FUNCTIONS' => System::getEnv('_APP_DOMAIN_FUNCTIONS'),
'_APP_OPTIONS_FORCE_HTTPS' => System::getEnv('_APP_OPTIONS_FORCE_HTTPS'),
'_APP_DOMAINS_NAMESERVERS' => System::getEnv('_APP_DOMAINS_NAMESERVERS'),
]);
$response->dynamic($variables, Response::MODEL_CONSOLE_VARIABLES);
});
App::post('/v1/console/assistant')
->desc('Create assistant query')
->groups(['api', 'assistant'])
->label('scope', 'assistant.read')
->label('sdk', new Method(
namespace: 'assistant',
group: 'console',
name: 'chat',
description: '/docs/references/assistant/chat.md',
auth: [AuthType::ADMIN],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_NONE,
)
],
contentType: ContentType::TEXT
))
->label('abuse-limit', 15)
->label('abuse-key', 'userId:{userId}')
->param('prompt', '', new Text(2000), 'Prompt. A string containing questions asked to the AI assistant.')
->inject('response')
->action(function (string $prompt, Response $response) {
$ch = curl_init('http://appwrite-assistant:3003/v1/models/assistant/prompt');
$responseHeaders = [];
$query = json_encode(['prompt' => $prompt]);
$headers = ['accept: text/event-stream'];
$handleEvent = function ($ch, $data) use ($response) {
$response->chunk($data);
return \strlen($data);
};
curl_setopt($ch, CURLOPT_WRITEFUNCTION, $handleEvent);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0);
curl_setopt($ch, CURLOPT_TIMEOUT, 9000);
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) {
$len = strlen($header);
$header = explode(':', $header, 2);
if (count($header) < 2) { // ignore invalid headers
return $len;
}
$responseHeaders[strtolower(trim($header[0]))] = trim($header[1]);
return $len;
});
curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
curl_exec($ch);
curl_close($ch);
$response->chunk('', true);
});

View file

@ -46,7 +46,7 @@ App::get('/v1/graphql')
namespace: 'graphql',
group: 'graphql',
name: 'get',
auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT],
hide: true,
description: '/docs/references/graphql/get.md',
responses: [
@ -93,7 +93,7 @@ App::post('/v1/graphql/mutation')
namespace: 'graphql',
group: 'graphql',
name: 'mutation',
auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT],
description: '/docs/references/graphql/post.md',
responses: [
new SDKResponse(
@ -144,7 +144,7 @@ App::post('/v1/graphql')
namespace: 'graphql',
group: 'graphql',
name: 'query',
auth: [AuthType::KEY, AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::KEY, AuthType::SESSION, AuthType::JWT],
description: '/docs/references/graphql/post.md',
responses: [
new SDKResponse(

View file

@ -50,7 +50,7 @@ App::get('/v1/health')
group: 'health',
name: 'get',
description: '/docs/references/health/get.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -89,7 +89,7 @@ App::get('/v1/health/db')
group: 'health',
name: 'getDB',
description: '/docs/references/health/get-db.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -150,7 +150,7 @@ App::get('/v1/health/cache')
group: 'health',
name: 'getCache',
description: '/docs/references/health/get-cache.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -210,7 +210,7 @@ App::get('/v1/health/pubsub')
group: 'health',
name: 'getPubSub',
description: '/docs/references/health/get-pubsub.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -270,7 +270,7 @@ App::get('/v1/health/time')
group: 'health',
name: 'getTime',
description: '/docs/references/health/get-time.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -334,7 +334,7 @@ App::get('/v1/health/queue/webhooks')
group: 'queue',
name: 'getQueueWebhooks',
description: '/docs/references/health/get-queue-webhooks.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -367,7 +367,7 @@ App::get('/v1/health/queue/logs')
group: 'queue',
name: 'getQueueLogs',
description: '/docs/references/health/get-queue-logs.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -400,7 +400,7 @@ App::get('/v1/health/certificate')
group: 'health',
name: 'getCertificate',
description: '/docs/references/health/get-certificate.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -457,7 +457,7 @@ App::get('/v1/health/queue/certificates')
group: 'queue',
name: 'getQueueCertificates',
description: '/docs/references/health/get-queue-certificates.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -490,7 +490,7 @@ App::get('/v1/health/queue/builds')
group: 'queue',
name: 'getQueueBuilds',
description: '/docs/references/health/get-queue-builds.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -523,7 +523,7 @@ App::get('/v1/health/queue/databases')
group: 'queue',
name: 'getQueueDatabases',
description: '/docs/references/health/get-queue-databases.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -556,7 +556,7 @@ App::get('/v1/health/queue/deletes')
group: 'queue',
name: 'getQueueDeletes',
description: '/docs/references/health/get-queue-deletes.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -589,7 +589,7 @@ App::get('/v1/health/queue/mails')
group: 'queue',
name: 'getQueueMails',
description: '/docs/references/health/get-queue-mails.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -622,7 +622,7 @@ App::get('/v1/health/queue/messaging')
group: 'queue',
name: 'getQueueMessaging',
description: '/docs/references/health/get-queue-messaging.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -655,7 +655,7 @@ App::get('/v1/health/queue/migrations')
group: 'queue',
name: 'getQueueMigrations',
description: '/docs/references/health/get-queue-migrations.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -688,7 +688,7 @@ App::get('/v1/health/queue/functions')
group: 'queue',
name: 'getQueueFunctions',
description: '/docs/references/health/get-queue-functions.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -721,7 +721,7 @@ App::get('/v1/health/queue/stats-resources')
group: 'queue',
name: 'getQueueStatsResources',
description: '/docs/references/health/get-queue-stats-resources.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -754,7 +754,7 @@ App::get('/v1/health/queue/stats-usage')
group: 'queue',
name: 'getQueueUsage',
description: '/docs/references/health/get-queue-stats-usage.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -787,7 +787,7 @@ App::get('/v1/health/storage/local')
group: 'storage',
name: 'getStorageLocal',
description: '/docs/references/health/get-storage-local.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -837,7 +837,7 @@ App::get('/v1/health/storage')
group: 'storage',
name: 'getStorage',
description: '/docs/references/health/get-storage.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -889,7 +889,7 @@ App::get('/v1/health/anti-virus')
group: 'health',
name: 'getAntivirus',
description: '/docs/references/health/get-storage-anti-virus.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -935,7 +935,7 @@ App::get('/v1/health/queue/failed/:name')
group: 'queue',
name: 'getFailedJobs',
description: '/docs/references/health/get-failed-queue-jobs.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -945,18 +945,18 @@ App::get('/v1/health/queue/failed/:name')
contentType: ContentType::JSON
))
->param('name', '', new WhiteList([
Event::DATABASE_QUEUE_NAME,
Event::DELETE_QUEUE_NAME,
Event::AUDITS_QUEUE_NAME,
Event::MAILS_QUEUE_NAME,
Event::FUNCTIONS_QUEUE_NAME,
Event::STATS_RESOURCES_QUEUE_NAME,
Event::STATS_USAGE_QUEUE_NAME,
Event::WEBHOOK_QUEUE_NAME,
Event::CERTIFICATES_QUEUE_NAME,
Event::BUILDS_QUEUE_NAME,
Event::MESSAGING_QUEUE_NAME,
Event::MIGRATIONS_QUEUE_NAME
System::getEnv('_APP_DATABASE_QUEUE_NAME', Event::DATABASE_QUEUE_NAME),
System::getEnv('_APP_DELETE_QUEUE_NAME', Event::DELETE_QUEUE_NAME),
System::getEnv('_APP_AUDITS_QUEUE_NAME', Event::AUDITS_QUEUE_NAME),
System::getEnv('_APP_MAILS_QUEUE_NAME', Event::MAILS_QUEUE_NAME),
System::getEnv('_APP_FUNCTIONS_QUEUE_NAME', Event::FUNCTIONS_QUEUE_NAME),
System::getEnv('_APP_STATS_RESOURCES_QUEUE_NAME', Event::STATS_RESOURCES_QUEUE_NAME),
System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME),
System::getEnv('_APP_WEBHOOK_QUEUE_NAME', Event::WEBHOOK_QUEUE_NAME),
System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME),
System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME),
System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME),
System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME)
]), 'The name of the queue')
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('response')
@ -993,18 +993,18 @@ App::get('/v1/health/queue/failed/:name')
/** @var Event $queue */
$queue = match ($name) {
Event::DATABASE_QUEUE_NAME => $queueForDatabase,
Event::DELETE_QUEUE_NAME => $queueForDeletes,
Event::AUDITS_QUEUE_NAME => $queueForAudits,
Event::MAILS_QUEUE_NAME => $queueForMails,
Event::FUNCTIONS_QUEUE_NAME => $queueForFunctions,
Event::STATS_RESOURCES_QUEUE_NAME => $queueForStatsResources,
Event::STATS_USAGE_QUEUE_NAME => $queueForStatsUsage,
Event::WEBHOOK_QUEUE_NAME => $queueForWebhooks,
Event::CERTIFICATES_QUEUE_NAME => $queueForCertificates,
Event::BUILDS_QUEUE_NAME => $queueForBuilds,
Event::MESSAGING_QUEUE_NAME => $queueForMessaging,
Event::MIGRATIONS_QUEUE_NAME => $queueForMigrations,
System::getEnv('_APP_DATABASE_QUEUE_NAME', Event::DATABASE_QUEUE_NAME) => $queueForDatabase,
System::getEnv('_APP_DELETE_QUEUE_NAME', Event::DELETE_QUEUE_NAME) => $queueForDeletes,
System::getEnv('_APP_AUDITS_QUEUE_NAME', Event::AUDITS_QUEUE_NAME) => $queueForAudits,
System::getEnv('_APP_MAILS_QUEUE_NAME', Event::MAILS_QUEUE_NAME) => $queueForMails,
System::getEnv('_APP_FUNCTIONS_QUEUE_NAME', Event::FUNCTIONS_QUEUE_NAME) => $queueForFunctions,
System::getEnv('_APP_STATS_RESOURCES_QUEUE_NAME', Event::STATS_RESOURCES_QUEUE_NAME) => $queueForStatsResources,
System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME) => $queueForStatsUsage,
System::getEnv('_APP_WEBHOOK_QUEUE_NAME', Event::WEBHOOK_QUEUE_NAME) => $queueForWebhooks,
System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME) => $queueForCertificates,
System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME) => $queueForBuilds,
System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME) => $queueForMessaging,
System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME) => $queueForMigrations,
};
$failed = $queue->getSize(failed: true);

View file

@ -20,7 +20,7 @@ App::get('/v1/locale')
group: null,
name: 'get',
description: '/docs/references/locale/get-locale.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -79,7 +79,7 @@ App::get('/v1/locale/codes')
group: null,
name: 'listCodes',
description: '/docs/references/locale/list-locale-codes.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -105,7 +105,7 @@ App::get('/v1/locale/countries')
group: null,
name: 'listCountries',
description: '/docs/references/locale/list-countries.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -142,7 +142,7 @@ App::get('/v1/locale/countries/eu')
group: null,
name: 'listCountriesEU',
description: '/docs/references/locale/list-countries-eu.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -181,7 +181,7 @@ App::get('/v1/locale/countries/phones')
group: null,
name: 'listCountriesPhones',
description: '/docs/references/locale/list-countries-phones.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -219,7 +219,7 @@ App::get('/v1/locale/continents')
group: null,
name: 'listContinents',
description: '/docs/references/locale/list-continents.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -255,7 +255,7 @@ App::get('/v1/locale/currencies')
group: null,
name: 'listCurrencies',
description: '/docs/references/locale/list-currencies.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -282,7 +282,7 @@ App::get('/v1/locale/languages')
group: null,
name: 'listLanguages',
description: '/docs/references/locale/list-languages.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,

View file

@ -1145,7 +1145,8 @@ App::get('/v1/messaging/providers/:providerId/logs')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->action(function (string $providerId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
->inject('audit')
->action(function (string $providerId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
$provider = $dbForProject->getDocument('providers', $providerId);
if ($provider->isEmpty()) {
@ -1158,9 +1159,12 @@ App::get('/v1/messaging/providers/:providerId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$audit = new Audit($dbForProject);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$resource = 'provider/' . $providerId;
$logs = $audit->getLogsByResource($resource, $queries);
$logs = $audit->getLogsByResource($resource, offset: $offset, limit: $limit);
$output = [];
foreach ($logs as $i => &$log) {
@ -1207,7 +1211,7 @@ App::get('/v1/messaging/providers/:providerId/logs')
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@ -2549,7 +2553,8 @@ App::get('/v1/messaging/topics/:topicId/logs')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->action(function (string $topicId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
->inject('audit')
->action(function (string $topicId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
$topic = $dbForProject->getDocument('topics', $topicId);
if ($topic->isEmpty()) {
@ -2562,9 +2567,12 @@ App::get('/v1/messaging/topics/:topicId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$audit = new Audit($dbForProject);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$resource = 'topic/' . $topicId;
$logs = $audit->getLogsByResource($resource, $queries);
$logs = $audit->getLogsByResource($resource, offset: $offset, limit: $limit);
$output = [];
@ -2612,7 +2620,7 @@ App::get('/v1/messaging/topics/:topicId/logs')
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@ -2966,7 +2974,8 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->action(function (string $subscriberId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
->inject('audit')
->action(function (string $subscriberId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
$subscriber = $dbForProject->getDocument('subscribers', $subscriberId);
if ($subscriber->isEmpty()) {
@ -2979,9 +2988,12 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$audit = new Audit($dbForProject);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$resource = 'subscriber/' . $subscriberId;
$logs = $audit->getLogsByResource($resource, $queries);
$logs = $audit->getLogsByResource($resource, limit: $limit, offset: $offset);
$output = [];
@ -3029,7 +3041,7 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@ -3552,7 +3564,8 @@ App::post('/v1/messaging/messages/push')
throw new Exception(Exception::STORAGE_FILE_TYPE_UNSUPPORTED);
}
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$endpoint = "$protocol://{$platform['apiHostname']}/v1";
$scheduleTime = $currentScheduledAt ?? $scheduledAt;
if (!\is_null($scheduleTime)) {
@ -3572,7 +3585,7 @@ App::post('/v1/messaging/messages/push')
$image = [
'bucketId' => $bucket->getId(),
'fileId' => $file->getId(),
'url' => "{$platform['endpoint']}/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/push?project={$project->getId()}&jwt={$jwt}",
'url' => "{$endpoint}/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/push?project={$project->getId()}&jwt={$jwt}",
];
}
@ -3761,7 +3774,8 @@ App::get('/v1/messaging/messages/:messageId/logs')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->action(function (string $messageId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
->inject('audit')
->action(function (string $messageId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
$message = $dbForProject->getDocument('messages', $messageId);
if ($message->isEmpty()) {
@ -3774,9 +3788,12 @@ App::get('/v1/messaging/messages/:messageId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$audit = new Audit($dbForProject);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$resource = 'message/' . $messageId;
$logs = $audit->getLogsByResource($resource, $queries);
$logs = $audit->getLogsByResource($resource, limit: $limit, offset: $offset);
$output = [];
@ -3824,7 +3841,7 @@ App::get('/v1/messaging/messages/:messageId/logs')
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@ -4562,10 +4579,13 @@ App::patch('/v1/messaging/messages/push/:messageId')
'projectId' => $project->getId(),
]);
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$endpoint = "$protocol://{$platform['apiHostname']}/v1";
$pushData['image'] = [
'bucketId' => $bucket->getId(),
'fileId' => $file->getId(),
'url' => "{$platform['endpoint']}/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/push?project={$project->getId()}&jwt={$jwt}",
'url' => "{$endpoint}/storage/buckets/{$bucket->getId()}/files/{$file->getId()}/push?project={$project->getId()}&jwt={$jwt}",
];
}

View file

@ -69,10 +69,11 @@ App::post('/v1/migrations/appwrite')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('platform')
->inject('user')
->inject('queueForEvents')
->inject('queueForMigrations')
->action(function (array $resources, string $endpoint, string $projectId, string $apiKey, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
->action(function (array $resources, string $endpoint, string $projectId, string $apiKey, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
$migration = $dbForProject->createDocument('migrations', new Document([
'$id' => ID::unique(),
'status' => 'pending',
@ -96,6 +97,7 @@ App::post('/v1/migrations/appwrite')
$queueForMigrations
->setMigration($migration)
->setProject($project)
->setPlatform($platform)
->setUser($user)
->trigger();
@ -128,10 +130,11 @@ App::post('/v1/migrations/firebase')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('platform')
->inject('user')
->inject('queueForEvents')
->inject('queueForMigrations')
->action(function (array $resources, string $serviceAccount, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
->action(function (array $resources, string $serviceAccount, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
$serviceAccountData = json_decode($serviceAccount, true);
if (empty($serviceAccountData)) {
@ -163,6 +166,7 @@ App::post('/v1/migrations/firebase')
$queueForMigrations
->setMigration($migration)
->setProject($project)
->setPlatform($platform)
->setUser($user)
->trigger();
@ -200,10 +204,11 @@ App::post('/v1/migrations/supabase')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('platform')
->inject('user')
->inject('queueForEvents')
->inject('queueForMigrations')
->action(function (array $resources, string $endpoint, string $apiKey, string $databaseHost, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
->action(function (array $resources, string $endpoint, string $apiKey, string $databaseHost, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
$migration = $dbForProject->createDocument('migrations', new Document([
'$id' => ID::unique(),
'status' => 'pending',
@ -230,6 +235,7 @@ App::post('/v1/migrations/supabase')
$queueForMigrations
->setMigration($migration)
->setProject($project)
->setPlatform($platform)
->setUser($user)
->trigger();
@ -268,10 +274,11 @@ App::post('/v1/migrations/nhost')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('platform')
->inject('user')
->inject('queueForEvents')
->inject('queueForMigrations')
->action(function (array $resources, string $subdomain, string $region, string $adminSecret, string $database, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
->action(function (array $resources, string $subdomain, string $region, string $adminSecret, string $database, string $username, string $password, int $port, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Event $queueForEvents, Migration $queueForMigrations) {
$migration = $dbForProject->createDocument('migrations', new Document([
'$id' => ID::unique(),
'status' => 'pending',
@ -299,6 +306,7 @@ App::post('/v1/migrations/nhost')
$queueForMigrations
->setMigration($migration)
->setProject($project)
->setPlatform($platform)
->setUser($user)
->trigger();
@ -335,6 +343,7 @@ App::post('/v1/migrations/csv/imports')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('project')
->inject('platform')
->inject('deviceForFiles')
->inject('deviceForMigrations')
->inject('queueForEvents')
@ -348,6 +357,7 @@ App::post('/v1/migrations/csv/imports')
Database $dbForProject,
Database $dbForPlatform,
Document $project,
array $platform,
Device $deviceForFiles,
Device $deviceForMigrations,
Event $queueForEvents,
@ -441,6 +451,7 @@ App::post('/v1/migrations/csv/imports')
$queueForMigrations
->setMigration($migration)
->setProject($project)
->setProject($project)
->trigger();
$response
@ -481,6 +492,7 @@ App::post('/v1/migrations/csv/exports')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('project')
->inject('platform')
->inject('queueForEvents')
->inject('queueForMigrations')
->action(function (
@ -498,6 +510,7 @@ App::post('/v1/migrations/csv/exports')
Database $dbForProject,
Database $dbForPlatform,
Document $project,
array $platform,
Event $queueForEvents,
Migration $queueForMigrations
) {
@ -571,6 +584,7 @@ App::post('/v1/migrations/csv/exports')
$queueForMigrations
->setMigration($migration)
->setProject($project)
->setPlatform($platform)
->trigger();
$response
@ -903,9 +917,10 @@ App::patch('/v1/migrations/:migrationId')
->inject('response')
->inject('dbForProject')
->inject('project')
->inject('platform')
->inject('user')
->inject('queueForMigrations')
->action(function (string $migrationId, Response $response, Database $dbForProject, Document $project, Document $user, Migration $queueForMigrations) {
->action(function (string $migrationId, Response $response, Database $dbForProject, Document $project, array $platform, Document $user, Migration $queueForMigrations) {
$migration = $dbForProject->getDocument('migrations', $migrationId);
if ($migration->isEmpty()) {
@ -924,6 +939,7 @@ App::patch('/v1/migrations/:migrationId')
$queueForMigrations
->setMigration($migration)
->setProject($project)
->setPlatform($platform)
->setUser($user)
->trigger();

View file

@ -21,6 +21,7 @@ use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use PHPMailer\PHPMailer\PHPMailer;
use Utopia\App;
use Utopia\Audit\Adapter\Database as AdapterDatabase;
use Utopia\Audit\Audit;
use Utopia\Cache\Cache;
use Utopia\Config\Config;
@ -247,13 +248,15 @@ App::post('/v1/projects')
}
if ($create || $projectTables) {
$audit = new Audit($dbForProject);
$adapter = new AdapterDatabase($dbForProject);
$audit = new Audit($adapter);
$audit->setup();
}
if (!$create && $sharedTablesV1) {
$attributes = \array_map(fn ($attribute) => new Document($attribute), Audit::ATTRIBUTES);
$indexes = \array_map(fn (array $index) => new Document($index), Audit::INDEXES);
$adapter = new AdapterDatabase($dbForProject);
$attributes = $adapter->getAttributeDocuments();
$indexes = $adapter->getIndexDocuments();
$dbForProject->createDocument(Database::METADATA, new Document([
'$id' => ID::custom('audit'),
'$permissions' => [Permission::create(Role::any())],
@ -294,7 +297,7 @@ App::post('/v1/projects')
// Hook allowing instant project mirroring during migration
// Outside of migration, hook is not registered and has no effect
$hooks->trigger('afterProjectCreation', [ $project, $pools, $cache ]);
$hooks->trigger('afterProjectCreation', [$project, $pools, $cache]);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@ -1499,6 +1502,9 @@ App::post('/v1/projects/:projectId/keys')
],
'projectInternalId' => $project->getSequence(),
'projectId' => $project->getId(),
'resourceInternalId' => $project->getSequence(),
'resourceId' => $project->getId(),
'resourceType' => 'projects',
'name' => $name,
'scopes' => $scopes,
'expire' => $expire,
@ -1546,7 +1552,13 @@ App::get('/v1/projects/:projectId/keys')
}
$keys = $dbForPlatform->find('keys', [
Query::equal('projectInternalId', [$project->getSequence()]),
Query::or([
Query::equal('projectInternalId', [$project->getSequence()]),
Query::and([
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$project->getSequence()]),
])
]),
Query::limit(5000),
]);
@ -1587,7 +1599,13 @@ App::get('/v1/projects/:projectId/keys/:keyId')
$key = $dbForPlatform->findOne('keys', [
Query::equal('$id', [$keyId]),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::or([
Query::equal('projectInternalId', [$project->getSequence()]),
Query::and([
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$project->getSequence()]),
])
])
]);
if ($key->isEmpty()) {
@ -1631,7 +1649,13 @@ App::put('/v1/projects/:projectId/keys/:keyId')
$key = $dbForPlatform->findOne('keys', [
Query::equal('$id', [$keyId]),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::or([
Query::equal('projectInternalId', [$project->getSequence()]),
Query::and([
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$project->getSequence()]),
])
])
]);
if ($key->isEmpty()) {
@ -1682,7 +1706,13 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
$key = $dbForPlatform->findOne('keys', [
Query::equal('$id', [$keyId]),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::or([
Query::equal('projectInternalId', [$project->getSequence()]),
Query::and([
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$project->getSequence()]),
])
])
]);
if ($key->isEmpty()) {
@ -2079,6 +2109,7 @@ App::patch('/v1/projects/:projectId/smtp')
if ($enabled) {
$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->SMTPAuth = (!empty($username) && !empty($password));
$mail->Username = $username;
$mail->Password = $password;
$mail->Host = $host;
@ -2094,7 +2125,7 @@ App::patch('/v1/projects/:projectId/smtp')
throw new Exception('Connection is not valid.');
}
} catch (Throwable $error) {
throw new Exception(Exception::PROJECT_SMTP_CONFIG_INVALID, 'Could not connect to SMTP server: ' . $error->getMessage());
throw new Exception(Exception::PROJECT_SMTP_CONFIG_INVALID, $error->getMessage());
}
}
@ -2665,7 +2696,7 @@ App::patch('/v1/projects/:projectId/auth/session-invalidation')
$auths = $project->getAttribute('auths', []);
$auths['invalidateSessions'] = $enabled;
$dbForPlatform->updateDocument('projects', $project->getId(), $project
->setAttribute('auths', $auths));
->setAttribute('auths', $auths));
$response->dynamic($project, Response::MODEL_PROJECT);
});

File diff suppressed because it is too large Load diff

View file

@ -72,7 +72,7 @@ App::post('/v1/teams')
group: 'teams',
name: 'create',
description: '/docs/references/teams/create-team.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -163,7 +163,7 @@ App::get('/v1/teams')
group: 'teams',
name: 'list',
description: '/docs/references/teams/list-teams.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -237,7 +237,7 @@ App::get('/v1/teams/:teamId')
group: 'teams',
name: 'get',
description: '/docs/references/teams/get-team.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -268,7 +268,7 @@ App::get('/v1/teams/:teamId/prefs')
group: 'teams',
name: 'getPrefs',
description: '/docs/references/teams/get-team-prefs.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -310,7 +310,7 @@ App::put('/v1/teams/:teamId')
group: 'teams',
name: 'updateName',
description: '/docs/references/teams/update-team-name.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -356,7 +356,7 @@ App::put('/v1/teams/:teamId/prefs')
group: 'teams',
name: 'updatePrefs',
description: '/docs/references/teams/update-team-prefs.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -403,7 +403,7 @@ App::delete('/v1/teams/:teamId')
group: 'teams',
name: 'delete',
description: '/docs/references/teams/delete-team.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
@ -462,7 +462,7 @@ App::post('/v1/teams/:teamId/memberships')
group: 'memberships',
name: 'createMembership',
description: '/docs/references/teams/create-team-membership.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -848,7 +848,7 @@ App::get('/v1/teams/:teamId/memberships')
group: 'memberships',
name: 'listMemberships',
description: '/docs/references/teams/list-team-members.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -991,7 +991,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
group: 'memberships',
name: 'getMembership',
description: '/docs/references/teams/get-team-member.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -1078,7 +1078,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
group: 'memberships',
name: 'updateMembership',
description: '/docs/references/teams/update-team-membership.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -1188,7 +1188,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
group: 'memberships',
name: 'updateMembershipStatus',
description: '/docs/references/teams/update-team-membership-status.md',
auth: [AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -1353,7 +1353,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
group: 'memberships',
name: 'deleteMembership',
description: '/docs/references/teams/delete-team-membership.md',
auth: [AuthType::SESSION, AuthType::KEY, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
@ -1464,7 +1464,8 @@ App::get('/v1/teams/:teamId/logs')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->action(function (string $teamId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
->inject('audit')
->action(function (string $teamId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
$team = $dbForProject->getDocument('teams', $teamId);
@ -1478,9 +1479,12 @@ App::get('/v1/teams/:teamId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$audit = new Audit($dbForProject);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$resource = 'team/' . $team->getId();
$logs = $audit->getLogsByResource($resource, $queries);
$logs = $audit->getLogsByResource($resource, offset: $offset, limit: $limit);
$output = [];
@ -1527,7 +1531,7 @@ App::get('/v1/teams/:teamId/logs')
}
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});

View file

@ -238,7 +238,7 @@ App::post('/v1/users')
group: 'users',
name: 'create',
description: '/docs/references/users/create-user.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -275,7 +275,7 @@ App::post('/v1/users/bcrypt')
group: 'users',
name: 'createBcryptUser',
description: '/docs/references/users/create-bcrypt-user.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -313,7 +313,7 @@ App::post('/v1/users/md5')
group: 'users',
name: 'createMD5User',
description: '/docs/references/users/create-md5-user.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -350,7 +350,7 @@ App::post('/v1/users/argon2')
group: 'users',
name: 'createArgon2User',
description: '/docs/references/users/create-argon2-user.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -387,7 +387,7 @@ App::post('/v1/users/sha')
group: 'users',
name: 'createSHAUser',
description: '/docs/references/users/create-sha-user.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -428,7 +428,7 @@ App::post('/v1/users/phpass')
group: 'users',
name: 'createPHPassUser',
description: '/docs/references/users/create-phpass-user.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -465,7 +465,7 @@ App::post('/v1/users/scrypt')
group: 'users',
name: 'createScryptUser',
description: '/docs/references/users/create-scrypt-user.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -513,7 +513,7 @@ App::post('/v1/users/scrypt-modified')
group: 'users',
name: 'createScryptModifiedUser',
description: '/docs/references/users/create-scrypt-modified-user.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -650,7 +650,7 @@ App::get('/v1/users')
group: 'users',
name: 'list',
description: '/docs/references/users/list-users.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -729,7 +729,7 @@ App::get('/v1/users/:userId')
group: 'users',
name: 'get',
description: '/docs/references/users/get-user.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -760,7 +760,7 @@ App::get('/v1/users/:userId/prefs')
group: 'users',
name: 'getPrefs',
description: '/docs/references/users/get-user-prefs.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -831,7 +831,7 @@ App::get('/v1/users/:userId/sessions')
group: 'sessions',
name: 'listSessions',
description: '/docs/references/users/list-user-sessions.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -875,7 +875,7 @@ App::get('/v1/users/:userId/memberships')
group: 'memberships',
name: 'listMemberships',
description: '/docs/references/users/list-user-memberships.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -930,7 +930,7 @@ App::get('/v1/users/:userId/logs')
group: 'logs',
name: 'listLogs',
description: '/docs/references/users/list-user-logs.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -945,7 +945,8 @@ App::get('/v1/users/:userId/logs')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->action(function (string $userId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
->inject('audit')
->action(function (string $userId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
$user = $dbForProject->getDocument('users', $userId);
@ -958,8 +959,11 @@ App::get('/v1/users/:userId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$audit = new Audit($dbForProject);
$logs = $audit->getLogsByUser($user->getSequence(), $queries);
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$logs = $audit->getLogsByUser($user->getSequence(), limit: $limit, offset: $offset);
$output = [];
foreach ($logs as $i => &$log) {
$log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
@ -999,7 +1003,7 @@ App::get('/v1/users/:userId/logs')
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByUser($user->getSequence(), $queries) : 0,
'total' => $includeTotal ? $audit->countLogsByUser($user->getSequence()) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@ -1078,7 +1082,7 @@ App::get('/v1/users/identities')
group: 'identities',
name: 'listIdentities',
description: '/docs/references/users/list-identities.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -1148,7 +1152,7 @@ App::patch('/v1/users/:userId/status')
group: 'users',
name: 'updateStatus',
description: '/docs/references/users/update-user-status.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -1189,7 +1193,7 @@ App::put('/v1/users/:userId/labels')
group: 'users',
name: 'updateLabels',
description: '/docs/references/users/update-user-labels.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -1232,7 +1236,7 @@ App::patch('/v1/users/:userId/verification/phone')
group: 'users',
name: 'updatePhoneVerification',
description: '/docs/references/users/update-user-phone-verification.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -1274,7 +1278,7 @@ App::patch('/v1/users/:userId/name')
group: 'users',
name: 'updateName',
description: '/docs/references/users/update-user-name.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -1317,7 +1321,7 @@ App::patch('/v1/users/:userId/password')
group: 'users',
name: 'updatePassword',
description: '/docs/references/users/update-user-password.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -1416,7 +1420,7 @@ App::patch('/v1/users/:userId/email')
group: 'users',
name: 'updateEmail',
description: '/docs/references/users/update-user-email.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -1527,7 +1531,7 @@ App::patch('/v1/users/:userId/phone')
group: 'users',
name: 'updatePhone',
description: '/docs/references/users/update-user-phone.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -1617,7 +1621,7 @@ App::patch('/v1/users/:userId/verification')
group: 'users',
name: 'updateEmailVerification',
description: '/docs/references/users/update-user-email-verification.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -1655,7 +1659,7 @@ App::patch('/v1/users/:userId/prefs')
group: 'users',
name: 'updatePrefs',
description: '/docs/references/users/update-user-prefs.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -1802,7 +1806,7 @@ App::patch('/v1/users/:userId/mfa')
group: 'users',
name: 'updateMfa',
description: '/docs/references/users/update-user-mfa.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -1820,7 +1824,7 @@ App::patch('/v1/users/:userId/mfa')
group: 'users',
name: 'updateMFA',
description: '/docs/references/users/update-user-mfa.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -1862,7 +1866,7 @@ App::get('/v1/users/:userId/mfa/factors')
group: 'mfa',
name: 'listMfaFactors',
description: '/docs/references/users/list-mfa-factors.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -1880,7 +1884,7 @@ App::get('/v1/users/:userId/mfa/factors')
group: 'mfa',
name: 'listMFAFactors',
description: '/docs/references/users/list-mfa-factors.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -1921,7 +1925,7 @@ App::get('/v1/users/:userId/mfa/recovery-codes')
group: 'mfa',
name: 'getMfaRecoveryCodes',
description: '/docs/references/users/get-mfa-recovery-codes.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -1939,7 +1943,7 @@ App::get('/v1/users/:userId/mfa/recovery-codes')
group: 'mfa',
name: 'getMFARecoveryCodes',
description: '/docs/references/users/get-mfa-recovery-codes.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -1986,7 +1990,7 @@ App::patch('/v1/users/:userId/mfa/recovery-codes')
group: 'mfa',
name: 'createMfaRecoveryCodes',
description: '/docs/references/users/create-mfa-recovery-codes.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -2004,7 +2008,7 @@ App::patch('/v1/users/:userId/mfa/recovery-codes')
group: 'mfa',
name: 'createMFARecoveryCodes',
description: '/docs/references/users/create-mfa-recovery-codes.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -2058,7 +2062,7 @@ App::put('/v1/users/:userId/mfa/recovery-codes')
group: 'mfa',
name: 'updateMfaRecoveryCodes',
description: '/docs/references/users/update-mfa-recovery-codes.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -2076,7 +2080,7 @@ App::put('/v1/users/:userId/mfa/recovery-codes')
group: 'mfa',
name: 'updateMFARecoveryCodes',
description: '/docs/references/users/update-mfa-recovery-codes.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@ -2130,7 +2134,7 @@ App::delete('/v1/users/:userId/mfa/authenticators/:type')
group: 'mfa',
name: 'deleteMfaAuthenticator',
description: '/docs/references/users/delete-mfa-authenticator.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
@ -2149,7 +2153,7 @@ App::delete('/v1/users/:userId/mfa/authenticators/:type')
group: 'mfa',
name: 'deleteMFAAuthenticator',
description: '/docs/references/users/delete-mfa-authenticator.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
@ -2198,7 +2202,7 @@ App::post('/v1/users/:userId/sessions')
group: 'sessions',
name: 'createSession',
description: '/docs/references/users/create-session.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -2290,7 +2294,7 @@ App::post('/v1/users/:userId/tokens')
group: 'sessions',
name: 'createToken',
description: '/docs/references/users/create-token.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@ -2355,7 +2359,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId')
group: 'sessions',
name: 'deleteSession',
description: '/docs/references/users/delete-user-session.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
@ -2406,7 +2410,7 @@ App::delete('/v1/users/:userId/sessions')
group: 'sessions',
name: 'deleteSessions',
description: '/docs/references/users/delete-user-sessions.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
@ -2456,7 +2460,7 @@ App::delete('/v1/users/:userId')
group: 'users',
name: 'delete',
description: '/docs/references/users/delete.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
@ -2566,7 +2570,7 @@ App::delete('/v1/users/identities/:identityId')
group: 'identities',
name: 'deleteIdentity',
description: '/docs/references/users/delete-identity.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
@ -2606,7 +2610,7 @@ App::post('/v1/users/:userId/jwts')
group: 'sessions',
name: 'createJWT',
description: '/docs/references/users/create-user-jwt.md',
auth: [AuthType::KEY],
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,

View file

@ -458,10 +458,13 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$vars[$var->getAttribute('key')] = $var->getAttribute('value', '');
}
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$endpoint = "$protocol://{$platform['apiHostname']}/v1";
// Appwrite vars
if ($type === 'function') {
$vars = \array_merge($vars, [
'APPWRITE_FUNCTION_API_ENDPOINT' => $platform['endpoint'],
'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint,
'APPWRITE_FUNCTION_ID' => $resource->getId(),
'APPWRITE_FUNCTION_NAME' => $resource->getAttribute('name'),
'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(),
@ -473,7 +476,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
]);
} elseif ($type === 'site') {
$vars = \array_merge($vars, [
'APPWRITE_SITE_API_ENDPOINT' => $platform['endpoint'],
'APPWRITE_SITE_API_ENDPOINT' => $endpoint,
'APPWRITE_SITE_ID' => $resource->getId(),
'APPWRITE_SITE_NAME' => $resource->getAttribute('name'),
'APPWRITE_SITE_DEPLOYMENT' => $deployment->getId(),
@ -1045,18 +1048,15 @@ App::init()
if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) {
$cache[$domain->get()] = false;
Config::setParam('hostnames', $cache);
Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.');
return;
}
if (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
Console::warning('Skipping SSL certificates generation on ACME challenge.');
return;
}
// 3. Check if domain is a main domain
if (!in_array($domain->get(), $platformHostnames)) {
Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.');
return;
}

View file

@ -117,6 +117,28 @@ App::get('/v1/mock/tests/general/oauth2/user')
'id' => 1,
'name' => 'User Name',
'email' => 'useroauth@localhost.test',
'verified' => true,
]);
});
App::get('/v1/mock/tests/general/oauth2/user-unverified')
->desc('OAuth2 User Unverified')
->groups(['mock'])
->label('scope', 'public')
->label('docs', false)
->param('token', '', new Text(100), 'OAuth2 Access Token.')
->inject('response')
->action(function (string $token, Response $response) {
if ($token != '123456') {
throw new Exception(Exception::GENERAL_MOCK, 'Invalid token');
}
$response->json([
'id' => 2,
'name' => 'User Name Unverified',
'email' => 'useroauthunverified@localhost.test',
'verified' => false,
]);
});

View file

@ -628,7 +628,6 @@ App::init()
$queueForFunctions->setPlatform($platform);
$queueForBuilds->setPlatform($platform);
$queueForMails->setPlatform($platform);
$queueForMigrations->setPlatform($platform);
// Clone the queues, to prevent events triggered by the database listener
// from overwriting the events that are supposed to be triggered in the shutdown hook.

View file

@ -1,43 +0,0 @@
<?php
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\App;
App::init()
->groups(['web'])
->inject('request')
->inject('response')
->action(function (Request $request, Response $response) {
$response
->addHeader('X-Frame-Options', 'SAMEORIGIN') // Avoid console and homepage from showing in iframes
->addHeader('X-XSS-Protection', '1; mode=block; report=/v1/xss?url=' . \urlencode($request->getURI()))
->addHeader('X-UA-Compatible', 'IE=Edge') // Deny IE browsers from going into quirks mode
;
});
App::get('/')
->alias('auth/*')
->alias('/invite')
->alias('/login')
->alias('/mfa')
->alias('/card/*')
->alias('/recover')
->alias('/register/*')
->groups(['web'])
->label('permission', 'public')
->label('scope', 'home')
->inject('request')
->inject('response')
->action(function (Request $request, Response $response) {
$url = parse_url($request->getURI());
$target = "/console{$url['path']}";
$params = $request->getParams();
if (!empty($params)) {
$target .= "?" . \http_build_query($params);
}
if ($url['fragment'] ?? false) {
$target .= "#{$url['fragment']}";
}
$response->redirect($target);
});

View file

@ -12,6 +12,8 @@ use Swoole\Process;
use Swoole\Table;
use Swoole\Timer;
use Utopia\App;
use Utopia\Audit\Adapter\Database as AdapterDatabase;
use Utopia\Audit\Adapter\SQL as AuditAdapterSQL;
use Utopia\Audit\Audit;
use Utopia\CLI\Console;
use Utopia\Compression\Compression;
@ -260,8 +262,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
// create appwrite database, `dbForPlatform` is a direct access call.
createDatabase($app, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections) {
if ($dbForPlatform->getCollection(Audit::COLLECTION)->isEmpty()) {
$audit = new Audit($dbForPlatform);
if ($dbForPlatform->getCollection(AuditAdapterSQL::COLLECTION)->isEmpty()) {
$adapter = new AdapterDatabase($dbForPlatform);
$audit = new Audit($adapter);
$audit->setup();
}
@ -389,8 +392,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
Console::success('[Setup] - Skip: metadata table already exists');
}
if ($dbForProject->getCollection(Audit::COLLECTION)->isEmpty()) {
$audit = new Audit($dbForProject);
if ($dbForProject->getCollection(AuditAdapterSQL::COLLECTION)->isEmpty()) {
$adapter = new AdapterDatabase($dbForProject);
$audit = new Audit($adapter);
$audit->setup();
}

View file

@ -60,6 +60,7 @@ const APP_DATABASE_TIMEOUT_MILLISECONDS_API = 15 * 1000; // 15 seconds
const APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER = 300 * 1000; // 5 minutes
const APP_DATABASE_TIMEOUT_MILLISECONDS_TASK = 300 * 1000; // 5 minutes
const APP_DATABASE_QUERY_MAX_VALUES = 500;
const APP_DATABASE_QUERY_MAX_VALUES_WORKER = 5000;
const APP_DATABASE_ENCRYPT_SIZE_MIN = 150;
const APP_DATABASE_TXN_TTL_MIN = 60; // 1 minute
const APP_DATABASE_TXN_TTL_MAX = 3600; // 1 hour
@ -89,9 +90,9 @@ const APP_SOCIAL_YOUTUBE = 'https://www.youtube.com/c/appwrite?sub_confirmation=
const APP_COMPUTE_CPUS_DEFAULT = 0.5;
const APP_COMPUTE_MEMORY_DEFAULT = 512;
const APP_COMPUTE_SPECIFICATION_DEFAULT = Specification::S_1VCPU_512MB;
const APP_PLATFORM_SERVER = 'server';
const APP_PLATFORM_CLIENT = 'client';
const APP_PLATFORM_CONSOLE = 'console';
const APP_SDK_PLATFORM_SERVER = 'server';
const APP_SDK_PLATFORM_CLIENT = 'client';
const APP_SDK_PLATFORM_CONSOLE = 'console';
const APP_VCS_GITHUB_USERNAME = 'Appwrite';
const APP_VCS_GITHUB_EMAIL = 'team@appwrite.io';
const APP_VCS_GITHUB_URL = 'https://github.com/TeamAppwrite';
@ -208,6 +209,12 @@ const DELETE_TYPE_SESSION_TARGETS = 'session_targets';
const DELETE_TYPE_CSV_EXPORTS = 'csv_exports';
const DELETE_TYPE_MAINTENANCE = 'maintenance';
// Rule statuses
const RULE_STATUS_CREATED = 'created'; // This is also the status when domain DNS verification fails.
const RULE_STATUS_CERTIFICATE_GENERATING = 'verifying';
const RULE_STATUS_CERTIFICATE_GENERATION_FAILED = 'unverified';
const RULE_STATUS_VERIFIED = 'verified';
// Message types
const MESSAGE_SEND_TYPE_INTERNAL = 'internal';
const MESSAGE_SEND_TYPE_EXTERNAL = 'external';

View file

@ -136,7 +136,13 @@ Database::addFilter(
function (mixed $value, Document $document, Database $database) {
return $database
->find('keys', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::or([
Query::equal('projectInternalId', [$document->getSequence()]),
Query::and([
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$document->getSequence()]),
])
]),
Query::limit(APP_LIMIT_SUBQUERY),
]);
}

View file

@ -4,7 +4,7 @@ use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Auth\Key;
use Appwrite\Databases\TransactionState;
use Appwrite\Event\Audit;
use Appwrite\Event\Audit as AuditEvent;
use Appwrite\Event\Build;
use Appwrite\Event\Certificate;
use Appwrite\Event\Database as EventDatabase;
@ -30,6 +30,8 @@ use Appwrite\Utopia\Response;
use Executor\Executor;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\App;
use Utopia\Audit\Adapter\Database as AdapterDatabase;
use Utopia\Audit\Audit;
use Utopia\Auth\Hashes\Argon2;
use Utopia\Auth\Hashes\Sha;
use Utopia\Auth\Proofs\Code;
@ -146,7 +148,7 @@ App::setResource('queueForStatsUsage', function (Publisher $publisher) {
return new StatsUsage($publisher);
}, ['publisher']);
App::setResource('queueForAudits', function (Publisher $publisher) {
return new Audit($publisher);
return new AuditEvent($publisher);
}, ['publisher']);
App::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
@ -164,21 +166,9 @@ App::setResource('queueForStatsResources', function (Publisher $publisher) {
/**
* Platform configuration
*/
App::setResource('platform', function (Request $request) {
$platform = Config::getParam('platform', []);
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
$port = '';
if ($request->getPort() === '443' && $protocol !== 'https') {
$port = ':443';
}
if ($request->getPort() === '80' && $protocol !== 'http') {
$port = ':80';
}
$platform['endpoint'] = "$protocol://{$platform['apiHostname']}{$port}/v1";
return $platform;
}, ['request']);
App::setResource('platform', function () {
return Config::getParam('platform', []);
}, []);
/**
* List of allowed request hostnames for the request.
@ -287,12 +277,14 @@ App::setResource('cors', fn (array $allowedHostnames) => new Cors(
'X-Appwrite-ID',
'X-Appwrite-Timestamp',
'X-Appwrite-Session',
'X-Appwrite-Platform', // for `$platform` injection and SDK generator
// SDK generator
'X-SDK-Version',
'X-SDK-Name',
'X-SDK-Language',
'X-SDK-Platform',
'X-SDK-GraphQL',
'X-SDK-Profile',
// Caching
'Range',
'Cache-Control',
@ -652,6 +644,11 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
};
}, ['pools', 'cache']);
App::setResource('audit', function ($dbForProject) {
$adapter = new AdapterDatabase($dbForProject);
return new Audit($adapter);
}, ['dbForProject']);
App::setResource('telemetry', fn () => new NoTelemetry());
App::setResource('cache', function (Group $pools, Telemetry $telemetry) {
@ -831,7 +828,7 @@ App::setResource('passwordsDictionary', function ($register) {
App::setResource('servers', function () {
$platforms = Config::getParam('sdks');
$server = $platforms[APP_PLATFORM_SERVER];
$server = $platforms[APP_SDK_PLATFORM_SERVER];
$languages = array_map(function ($language) {
return strtolower($language['name']);

View file

@ -21,6 +21,8 @@ use Appwrite\Utopia\Database\Documents\User;
use Executor\Executor;
use Swoole\Runtime;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\Audit\Adapter\Database as AdapterDatabase;
use Utopia\Audit\Audit as UtopiaAudit;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
@ -183,7 +185,7 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
->setSharedTables(true)
->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES_WORKER);
// set tenant
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
@ -411,6 +413,13 @@ Server::setResource('logError', function (Registry $register, Document $project)
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
if ($error->getPrevious() !== null) {
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
$log->addExtra('previousMessage', $error->getPrevious()->getMessage());
}
$log->addExtra('previousFile', $error->getPrevious()->getFile());
$log->addExtra('previousLine', $error->getPrevious()->getLine());
}
foreach (($extras ?? []) as $key => $value) {
$log->addExtra($key, $value);
@ -431,11 +440,31 @@ Server::setResource('logError', function (Registry $register, Document $project)
Console::warning("Failed: {$error->getMessage()}");
Console::warning($error->getTraceAsString());
if ($error->getPrevious() !== null) {
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
Console::warning("Previous Failed: {$error->getPrevious()->getMessage()}");
}
Console::warning("Previous File: {$error->getPrevious()->getFile()} Line: {$error->getPrevious()->getLine()}");
}
};
}, ['register', 'project']);
Server::setResource('executor', fn () => new Executor());
Server::setResource('getAudit', function (Database $dbForPlatform, callable $getProjectDB) {
return function (Document $project) use ($dbForPlatform, $getProjectDB) {
if ($project->isEmpty() || $project->getId() === 'console') {
$adapter = new AdapterDatabase($dbForPlatform);
return new UtopiaAudit($adapter);
}
$dbForProject = $getProjectDB($project);
$adapter = new AdapterDatabase($dbForProject);
return new UtopiaAudit($adapter);
};
}, ['dbForPlatform', 'getProjectDB']);
$pools = $register->get('pools');
$platform = new Appwrite();
$args = $platform->getEnv('argv');

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php doctor $@
exec php /usr/src/code/app/cli.php doctor "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php install $@
exec php /usr/src/code/app/cli.php install "$@"

3
bin/interval Normal file
View file

@ -0,0 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php interval "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php maintenance $@
exec php /usr/src/code/app/cli.php maintenance "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php migrate $@
exec php /usr/src/code/app/cli.php migrate "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php queue-count --type=failed $@
exec php /usr/src/code/app/cli.php queue-count --type=failed "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php queue-count --type=processing $@
exec php /usr/src/code/app/cli.php queue-count --type=processing "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php queue-count --type=success $@
exec php /usr/src/code/app/cli.php queue-count --type=success "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php queue-retry $@
exec php /usr/src/code/app/cli.php queue-retry "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/realtime.php $@
exec php /usr/src/code/app/realtime.php "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php schedule-executions $@
exec php /usr/src/code/app/cli.php schedule-executions "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php schedule-functions $@
exec php /usr/src/code/app/cli.php schedule-functions "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php schedule-messages $@
exec php /usr/src/code/app/cli.php schedule-messages "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php screenshot $@
exec php /usr/src/code/app/cli.php screenshot "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php sdks $@
exec php /usr/src/code/app/cli.php sdks "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php specs $@
exec php /usr/src/code/app/cli.php specs "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php ssl $@
exec php /usr/src/code/app/cli.php ssl "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php stats-resources $@
exec php /usr/src/code/app/cli.php stats-resources "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
/usr/src/code/vendor/bin/phpunit --configuration /usr/src/code/phpunit.xml $@
exec /usr/src/code/vendor/bin/phpunit --configuration /usr/src/code/phpunit.xml "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php upgrade $@
exec php /usr/src/code/app/cli.php upgrade "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php vars $@
exec php /usr/src/code/app/cli.php vars "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php audits $@
exec php /usr/src/code/app/worker.php audits "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php builds $@
exec php /usr/src/code/app/worker.php builds "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php certificates $@
exec php /usr/src/code/app/worker.php certificates "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php databases $@
exec php /usr/src/code/app/worker.php databases "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php deletes $@
exec php /usr/src/code/app/worker.php deletes "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php functions $@
exec php /usr/src/code/app/worker.php functions "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php mails $@
exec php /usr/src/code/app/worker.php mails "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php messaging $@
exec php /usr/src/code/app/worker.php messaging "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php migrations $@
exec php /usr/src/code/app/worker.php migrations "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php stats-resources $@
exec php /usr/src/code/app/worker.php stats-resources "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php stats-usage $@
exec php /usr/src/code/app/worker.php stats-usage "$@"

View file

@ -1,3 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php webhooks $@
exec php /usr/src/code/app/worker.php webhooks "$@"

View file

@ -47,12 +47,12 @@
"appwrite/php-clamav": "2.0.*",
"utopia-php/abuse": "1.*",
"utopia-php/analytics": "0.10.*",
"utopia-php/audit": "1.*",
"utopia-php/audit": "2.0.2-rc1",
"utopia-php/auth": "0.5.*",
"utopia-php/cache": "0.13.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "1.*.*",
"utopia-php/database": "3.*",
"utopia-php/database": "3.*.*",
"utopia-php/detector": "0.2.*",
"utopia-php/domains": "0.9.*",
"utopia-php/emails": "0.6.*",
@ -109,4 +109,4 @@
"tbachert/spi": true
}
}
}
}

206
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "7c9cb03eb5267f1e7a3ffc037ae22b6a",
"content-hash": "b873febd2b03c32ec61a57b690cc44a2",
"packages": [
{
"name": "adhocore/jwt",
@ -2004,16 +2004,16 @@
},
{
"name": "phpseclib/phpseclib",
"version": "3.0.47",
"version": "3.0.48",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d"
"reference": "64065a5679c50acb886e82c07aa139b0f757bb89"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/9d6ca36a6c2dd434765b1071b2644a1c683b385d",
"reference": "9d6ca36a6c2dd434765b1071b2644a1c683b385d",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/64065a5679c50acb886e82c07aa139b0f757bb89",
"reference": "64065a5679c50acb886e82c07aa139b0f757bb89",
"shasum": ""
},
"require": {
@ -2094,7 +2094,7 @@
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.47"
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.48"
},
"funding": [
{
@ -2110,7 +2110,7 @@
"type": "tidelift"
}
],
"time": "2025-10-06T01:07:24+00:00"
"time": "2025-12-15T11:51:42+00:00"
},
{
"name": "psr/container",
@ -2453,20 +2453,20 @@
},
{
"name": "ramsey/uuid",
"version": "4.9.1",
"version": "4.9.2",
"source": {
"type": "git",
"url": "https://github.com/ramsey/uuid.git",
"reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440"
"reference": "8429c78ca35a09f27565311b98101e2826affde0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/81f941f6f729b1e3ceea61d9d014f8b6c6800440",
"reference": "81f941f6f729b1e3ceea61d9d014f8b6c6800440",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0",
"reference": "8429c78ca35a09f27565311b98101e2826affde0",
"shasum": ""
},
"require": {
"brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14",
"brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14",
"php": "^8.0",
"ramsey/collection": "^1.2 || ^2.0"
},
@ -2525,9 +2525,9 @@
],
"support": {
"issues": "https://github.com/ramsey/uuid/issues",
"source": "https://github.com/ramsey/uuid/tree/4.9.1"
"source": "https://github.com/ramsey/uuid/tree/4.9.2"
},
"time": "2025-09-04T20:59:21+00:00"
"time": "2025-12-14T04:43:48+00:00"
},
{
"name": "spomky-labs/otphp",
@ -2673,16 +2673,16 @@
},
{
"name": "symfony/http-client",
"version": "v7.4.1",
"version": "v7.4.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
"reference": "26cc224ea7103dda90e9694d9e139a389092d007"
"reference": "d01dfac1e0dc99f18da48b18101c23ce57929616"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-client/zipball/26cc224ea7103dda90e9694d9e139a389092d007",
"reference": "26cc224ea7103dda90e9694d9e139a389092d007",
"url": "https://api.github.com/repos/symfony/http-client/zipball/d01dfac1e0dc99f18da48b18101c23ce57929616",
"reference": "d01dfac1e0dc99f18da48b18101c23ce57929616",
"shasum": ""
},
"require": {
@ -2750,7 +2750,7 @@
"http"
],
"support": {
"source": "https://github.com/symfony/http-client/tree/v7.4.1"
"source": "https://github.com/symfony/http-client/tree/v7.4.3"
},
"funding": [
{
@ -2770,7 +2770,7 @@
"type": "tidelift"
}
],
"time": "2025-12-04T21:12:57+00:00"
"time": "2025-12-23T14:50:43+00:00"
},
{
"name": "symfony/http-client-contracts",
@ -3552,21 +3552,23 @@
},
{
"name": "utopia-php/audit",
"version": "1.0.2",
"version": "2.0.2-rc1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/audit.git",
"reference": "8c17065c2473d4ca799f65585ca74eb53e1be211"
"reference": "7b35dab40bce66bda56eeeacd2bbcbf1e823f05f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/8c17065c2473d4ca799f65585ca74eb53e1be211",
"reference": "8c17065c2473d4ca799f65585ca74eb53e1be211",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/7b35dab40bce66bda56eeeacd2bbcbf1e823f05f",
"reference": "7b35dab40bce66bda56eeeacd2bbcbf1e823f05f",
"shasum": ""
},
"require": {
"php": ">=8.0",
"utopia-php/database": "*"
"utopia-php/database": "3.*",
"utopia-php/fetch": "^0.4.2",
"utopia-php/validators": "^0.1.0"
},
"require-dev": {
"laravel/pint": "1.*",
@ -3593,9 +3595,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/audit/issues",
"source": "https://github.com/utopia-php/audit/tree/1.0.2"
"source": "https://github.com/utopia-php/audit/tree/2.0.2-rc1"
},
"time": "2025-10-20T07:14:26+00:00"
"time": "2025-12-24T01:20:43+00:00"
},
{
"name": "utopia-php/auth",
@ -3654,16 +3656,16 @@
},
{
"name": "utopia-php/cache",
"version": "0.13.1",
"version": "0.13.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/cache.git",
"reference": "97220cb3b3822b166ee016d1646e2ae2815dc540"
"reference": "5768498c9f451482f0bf3eede4d6452ddcd4a0f6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/cache/zipball/97220cb3b3822b166ee016d1646e2ae2815dc540",
"reference": "97220cb3b3822b166ee016d1646e2ae2815dc540",
"url": "https://api.github.com/repos/utopia-php/cache/zipball/5768498c9f451482f0bf3eede4d6452ddcd4a0f6",
"reference": "5768498c9f451482f0bf3eede4d6452ddcd4a0f6",
"shasum": ""
},
"require": {
@ -3672,7 +3674,7 @@
"ext-redis": "*",
"php": ">=8.0",
"utopia-php/pools": "0.8.*",
"utopia-php/telemetry": "0.1.*"
"utopia-php/telemetry": "*"
},
"require-dev": {
"laravel/pint": "1.2.*",
@ -3700,9 +3702,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/cache/issues",
"source": "https://github.com/utopia-php/cache/tree/0.13.1"
"source": "https://github.com/utopia-php/cache/tree/0.13.2"
},
"time": "2025-05-09T14:43:52+00:00"
"time": "2025-12-17T08:55:43+00:00"
},
{
"name": "utopia-php/cli",
@ -3896,16 +3898,16 @@
},
{
"name": "utopia-php/database",
"version": "3.5.0",
"version": "3.6.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "5da71b65a6123ce2e78795522b05b7458aabfbd7"
"reference": "c8c1b2f5770245dd4006e2680681e3efbe8b1fa7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/5da71b65a6123ce2e78795522b05b7458aabfbd7",
"reference": "5da71b65a6123ce2e78795522b05b7458aabfbd7",
"url": "https://api.github.com/repos/utopia-php/database/zipball/c8c1b2f5770245dd4006e2680681e3efbe8b1fa7",
"reference": "c8c1b2f5770245dd4006e2680681e3efbe8b1fa7",
"shasum": ""
},
"require": {
@ -3948,9 +3950,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/3.5.0"
"source": "https://github.com/utopia-php/database/tree/3.6.1"
},
"time": "2025-11-18T08:11:01+00:00"
"time": "2025-12-16T09:55:41+00:00"
},
{
"name": "utopia-php/detector",
@ -3999,23 +4001,23 @@
},
{
"name": "utopia-php/dns",
"version": "1.4.0",
"version": "1.4.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/dns.git",
"reference": "dce3453364a4524b7250db8d8eb74820b814409e"
"reference": "5daf8b683dad877491c4df84c6be24850b2f363b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/dns/zipball/dce3453364a4524b7250db8d8eb74820b814409e",
"reference": "dce3453364a4524b7250db8d8eb74820b814409e",
"url": "https://api.github.com/repos/utopia-php/dns/zipball/5daf8b683dad877491c4df84c6be24850b2f363b",
"reference": "5daf8b683dad877491c4df84c6be24850b2f363b",
"shasum": ""
},
"require": {
"php": ">=8.3",
"utopia-php/console": "0.0.*",
"utopia-php/domains": "0.9.*",
"utopia-php/telemetry": "0.1.*",
"utopia-php/telemetry": "*",
"utopia-php/validators": "0.*"
},
"require-dev": {
@ -4050,9 +4052,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/dns/issues",
"source": "https://github.com/utopia-php/dns/tree/1.4.0"
"source": "https://github.com/utopia-php/dns/tree/1.4.1"
},
"time": "2025-12-05T10:09:00+00:00"
"time": "2025-12-17T09:09:08+00:00"
},
{
"name": "utopia-php/domains",
@ -4264,16 +4266,16 @@
},
{
"name": "utopia-php/framework",
"version": "0.33.34",
"version": "0.33.35",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/http.git",
"reference": "76def92594c32504ec80eaacdb60ff8fad73c856"
"reference": "82b139fb04f30045db51b0d322224f222da32313"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/http/zipball/76def92594c32504ec80eaacdb60ff8fad73c856",
"reference": "76def92594c32504ec80eaacdb60ff8fad73c856",
"url": "https://api.github.com/repos/utopia-php/http/zipball/82b139fb04f30045db51b0d322224f222da32313",
"reference": "82b139fb04f30045db51b0d322224f222da32313",
"shasum": ""
},
"require": {
@ -4306,9 +4308,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/http/issues",
"source": "https://github.com/utopia-php/http/tree/0.33.34"
"source": "https://github.com/utopia-php/http/tree/0.33.35"
},
"time": "2025-12-08T07:55:31+00:00"
"time": "2025-12-12T08:33:52+00:00"
},
{
"name": "utopia-php/image",
@ -4730,21 +4732,21 @@
},
{
"name": "utopia-php/pools",
"version": "0.8.2",
"version": "0.8.3",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/pools.git",
"reference": "05c67aba42eb68ac65489cc1e7fc5db83db2dd4d"
"reference": "ad7d6ba946376e81c603204285ce9a674b6502b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/pools/zipball/05c67aba42eb68ac65489cc1e7fc5db83db2dd4d",
"reference": "05c67aba42eb68ac65489cc1e7fc5db83db2dd4d",
"url": "https://api.github.com/repos/utopia-php/pools/zipball/ad7d6ba946376e81c603204285ce9a674b6502b8",
"reference": "ad7d6ba946376e81c603204285ce9a674b6502b8",
"shasum": ""
},
"require": {
"php": ">=8.3",
"utopia-php/telemetry": "0.1.*"
"php": ">=8.4",
"utopia-php/telemetry": "*"
},
"require-dev": {
"laravel/pint": "1.*",
@ -4776,9 +4778,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/pools/issues",
"source": "https://github.com/utopia-php/pools/tree/0.8.2"
"source": "https://github.com/utopia-php/pools/tree/0.8.3"
},
"time": "2025-04-17T02:04:54+00:00"
"time": "2025-12-17T09:35:18+00:00"
},
{
"name": "utopia-php/preloader",
@ -4835,16 +4837,16 @@
},
{
"name": "utopia-php/queue",
"version": "0.11.1",
"version": "0.11.2",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/queue.git",
"reference": "498bbbef418b1db71b51e1bb62f5d1d752ddd8d6"
"reference": "a854f7c4abc18e0eca55fc5608cd7088d71eb19f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/queue/zipball/498bbbef418b1db71b51e1bb62f5d1d752ddd8d6",
"reference": "498bbbef418b1db71b51e1bb62f5d1d752ddd8d6",
"url": "https://api.github.com/repos/utopia-php/queue/zipball/a854f7c4abc18e0eca55fc5608cd7088d71eb19f",
"reference": "a854f7c4abc18e0eca55fc5608cd7088d71eb19f",
"shasum": ""
},
"require": {
@ -4854,7 +4856,7 @@
"utopia-php/fetch": "0.4.*",
"utopia-php/framework": "0.33.*",
"utopia-php/pools": "0.8.*",
"utopia-php/telemetry": "0.1.*"
"utopia-php/telemetry": "*"
},
"require-dev": {
"ext-redis": "*",
@ -4895,9 +4897,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/queue/issues",
"source": "https://github.com/utopia-php/queue/tree/0.11.1"
"source": "https://github.com/utopia-php/queue/tree/0.11.2"
},
"time": "2025-05-30T11:50:34+00:00"
"time": "2025-12-17T09:32:35+00:00"
},
{
"name": "utopia-php/registry",
@ -4953,16 +4955,16 @@
},
{
"name": "utopia-php/storage",
"version": "0.18.16",
"version": "0.18.18",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/storage.git",
"reference": "0c7b8ad68de8e1eb23ccc8af9f27a30eb832930f"
"reference": "acaea524f315f87b8811a2c34450fe2b502f49d8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/0c7b8ad68de8e1eb23ccc8af9f27a30eb832930f",
"reference": "0c7b8ad68de8e1eb23ccc8af9f27a30eb832930f",
"url": "https://api.github.com/repos/utopia-php/storage/zipball/acaea524f315f87b8811a2c34450fe2b502f49d8",
"reference": "acaea524f315f87b8811a2c34450fe2b502f49d8",
"shasum": ""
},
"require": {
@ -5005,28 +5007,28 @@
],
"support": {
"issues": "https://github.com/utopia-php/storage/issues",
"source": "https://github.com/utopia-php/storage/tree/0.18.16"
"source": "https://github.com/utopia-php/storage/tree/0.18.18"
},
"time": "2025-12-03T02:15:45+00:00"
"time": "2025-12-17T07:33:45+00:00"
},
{
"name": "utopia-php/swoole",
"version": "0.8.4",
"version": "0.8.5",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/swoole.git",
"reference": "150c30700e738c52348cce9ed0e0f0ff96872081"
"reference": "e42b6b8e44c457a7b35d8a857d7af1d67d667c58"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/swoole/zipball/150c30700e738c52348cce9ed0e0f0ff96872081",
"reference": "150c30700e738c52348cce9ed0e0f0ff96872081",
"url": "https://api.github.com/repos/utopia-php/swoole/zipball/e42b6b8e44c457a7b35d8a857d7af1d67d667c58",
"reference": "e42b6b8e44c457a7b35d8a857d7af1d67d667c58",
"shasum": ""
},
"require": {
"ext-swoole": "*",
"php": ">=8.0",
"utopia-php/framework": "0.33.*"
"utopia-php/framework": "0.33.35"
},
"require-dev": {
"laravel/pint": "1.2.*",
@ -5056,9 +5058,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/swoole/issues",
"source": "https://github.com/utopia-php/swoole/tree/0.8.4"
"source": "https://github.com/utopia-php/swoole/tree/0.8.5"
},
"time": "2025-09-07T09:39:46+00:00"
"time": "2025-12-15T14:03:23+00:00"
},
{
"name": "utopia-php/system",
@ -5436,16 +5438,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "1.5.9",
"version": "1.8.6",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "ee434aa00a9185380b9a39bb46bf86d7104d3a93"
"reference": "b6cc29d3bd247e193f3c06b4168dc69d884645f0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/ee434aa00a9185380b9a39bb46bf86d7104d3a93",
"reference": "ee434aa00a9185380b9a39bb46bf86d7104d3a93",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/b6cc29d3bd247e193f3c06b4168dc69d884645f0",
"reference": "b6cc29d3bd247e193f3c06b4168dc69d884645f0",
"shasum": ""
},
"require": {
@ -5481,9 +5483,9 @@
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
"support": {
"issues": "https://github.com/appwrite/sdk-generator/issues",
"source": "https://github.com/appwrite/sdk-generator/tree/1.5.9"
"source": "https://github.com/appwrite/sdk-generator/tree/1.8.6"
},
"time": "2025-11-25T05:22:25+00:00"
"time": "2025-12-31T10:22:17+00:00"
},
{
"name": "doctrine/annotations",
@ -7931,16 +7933,16 @@
},
{
"name": "symfony/console",
"version": "v8.0.1",
"version": "v8.0.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "fcb73f69d655b48fcb894a262f074218df08bd58"
"reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/fcb73f69d655b48fcb894a262f074218df08bd58",
"reference": "fcb73f69d655b48fcb894a262f074218df08bd58",
"url": "https://api.github.com/repos/symfony/console/zipball/6145b304a5c1ea0bdbd0b04d297a5864f9a7d587",
"reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587",
"shasum": ""
},
"require": {
@ -7997,7 +7999,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v8.0.1"
"source": "https://github.com/symfony/console/tree/v8.0.3"
},
"funding": [
{
@ -8017,7 +8019,7 @@
"type": "tidelift"
}
],
"time": "2025-12-05T15:25:33+00:00"
"time": "2025-12-23T14:52:06+00:00"
},
{
"name": "symfony/filesystem",
@ -8091,16 +8093,16 @@
},
{
"name": "symfony/finder",
"version": "v8.0.0",
"version": "v8.0.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "7598dd5770580fa3517ec83e8da0c9b9e01f4291"
"reference": "dd3a2953570a283a2ba4e17063bb98c734cf5b12"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/7598dd5770580fa3517ec83e8da0c9b9e01f4291",
"reference": "7598dd5770580fa3517ec83e8da0c9b9e01f4291",
"url": "https://api.github.com/repos/symfony/finder/zipball/dd3a2953570a283a2ba4e17063bb98c734cf5b12",
"reference": "dd3a2953570a283a2ba4e17063bb98c734cf5b12",
"shasum": ""
},
"require": {
@ -8135,7 +8137,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/finder/tree/v8.0.0"
"source": "https://github.com/symfony/finder/tree/v8.0.3"
},
"funding": [
{
@ -8155,7 +8157,7 @@
"type": "tidelift"
}
],
"time": "2025-11-05T14:36:47+00:00"
"time": "2025-12-23T14:52:06+00:00"
},
{
"name": "symfony/options-resolver",
@ -8943,7 +8945,9 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {
"utopia-php/audit": 5
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
@ -8967,5 +8971,5 @@
"platform-overrides": {
"php": "8.3"
},
"plugin-api-version": "2.6.0"
"plugin-api-version": "2.9.0"
}

View file

@ -63,6 +63,8 @@ services:
- 9501:80
networks:
- appwrite
dns:
- 172.16.238.100
labels:
- "traefik.enable=true"
- "traefik.constraint-label-stack=appwrite"
@ -99,6 +101,7 @@ services:
depends_on:
- mariadb
- redis
- coredns
# - clamav
entrypoint:
- php
@ -220,6 +223,7 @@ services:
- _APP_DATABASE_SHARED_NAMESPACE
- _APP_FUNCTIONS_CREATION_ABUSE_LIMIT
- _APP_CUSTOM_DOMAIN_DENY_LIST
- _APP_TRUSTED_HEADERS
extra_hosts:
- "host.docker.internal:host-gateway"
@ -551,6 +555,7 @@ services:
- _APP_DOMAIN_TARGET_CAA
- _APP_DNS
- _APP_DOMAIN_FUNCTIONS
- _APP_DOMAIN_SITES
- _APP_EMAIL_CERTIFICATES
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@ -736,6 +741,7 @@ services:
- _APP_MIGRATIONS_FIREBASE_CLIENT_ID
- _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
- _APP_DATABASE_SHARED_TABLES
- _APP_OPTIONS_FORCE_HTTPS
appwrite-task-maintenance:
entrypoint: maintenance
@ -748,6 +754,7 @@ services:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- mariadb
- redis
environment:
- _APP_ENV
@ -780,6 +787,43 @@ services:
- _APP_MAINTENANCE_START_TIME
- _APP_DATABASE_SHARED_TABLES
appwrite-task-interval:
entrypoint: interval
<<: *x-logging
container_name: appwrite-task-interval
image: appwrite-dev
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- mariadb
- redis
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
- _APP_DOMAIN_TARGET_A
- _APP_DOMAIN_TARGET_CAA
- _APP_DNS
- _APP_DOMAIN_FUNCTIONS
- _APP_DOMAIN_SITES
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_DATABASE_SHARED_TABLES
- _APP_INTERVAL_DOMAIN_VERIFICATION
appwrite-task-stats-resources:
container_name: appwrite-task-stats-resources
entrypoint: stats-resources
@ -1023,27 +1067,6 @@ services:
- OPR_EXECUTOR_STORAGE_WASABI_REGION=$_APP_STORAGE_WASABI_REGION
- OPR_EXECUTOR_STORAGE_WASABI_BUCKET=$_APP_STORAGE_WASABI_BUCKET
openruntimes-proxy:
container_name: openruntimes-proxy
hostname: proxy
<<: *x-logging
stop_signal: SIGINT
image: openruntimes/proxy:0.5.5
networks:
- appwrite
- runtimes
environment:
- OPR_PROXY_WORKER_PER_CORE=$_APP_WORKER_PER_CORE
- OPR_PROXY_ENV=$_APP_ENV
- OPR_PROXY_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET
- OPR_PROXY_SECRET=$_APP_EXECUTOR_SECRET
- OPR_PROXY_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
- OPR_PROXY_ALGORITHM=random
- OPR_PROXY_EXECUTORS=exc1
- OPR_PROXY_HEALTHCHECK_INTERVAL=10000
- OPR_PROXY_MAX_TIMEOUT=600
- OPR_PROXY_HEALTHCHECK=enabled
mariadb:
image: mariadb:10.11 # fix issues when upgrading using: mysql_upgrade -u root -p
container_name: appwrite-mariadb
@ -1078,6 +1101,21 @@ services:
volumes:
- appwrite-redis:/data:rw
coredns: # DNS server for testing purposes (Proxy APIs)
image: coredns/coredns:1.12.4
container_name: appwrite-coredns
restart: unless-stopped
<<: *x-logging
command: ["-conf", "/mnt/resources/Corefile"]
# If you need to debug CoreDNS, do it from "appwrite container", or port forward:
# ports:
# - "53:53"
networks:
appwrite:
ipv4_address: 172.16.238.100
volumes:
- ./tests/resources/coredns:/mnt/resources:ro
# Dev Tools Start ------------------------------------------------------------------------------------------
#
# The Appwrite Team uses the following tools to help debug, monitor and diagnose the Appwrite stack
@ -1111,6 +1149,9 @@ services:
- "traefik.http.routers.appwrite_maildev_https.rule=Host(`mail.localhost`)"
- "traefik.http.routers.appwrite_maildev_https.service=appwrite_maildev"
- "traefik.http.routers.appwrite_maildev_https.tls=true"
environment:
- MAILDEV_INCOMING_USER=${_APP_SMTP_USERNAME}
- MAILDEV_INCOMING_PASS=${_APP_SMTP_PASSWORD}
request-catcher-webhook: # used mainly for dev tests (mock HTTP webhook)
image: appwrite/requestcatcher:1.0.0
@ -1208,6 +1249,9 @@ networks:
name: gateway
appwrite:
name: appwrite
ipam:
config:
- subnet: 172.16.238.0/24
runtimes:
name: runtimes

View file

@ -14,7 +14,13 @@ databases.updateDocument(
"<DATABASE_ID>", // databaseId
"<COLLECTION_ID>", // collectionId
"<DOCUMENT_ID>", // documentId
Map.of("a", "b"), // data (optional)
Map.of(
"username", "walter.obrien",
"email", "walter.obrien@example.com",
"fullName", "Walter O'Brien",
"age", 33,
"isAdmin", false
), // data (optional)
List.of(Permission.read(Role.any())), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {

View file

@ -14,7 +14,13 @@ databases.upsertDocument(
"<DATABASE_ID>", // databaseId
"<COLLECTION_ID>", // collectionId
"<DOCUMENT_ID>", // documentId
Map.of("a", "b"), // data
Map.of(
"username", "walter.obrien",
"email", "walter.obrien@example.com",
"fullName", "Walter O'Brien",
"age", 30,
"isAdmin", false
), // data (optional)
List.of(Permission.read(Role.any())), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {

View file

@ -14,7 +14,13 @@ tablesDB.updateRow(
"<DATABASE_ID>", // databaseId
"<TABLE_ID>", // tableId
"<ROW_ID>", // rowId
Map.of("a", "b"), // data (optional)
Map.of(
"username", "walter.obrien",
"email", "walter.obrien@example.com",
"fullName", "Walter O'Brien",
"age", 33,
"isAdmin", false
), // data (optional)
List.of(Permission.read(Role.any())), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {

View file

@ -14,7 +14,13 @@ tablesDB.upsertRow(
"<DATABASE_ID>", // databaseId
"<TABLE_ID>", // tableId
"<ROW_ID>", // rowId
Map.of("a", "b"), // data (optional)
Map.of(
"username", "walter.obrien",
"email", "walter.obrien@example.com",
"fullName", "Walter O'Brien",
"age", 33,
"isAdmin", false
), // data (optional)
List.of(Permission.read(Role.any())), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {

View file

@ -14,7 +14,13 @@ val result = databases.updateDocument(
databaseId = "<DATABASE_ID>",
collectionId = "<COLLECTION_ID>",
documentId = "<DOCUMENT_ID>",
data = mapOf( "a" to "b" ), // (optional)
data = mapOf(
"username" to "walter.obrien",
"email" to "walter.obrien@example.com",
"fullName" to "Walter O'Brien",
"age" to 33,
"isAdmin" to false
), // (optional)
permissions = listOf(Permission.read(Role.any())), // (optional)
transactionId = "<TRANSACTION_ID>", // (optional)
)

View file

@ -14,7 +14,13 @@ val result = databases.upsertDocument(
databaseId = "<DATABASE_ID>",
collectionId = "<COLLECTION_ID>",
documentId = "<DOCUMENT_ID>",
data = mapOf( "a" to "b" ),
data = mapOf(
"username" to "walter.obrien",
"email" to "walter.obrien@example.com",
"fullName" to "Walter O'Brien",
"age" to 30,
"isAdmin" to false
), // (optional)
permissions = listOf(Permission.read(Role.any())), // (optional)
transactionId = "<TRANSACTION_ID>", // (optional)
)

View file

@ -14,7 +14,13 @@ val result = tablesDB.updateRow(
databaseId = "<DATABASE_ID>",
tableId = "<TABLE_ID>",
rowId = "<ROW_ID>",
data = mapOf( "a" to "b" ), // (optional)
data = mapOf(
"username" to "walter.obrien",
"email" to "walter.obrien@example.com",
"fullName" to "Walter O'Brien",
"age" to 33,
"isAdmin" to false
), // (optional)
permissions = listOf(Permission.read(Role.any())), // (optional)
transactionId = "<TRANSACTION_ID>", // (optional)
)

View file

@ -14,7 +14,13 @@ val result = tablesDB.upsertRow(
databaseId = "<DATABASE_ID>",
tableId = "<TABLE_ID>",
rowId = "<ROW_ID>",
data = mapOf( "a" to "b" ), // (optional)
data = mapOf(
"username" to "walter.obrien",
"email" to "walter.obrien@example.com",
"fullName" to "Walter O'Brien",
"age" to 33,
"isAdmin" to false
), // (optional)
permissions = listOf(Permission.read(Role.any())), // (optional)
transactionId = "<TRANSACTION_ID>", // (optional)
)

View file

@ -10,7 +10,13 @@ let document = try await databases.updateDocument(
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
documentId: "<DOCUMENT_ID>",
data: [:], // optional
data: [
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
], // optional
permissions: [Permission.read(Role.any())], // optional
transactionId: "<TRANSACTION_ID>" // optional
)

View file

@ -10,7 +10,13 @@ let document = try await databases.upsertDocument(
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
documentId: "<DOCUMENT_ID>",
data: [:],
data: [
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 30,
"isAdmin": false
], // optional
permissions: [Permission.read(Role.any())], // optional
transactionId: "<TRANSACTION_ID>" // optional
)

View file

@ -10,7 +10,13 @@ let row = try await tablesDB.updateRow(
databaseId: "<DATABASE_ID>",
tableId: "<TABLE_ID>",
rowId: "<ROW_ID>",
data: [:], // optional
data: [
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
], // optional
permissions: [Permission.read(Role.any())], // optional
transactionId: "<TRANSACTION_ID>" // optional
)

View file

@ -10,7 +10,13 @@ let row = try await tablesDB.upsertRow(
databaseId: "<DATABASE_ID>",
tableId: "<TABLE_ID>",
rowId: "<ROW_ID>",
data: [:], // optional
data: [
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
], // optional
permissions: [Permission.read(Role.any())], // optional
transactionId: "<TRANSACTION_ID>" // optional
)

View file

@ -12,7 +12,13 @@ Document result = await databases.updateDocument(
databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>',
documentId: '<DOCUMENT_ID>',
data: {}, // optional
data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>', // optional
);

View file

@ -12,7 +12,13 @@ Document result = await databases.upsertDocument(
databaseId: '<DATABASE_ID>',
collectionId: '<COLLECTION_ID>',
documentId: '<DOCUMENT_ID>',
data: {},
data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 30,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>', // optional
);

View file

@ -12,7 +12,13 @@ Row result = await tablesDB.updateRow(
databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>',
rowId: '<ROW_ID>',
data: {}, // optional
data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>', // optional
);

View file

@ -12,7 +12,13 @@ Row result = await tablesDB.upsertRow(
databaseId: '<DATABASE_ID>',
tableId: '<TABLE_ID>',
rowId: '<ROW_ID>',
data: {}, // optional
data: {
"username": "walter.obrien",
"email": "walter.obrien@example.com",
"fullName": "Walter O'Brien",
"age": 33,
"isAdmin": false
}, // optional
permissions: [Permission.read(Role.any())], // optional
transactionId: '<TRANSACTION_ID>', // optional
);

View file

@ -3,7 +3,7 @@ mutation {
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
documentId: "<DOCUMENT_ID>",
data: "{}",
data: "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}",
permissions: ["read("any")"],
transactionId: "<TRANSACTION_ID>"
) {

View file

@ -3,7 +3,7 @@ mutation {
databaseId: "<DATABASE_ID>",
collectionId: "<COLLECTION_ID>",
documentId: "<DOCUMENT_ID>",
data: "{}",
data: "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":30,\"isAdmin\":false}",
permissions: ["read("any")"],
transactionId: "<TRANSACTION_ID>"
) {

View file

@ -3,7 +3,7 @@ mutation {
databaseId: "<DATABASE_ID>",
tableId: "<TABLE_ID>",
rowId: "<ROW_ID>",
data: "{}",
data: "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}",
permissions: ["read("any")"],
transactionId: "<TRANSACTION_ID>"
) {

View file

@ -3,7 +3,7 @@ mutation {
databaseId: "<DATABASE_ID>",
tableId: "<TABLE_ID>",
rowId: "<ROW_ID>",
data: "{}",
data: "{\"username\":\"walter.obrien\",\"email\":\"walter.obrien@example.com\",\"fullName\":\"Walter O'Brien\",\"age\":33,\"isAdmin\":false}",
permissions: ["read("any")"],
transactionId: "<TRANSACTION_ID>"
) {

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