Compare commits

..
Author SHA1 Message Date
fogelito fa3c9a5b76 Remove comment 2026-01-12 16:24:42 +02:00
fogelito c388fea5d0 fix Filters 2026-01-12 16:19:48 +02:00
fogelito a407d2ea84 formatting 2026-01-12 15:42:01 +02:00
fogelito da56ef39f8 remove static 2026-01-12 15:39:36 +02:00
fogelito 275b9a1186 coderabbit comments 2026-01-12 15:35:19 +02:00
fogelito 2f514ae55e Fix message 2026-01-12 15:10:52 +02:00
fogelito 38517b8004 Fix message 2026-01-12 15:02:30 +02:00
fogelito 8758e2e5a0 Run 2026-01-12 12:43:53 +02:00
fogelito dacc6a97d0 disbaled internals 2026-01-12 08:28:50 +02:00
fogelito 13fc2222f8 Update message 2026-01-11 16:37:05 +02:00
fogelito 900d8f248e allowedAttributes 2026-01-11 16:22:19 +02:00
fogelito de572c3aa9 composer 2026-01-11 15:06:55 +02:00
fogelito d4fd5e79d9 lock 2026-01-11 14:54:41 +02:00
fogelito 87247826e0 lock 2026-01-11 14:54:25 +02:00
fogelito 05f4d05cf3 lock 2026-01-11 14:42:46 +02:00
fogelito d12d6acd62 revert 2026-01-11 14:42:16 +02:00
fogelito 133eac66e7 upgrade fetch 2026-01-11 14:35:32 +02:00
fogelito 53a6fef69e lock file 2026-01-11 14:15:34 +02:00
fogelito 0c4f540325 Merge branch 'main' of https://github.com/appwrite/appwrite into joins2 2026-01-11 14:02:57 +02:00
Steven NguyenandGitHub f0191202a0 Merge pull request #10342 from tejas-raskar/doc-10339-fix-inconsistent-dir-structure-in-contribution-docs
docs: update the directory structure in CONTRIBUTING.md
2026-01-02 15:59:24 -08:00
fogelito 19e22e1da7 Types 2025-12-25 18:25:14 +02:00
fogelito 23c1e4fb79 remove var dump 2025-12-25 13:10:30 +02:00
fogelito 904b2da8ce Remove dbg 2025-12-25 12:02:54 +02:00
fogelito ee129e28f1 Remove dbg 2025-12-25 12:02:02 +02:00
fogelito c50ba30f1b fomratting 2025-12-25 12:00:33 +02:00
fogelito 265ccdb03a TablesDB tests 2025-12-25 11:03:19 +02:00
fogelito 3b93e925d4 database test 2025-12-25 10:56:58 +02:00
fogelito 10b38c7c6d enums 2025-12-23 16:42:37 +02:00
fogelito adf6b02462 create document fix reference 2025-12-23 12:18:15 +02:00
fogelito f47c484d8d csv queries 2025-12-23 09:35:28 +02:00
fogelito 6e1d2008f8 Fix filter V17 2025-12-22 15:44:10 +02:00
fogelito 38fb9f2814 select query 2025-12-22 12:47:49 +02:00
fogelito 8d37e843e0 Query select 2025-12-22 12:25:14 +02:00
fogelito 975510c6a0 select queries 2025-12-21 17:42:57 +02:00
fogelito a971976587 format 2025-12-21 17:01:17 +02:00
fogelito 9bee2f5a25 Types validator 2025-12-21 16:26:55 +02:00
fogelito 21e830146b composer.lock 2025-12-21 10:47:32 +02:00
Steven NguyenandGitHub 44ec8de6bf Merge pull request #10946 from appwrite/1.8.x
Sync 1.8.x into main
2025-12-18 17:06:43 -08:00
Tejas Raskar 80ad740d36 docs: update the directory structure in CONTRIBUTING.md 2025-08-20 14:34:43 +05:30
330 changed files with 6983 additions and 8552 deletions
+2 -3
View File
@@ -69,8 +69,8 @@ _APP_STORAGE_ANTIVIRUS_PORT=3310
_APP_SMTP_HOST=maildev
_APP_SMTP_PORT=1025
_APP_SMTP_SECURE=
_APP_SMTP_USERNAME=user
_APP_SMTP_PASSWORD=password
_APP_SMTP_USERNAME=
_APP_SMTP_PASSWORD=
_APP_SMS_PROVIDER=sms://username:password@mock
_APP_SMS_FROM=+123456789
_APP_SMS_PROJECTS_DENY_LIST=
@@ -101,7 +101,6 @@ _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=
-77
View File
@@ -1,77 +0,0 @@
# 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.
## Adding new endpoints
When adding new endpoints, make sure to use modules and follow its patterns. Find instruction in [Modules AGENTS.md](src/Appwrite/Platform/AGENTS.md) file.
## 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
+50 -28
View File
@@ -222,51 +222,73 @@ Appwrite's current structure is a combination of both [Monolithic](https://en.wi
```bash
.
├── app # Main application
│ ├── assets
│ │ ├── dbip
│ │ ├── fonts
│ │ └── security
│ ├── config # Config files
│ │ ├── avatars
│ │ ├── collections
│ │ ├── locale
│ │ ├── specs
│ │ ├── storage
│ │ └── templates
│ ├── controllers # API & dashboard controllers
│ │ ├── api
│ │ ├── shared
│ │ └── web
│ ├── db # DB schemas
├── sdks # SDKs generated copies (used for generating code examples)
── tasks # Server CLI commands
├── views # HTML server-side templates
└── workers # Background workers
│ ├── init # DB schemas
│ └── database
── views # HTML server-side templates
├── general
└── install
├── bin # Server executables (tasks & workers)
├── docker # Docker related resources and configs
├── dev # Debugger config
├── docs # Docs and tutorials
│ ├── examples
│ ├── lists
│ ├── references
│ ├── sdks
│ ├── services
│ ├── specs
│ └── tutorials
├── public # Public files
│ ├── dist
│ ├── fonts
│ ├── images
│ ├── scripts
── styles
├── src # Supporting libraries (each lib has one role, common libs are released as individual projects)
│ └── Appwrite
├── Auth
├── Detector
├── Docker
| ├── DSN
├── Event
├── Extend
├── GraphQL
├── Messaging
├── Migration
├── Network
├── OpenSSL
├── Promises
├── Specification
├── Task
├── Template
├── URL
── Utopia
│ ├── sdk-console
── sdk-project
│ └── sdk-web
├── src # Supporting libraries (each lib has one role, common libs are released as
│ ├── Appwrite
├── Auth
├── Certificates
├── Deletes
├── Detector
├── Docker
├── Event
├── Extend
├── Functions/Validator
├── GraphQL
├── Hooks
├── Messaging
├── Migration
├── Network
├── OpenSSL
├── Platform
── Promises
│ │ ├── PubSub
│ │ ├── SDK
│ │ ├── Task/Validator
│ │ ├── Template
│ │ ├── Transformation
│ │ ├── URL
│ │ ├── Utopia
│ │ └── Vcs
│ └── Executor
└── tests # End to end & unit tests
├── benchmarks
├── e2e
├── extensions
├── resources
└── unit
```
-1
View File
@@ -57,7 +57,6 @@ 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 && \
+18 -18
View File
@@ -41,8 +41,6 @@ Config::setParam('runtimes', (new Runtimes('v5'))->getAll(supported: false));
// require controllers after overwriting runtimes
require_once __DIR__ . '/controllers/general.php';
Authorization::disable();
CLI::setResource('register', fn () => $register);
CLI::setResource('cache', function ($pools) {
@@ -60,7 +58,13 @@ CLI::setResource('pools', function (Registry $register) {
return $register->get('pools');
}, ['register']);
CLI::setResource('dbForPlatform', function ($pools, $cache) {
CLI::setResource('authorization', function () {
$authorization = new Authorization();
$authorization->disable();
return $authorization;
}, []);
CLI::setResource('dbForPlatform', function ($pools, $cache, $authorization) {
$sleep = 3;
$maxAttempts = 5;
$attempts = 0;
@@ -74,6 +78,7 @@ CLI::setResource('dbForPlatform', function ($pools, $cache) {
$dbForPlatform = new Database($adapter, $cache);
$dbForPlatform
->setAuthorization($authorization)
->setNamespace('_console')
->setMetadata('host', \gethostname())
->setMetadata('project', 'console');
@@ -99,7 +104,7 @@ CLI::setResource('dbForPlatform', function ($pools, $cache) {
}
return $dbForPlatform;
}, ['pools', 'cache']);
}, ['pools', 'cache', 'authorization']);
CLI::setResource('console', function () {
return new Document(Config::getParam('console'));
@@ -110,10 +115,10 @@ CLI::setResource(
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
);
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, $authorization) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) {
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
@@ -146,6 +151,7 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache);
$databases[$dsn->getHost()] = $database;
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
@@ -162,17 +168,18 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
}
$database
->setAuthorization($authorization)
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId());
return $database;
};
}, ['pools', 'dbForPlatform', 'cache']);
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
CLI::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $database) {
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int)$project->getSequence());
return $database;
@@ -182,6 +189,7 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database = new Database($adapter, $cache);
$database
->setAuthorization($authorization)
->setSharedTables(true)
->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_TASK)
@@ -194,7 +202,7 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
return $database;
};
}, ['pools', 'cache']);
}, ['pools', 'cache', 'authorization']);
CLI::setResource('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
@@ -257,14 +265,6 @@ CLI::setResource('logError', function (Registry $register) {
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
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());
}
$log->setAction($action);
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
+1 -69
View File
@@ -6,7 +6,7 @@ use Utopia\Database\Helpers\ID;
$providers = Config::getParam('oAuthProviders', []);
$platformCollections = [
return [
'projects' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('projects'),
@@ -643,39 +643,6 @@ $platformCollections = [
'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,
@@ -751,13 +718,6 @@ $platformCollections = [
'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,
@@ -1943,31 +1903,3 @@ $platformCollections = [
'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;
-2
View File
@@ -116,7 +116,6 @@ 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',
@@ -396,7 +395,6 @@ return [
[
'key' => 'kotlin',
'name' => 'Kotlin',
'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',
+4 -4
View File
@@ -20,7 +20,7 @@ return [
'name' => 'Console',
'subtitle' => '',
'description' => '',
'controller' => '', // Uses modules
'controller' => 'web/console.php',
'sdk' => false,
'docs' => false,
'docsUrl' => '',
@@ -146,7 +146,7 @@ return [
'name' => 'Storage',
'subtitle' => 'The Storage service allows you to manage your project files.',
'description' => '/docs/services/storage.md',
'controller' => '',
'controller' => 'api/storage.php',
'sdk' => true,
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/client/storage',
@@ -270,9 +270,9 @@ return [
'console' => [
'key' => 'console',
'name' => 'Console',
'subtitle' => 'The Console service allows you to interact with console relevant information.',
'subtitle' => 'The Console service allows you to interact with console relevant informations.',
'description' => '',
'controller' => '', // Uses modules
'controller' => 'api/console.php',
'sdk' => true,
'docs' => true,
'docsUrl' => '',
+51 -69
View File
@@ -407,8 +407,8 @@
"cookies": false,
"type": "",
"demo": "account\/create-jwt.md",
"rate-limit": 120,
"rate-time": 60,
"rate-limit": 100,
"rate-time": 3600,
"rate-key": "url:{url},userId:{userId}",
"scope": "account",
"platforms": [
@@ -429,23 +429,7 @@
"Session": [],
"JWT": []
}
],
"requestBody": {
"content": {
"application\/json": {
"schema": {
"type": "object",
"properties": {
"duration": {
"type": "integer",
"description": "Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.",
"x-example": 0
}
}
}
}
}
}
]
}
},
"\/account\/logs": {
@@ -551,7 +535,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 307,
"weight": 306,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@@ -623,7 +607,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 309,
"weight": 308,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@@ -747,7 +731,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 310,
"weight": 309,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@@ -887,7 +871,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 311,
"weight": 310,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@@ -1011,7 +995,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 315,
"weight": 314,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@@ -1145,7 +1129,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 316,
"weight": 315,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@@ -1283,7 +1267,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 308,
"weight": 307,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@@ -1384,7 +1368,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 314,
"weight": 313,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@@ -1483,7 +1467,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 312,
"weight": 311,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@@ -1582,7 +1566,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 313,
"weight": 312,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@@ -2574,8 +2558,7 @@
"yandex",
"zoho",
"zoom",
"mock",
"mock-unverified"
"mock"
],
"x-enum-name": "OAuthProvider",
"x-enum-keys": []
@@ -3505,8 +3488,7 @@
"yandex",
"zoho",
"zoom",
"mock",
"mock-unverified"
"mock"
],
"x-enum-name": "OAuthProvider",
"x-enum-keys": []
@@ -5843,7 +5825,7 @@
"avif",
"gif"
],
"x-enum-name": "ImageFormat",
"x-enum-name": null,
"x-enum-keys": [],
"default": ""
},
@@ -5876,7 +5858,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 381,
"weight": 380,
"cookies": false,
"type": "",
"demo": "databases\/list-transactions.md",
@@ -5943,7 +5925,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 377,
"weight": 376,
"cookies": false,
"type": "",
"demo": "databases\/create-transaction.md",
@@ -6013,7 +5995,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 378,
"weight": 377,
"cookies": false,
"type": "",
"demo": "databases\/get-transaction.md",
@@ -6077,7 +6059,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 379,
"weight": 378,
"cookies": false,
"type": "",
"demo": "databases\/update-transaction.md",
@@ -6155,7 +6137,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 380,
"weight": 379,
"cookies": false,
"type": "",
"demo": "databases\/delete-transaction.md",
@@ -6221,7 +6203,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 382,
"weight": 381,
"cookies": false,
"type": "",
"demo": "databases\/create-operations.md",
@@ -6306,7 +6288,7 @@
"x-appwrite": {
"method": "listDocuments",
"group": "documents",
"weight": 340,
"weight": 339,
"cookies": false,
"type": "",
"demo": "databases\/list-documents.md",
@@ -6418,7 +6400,7 @@
"x-appwrite": {
"method": "createDocument",
"group": "documents",
"weight": 332,
"weight": 331,
"cookies": false,
"type": "",
"demo": "databases\/create-document.md",
@@ -6579,7 +6561,7 @@
"x-appwrite": {
"method": "getDocument",
"group": "documents",
"weight": 333,
"weight": 332,
"cookies": false,
"type": "",
"demo": "databases\/get-document.md",
@@ -6690,7 +6672,7 @@
"x-appwrite": {
"method": "upsertDocument",
"group": "documents",
"weight": 336,
"weight": 335,
"cookies": false,
"type": "",
"demo": "databases\/upsert-document.md",
@@ -6845,7 +6827,7 @@
"x-appwrite": {
"method": "updateDocument",
"group": "documents",
"weight": 334,
"weight": 333,
"cookies": false,
"type": "",
"demo": "databases\/update-document.md",
@@ -6957,7 +6939,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 338,
"weight": 337,
"cookies": false,
"type": "",
"demo": "databases\/delete-document.md",
@@ -7064,7 +7046,7 @@
"x-appwrite": {
"method": "decrementDocumentAttribute",
"group": "documents",
"weight": 343,
"weight": 342,
"cookies": false,
"type": "",
"demo": "databases\/decrement-document-attribute.md",
@@ -7191,7 +7173,7 @@
"x-appwrite": {
"method": "incrementDocumentAttribute",
"group": "documents",
"weight": 342,
"weight": 341,
"cookies": false,
"type": "",
"demo": "databases\/increment-document-attribute.md",
@@ -7318,7 +7300,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 473,
"weight": 472,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@@ -7405,7 +7387,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 471,
"weight": 470,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@@ -7523,7 +7505,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 472,
"weight": 471,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@@ -7598,7 +7580,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 242,
"weight": 241,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@@ -7652,7 +7634,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 241,
"weight": 240,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@@ -8138,7 +8120,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 291,
"weight": 290,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@@ -8222,7 +8204,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 295,
"weight": 294,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@@ -9131,7 +9113,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 446,
"weight": 445,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-transactions.md",
@@ -9201,7 +9183,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 442,
"weight": 441,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-transaction.md",
@@ -9274,7 +9256,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 443,
"weight": 442,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-transaction.md",
@@ -9341,7 +9323,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 444,
"weight": 443,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-transaction.md",
@@ -9422,7 +9404,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 445,
"weight": 444,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-transaction.md",
@@ -9491,7 +9473,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 447,
"weight": 446,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-operations.md",
@@ -9579,7 +9561,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 438,
"weight": 437,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@@ -9690,7 +9672,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 430,
"weight": 429,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@@ -9846,7 +9828,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 431,
"weight": 430,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@@ -9956,7 +9938,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 434,
"weight": 433,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@@ -10106,7 +10088,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 432,
"weight": 431,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@@ -10217,7 +10199,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 436,
"weight": 435,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@@ -10323,7 +10305,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 441,
"weight": 440,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@@ -10449,7 +10431,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 440,
"weight": 439,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+51 -69
View File
@@ -407,8 +407,8 @@
"cookies": false,
"type": "",
"demo": "account\/create-jwt.md",
"rate-limit": 120,
"rate-time": 60,
"rate-limit": 100,
"rate-time": 3600,
"rate-key": "url:{url},userId:{userId}",
"scope": "account",
"platforms": [
@@ -429,23 +429,7 @@
"Session": [],
"JWT": []
}
],
"requestBody": {
"content": {
"application\/json": {
"schema": {
"type": "object",
"properties": {
"duration": {
"type": "integer",
"description": "Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.",
"x-example": 0
}
}
}
}
}
}
]
}
},
"\/account\/logs": {
@@ -551,7 +535,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 307,
"weight": 306,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@@ -623,7 +607,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 309,
"weight": 308,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@@ -747,7 +731,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 310,
"weight": 309,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@@ -887,7 +871,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 311,
"weight": 310,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@@ -1011,7 +995,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 315,
"weight": 314,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@@ -1145,7 +1129,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 316,
"weight": 315,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@@ -1283,7 +1267,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 308,
"weight": 307,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@@ -1384,7 +1368,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 314,
"weight": 313,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@@ -1483,7 +1467,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 312,
"weight": 311,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@@ -1582,7 +1566,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 313,
"weight": 312,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@@ -2574,8 +2558,7 @@
"yandex",
"zoho",
"zoom",
"mock",
"mock-unverified"
"mock"
],
"x-enum-name": "OAuthProvider",
"x-enum-keys": []
@@ -3505,8 +3488,7 @@
"yandex",
"zoho",
"zoom",
"mock",
"mock-unverified"
"mock"
],
"x-enum-name": "OAuthProvider",
"x-enum-keys": []
@@ -5843,7 +5825,7 @@
"avif",
"gif"
],
"x-enum-name": "ImageFormat",
"x-enum-name": null,
"x-enum-keys": [],
"default": ""
},
@@ -5876,7 +5858,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 381,
"weight": 380,
"cookies": false,
"type": "",
"demo": "databases\/list-transactions.md",
@@ -5943,7 +5925,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 377,
"weight": 376,
"cookies": false,
"type": "",
"demo": "databases\/create-transaction.md",
@@ -6013,7 +5995,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 378,
"weight": 377,
"cookies": false,
"type": "",
"demo": "databases\/get-transaction.md",
@@ -6077,7 +6059,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 379,
"weight": 378,
"cookies": false,
"type": "",
"demo": "databases\/update-transaction.md",
@@ -6155,7 +6137,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 380,
"weight": 379,
"cookies": false,
"type": "",
"demo": "databases\/delete-transaction.md",
@@ -6221,7 +6203,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 382,
"weight": 381,
"cookies": false,
"type": "",
"demo": "databases\/create-operations.md",
@@ -6306,7 +6288,7 @@
"x-appwrite": {
"method": "listDocuments",
"group": "documents",
"weight": 340,
"weight": 339,
"cookies": false,
"type": "",
"demo": "databases\/list-documents.md",
@@ -6418,7 +6400,7 @@
"x-appwrite": {
"method": "createDocument",
"group": "documents",
"weight": 332,
"weight": 331,
"cookies": false,
"type": "",
"demo": "databases\/create-document.md",
@@ -6579,7 +6561,7 @@
"x-appwrite": {
"method": "getDocument",
"group": "documents",
"weight": 333,
"weight": 332,
"cookies": false,
"type": "",
"demo": "databases\/get-document.md",
@@ -6690,7 +6672,7 @@
"x-appwrite": {
"method": "upsertDocument",
"group": "documents",
"weight": 336,
"weight": 335,
"cookies": false,
"type": "",
"demo": "databases\/upsert-document.md",
@@ -6845,7 +6827,7 @@
"x-appwrite": {
"method": "updateDocument",
"group": "documents",
"weight": 334,
"weight": 333,
"cookies": false,
"type": "",
"demo": "databases\/update-document.md",
@@ -6957,7 +6939,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 338,
"weight": 337,
"cookies": false,
"type": "",
"demo": "databases\/delete-document.md",
@@ -7064,7 +7046,7 @@
"x-appwrite": {
"method": "decrementDocumentAttribute",
"group": "documents",
"weight": 343,
"weight": 342,
"cookies": false,
"type": "",
"demo": "databases\/decrement-document-attribute.md",
@@ -7191,7 +7173,7 @@
"x-appwrite": {
"method": "incrementDocumentAttribute",
"group": "documents",
"weight": 342,
"weight": 341,
"cookies": false,
"type": "",
"demo": "databases\/increment-document-attribute.md",
@@ -7318,7 +7300,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 473,
"weight": 472,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@@ -7405,7 +7387,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 471,
"weight": 470,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@@ -7523,7 +7505,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 472,
"weight": 471,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@@ -7598,7 +7580,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 242,
"weight": 241,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@@ -7652,7 +7634,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 241,
"weight": 240,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@@ -8138,7 +8120,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 291,
"weight": 290,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@@ -8222,7 +8204,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 295,
"weight": 294,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@@ -9131,7 +9113,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 446,
"weight": 445,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-transactions.md",
@@ -9201,7 +9183,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 442,
"weight": 441,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-transaction.md",
@@ -9274,7 +9256,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 443,
"weight": 442,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-transaction.md",
@@ -9341,7 +9323,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 444,
"weight": 443,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-transaction.md",
@@ -9422,7 +9404,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 445,
"weight": 444,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-transaction.md",
@@ -9491,7 +9473,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 447,
"weight": 446,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-operations.md",
@@ -9579,7 +9561,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 438,
"weight": 437,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@@ -9690,7 +9672,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 430,
"weight": 429,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@@ -9846,7 +9828,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 431,
"weight": 430,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@@ -9956,7 +9938,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 434,
"weight": 433,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@@ -10106,7 +10088,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 432,
"weight": 431,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@@ -10217,7 +10199,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 436,
"weight": 435,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@@ -10323,7 +10305,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 441,
"weight": 440,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@@ -10449,7 +10431,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 440,
"weight": 439,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+50 -69
View File
@@ -464,8 +464,8 @@
"cookies": false,
"type": "",
"demo": "account\/create-jwt.md",
"rate-limit": 120,
"rate-time": 60,
"rate-limit": 100,
"rate-time": 3600,
"rate-key": "url:{url},userId:{userId}",
"scope": "account",
"platforms": [
@@ -486,23 +486,6 @@
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "payload",
"in": "body",
"schema": {
"type": "object",
"properties": {
"duration": {
"type": "integer",
"description": "Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.",
"default": 900,
"x-example": 0
}
}
}
}
]
}
},
@@ -608,7 +591,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 307,
"weight": 306,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@@ -683,7 +666,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 309,
"weight": 308,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@@ -807,7 +790,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 310,
"weight": 309,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@@ -948,7 +931,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 311,
"weight": 310,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@@ -1072,7 +1055,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 315,
"weight": 314,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@@ -1209,7 +1192,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 316,
"weight": 315,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@@ -1349,7 +1332,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 308,
"weight": 307,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@@ -1450,7 +1433,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 314,
"weight": 313,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@@ -1551,7 +1534,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 312,
"weight": 311,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@@ -1652,7 +1635,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 313,
"weight": 312,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@@ -2684,8 +2667,7 @@
"yandex",
"zoho",
"zoom",
"mock",
"mock-unverified"
"mock"
],
"x-enum-name": "OAuthProvider",
"x-enum-keys": [],
@@ -3639,8 +3621,7 @@
"yandex",
"zoho",
"zoom",
"mock",
"mock-unverified"
"mock"
],
"x-enum-name": "OAuthProvider",
"x-enum-keys": [],
@@ -5950,7 +5931,7 @@
"avif",
"gif"
],
"x-enum-name": "ImageFormat",
"x-enum-name": null,
"x-enum-keys": [],
"default": "",
"in": "query"
@@ -5982,7 +5963,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 381,
"weight": 380,
"cookies": false,
"type": "",
"demo": "databases\/list-transactions.md",
@@ -6049,7 +6030,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 377,
"weight": 376,
"cookies": false,
"type": "",
"demo": "databases\/create-transaction.md",
@@ -6119,7 +6100,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 378,
"weight": 377,
"cookies": false,
"type": "",
"demo": "databases\/get-transaction.md",
@@ -6182,7 +6163,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 379,
"weight": 378,
"cookies": false,
"type": "",
"demo": "databases\/update-transaction.md",
@@ -6261,7 +6242,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 380,
"weight": 379,
"cookies": false,
"type": "",
"demo": "databases\/delete-transaction.md",
@@ -6326,7 +6307,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 382,
"weight": 381,
"cookies": false,
"type": "",
"demo": "databases\/create-operations.md",
@@ -6407,7 +6388,7 @@
"x-appwrite": {
"method": "listDocuments",
"group": "documents",
"weight": 340,
"weight": 339,
"cookies": false,
"type": "",
"demo": "databases\/list-documents.md",
@@ -6511,7 +6492,7 @@
"x-appwrite": {
"method": "createDocument",
"group": "documents",
"weight": 332,
"weight": 331,
"cookies": false,
"type": "",
"demo": "databases\/create-document.md",
@@ -6670,7 +6651,7 @@
"x-appwrite": {
"method": "getDocument",
"group": "documents",
"weight": 333,
"weight": 332,
"cookies": false,
"type": "",
"demo": "databases\/get-document.md",
@@ -6773,7 +6754,7 @@
"x-appwrite": {
"method": "upsertDocument",
"group": "documents",
"weight": 336,
"weight": 335,
"cookies": false,
"type": "",
"demo": "databases\/upsert-document.md",
@@ -6924,7 +6905,7 @@
"x-appwrite": {
"method": "updateDocument",
"group": "documents",
"weight": 334,
"weight": 333,
"cookies": false,
"type": "",
"demo": "databases\/update-document.md",
@@ -7034,7 +7015,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 338,
"weight": 337,
"cookies": false,
"type": "",
"demo": "databases\/delete-document.md",
@@ -7135,7 +7116,7 @@
"x-appwrite": {
"method": "decrementDocumentAttribute",
"group": "documents",
"weight": 343,
"weight": 342,
"cookies": false,
"type": "",
"demo": "databases\/decrement-document-attribute.md",
@@ -7256,7 +7237,7 @@
"x-appwrite": {
"method": "incrementDocumentAttribute",
"group": "documents",
"weight": 342,
"weight": 341,
"cookies": false,
"type": "",
"demo": "databases\/increment-document-attribute.md",
@@ -7375,7 +7356,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 473,
"weight": 472,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@@ -7458,7 +7439,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 471,
"weight": 470,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@@ -7577,7 +7558,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 472,
"weight": 471,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@@ -7649,7 +7630,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 242,
"weight": 241,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@@ -7724,7 +7705,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 241,
"weight": 240,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@@ -8223,7 +8204,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 291,
"weight": 290,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@@ -8308,7 +8289,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 295,
"weight": 294,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@@ -9165,7 +9146,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 446,
"weight": 445,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-transactions.md",
@@ -9235,7 +9216,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 442,
"weight": 441,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-transaction.md",
@@ -9308,7 +9289,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 443,
"weight": 442,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-transaction.md",
@@ -9374,7 +9355,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 444,
"weight": 443,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-transaction.md",
@@ -9456,7 +9437,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 445,
"weight": 444,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-transaction.md",
@@ -9524,7 +9505,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 447,
"weight": 446,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-operations.md",
@@ -9608,7 +9589,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 438,
"weight": 437,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@@ -9711,7 +9692,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 430,
"weight": 429,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@@ -9865,7 +9846,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 431,
"weight": 430,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@@ -9967,7 +9948,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 434,
"weight": 433,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@@ -10113,7 +10094,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 432,
"weight": 431,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@@ -10222,7 +10203,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 436,
"weight": 435,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@@ -10322,7 +10303,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 441,
"weight": 440,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@@ -10442,7 +10423,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 440,
"weight": 439,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+50 -69
View File
@@ -464,8 +464,8 @@
"cookies": false,
"type": "",
"demo": "account\/create-jwt.md",
"rate-limit": 120,
"rate-time": 60,
"rate-limit": 100,
"rate-time": 3600,
"rate-key": "url:{url},userId:{userId}",
"scope": "account",
"platforms": [
@@ -486,23 +486,6 @@
"Session": [],
"JWT": []
}
],
"parameters": [
{
"name": "payload",
"in": "body",
"schema": {
"type": "object",
"properties": {
"duration": {
"type": "integer",
"description": "Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.",
"default": 900,
"x-example": 0
}
}
}
}
]
}
},
@@ -608,7 +591,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 307,
"weight": 306,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@@ -683,7 +666,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 309,
"weight": 308,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@@ -807,7 +790,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 310,
"weight": 309,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@@ -948,7 +931,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 311,
"weight": 310,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@@ -1072,7 +1055,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 315,
"weight": 314,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@@ -1209,7 +1192,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 316,
"weight": 315,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@@ -1349,7 +1332,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 308,
"weight": 307,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@@ -1450,7 +1433,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 314,
"weight": 313,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@@ -1551,7 +1534,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 312,
"weight": 311,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@@ -1652,7 +1635,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 313,
"weight": 312,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@@ -2684,8 +2667,7 @@
"yandex",
"zoho",
"zoom",
"mock",
"mock-unverified"
"mock"
],
"x-enum-name": "OAuthProvider",
"x-enum-keys": [],
@@ -3639,8 +3621,7 @@
"yandex",
"zoho",
"zoom",
"mock",
"mock-unverified"
"mock"
],
"x-enum-name": "OAuthProvider",
"x-enum-keys": [],
@@ -5950,7 +5931,7 @@
"avif",
"gif"
],
"x-enum-name": "ImageFormat",
"x-enum-name": null,
"x-enum-keys": [],
"default": "",
"in": "query"
@@ -5982,7 +5963,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 381,
"weight": 380,
"cookies": false,
"type": "",
"demo": "databases\/list-transactions.md",
@@ -6049,7 +6030,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 377,
"weight": 376,
"cookies": false,
"type": "",
"demo": "databases\/create-transaction.md",
@@ -6119,7 +6100,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 378,
"weight": 377,
"cookies": false,
"type": "",
"demo": "databases\/get-transaction.md",
@@ -6182,7 +6163,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 379,
"weight": 378,
"cookies": false,
"type": "",
"demo": "databases\/update-transaction.md",
@@ -6261,7 +6242,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 380,
"weight": 379,
"cookies": false,
"type": "",
"demo": "databases\/delete-transaction.md",
@@ -6326,7 +6307,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 382,
"weight": 381,
"cookies": false,
"type": "",
"demo": "databases\/create-operations.md",
@@ -6407,7 +6388,7 @@
"x-appwrite": {
"method": "listDocuments",
"group": "documents",
"weight": 340,
"weight": 339,
"cookies": false,
"type": "",
"demo": "databases\/list-documents.md",
@@ -6511,7 +6492,7 @@
"x-appwrite": {
"method": "createDocument",
"group": "documents",
"weight": 332,
"weight": 331,
"cookies": false,
"type": "",
"demo": "databases\/create-document.md",
@@ -6670,7 +6651,7 @@
"x-appwrite": {
"method": "getDocument",
"group": "documents",
"weight": 333,
"weight": 332,
"cookies": false,
"type": "",
"demo": "databases\/get-document.md",
@@ -6773,7 +6754,7 @@
"x-appwrite": {
"method": "upsertDocument",
"group": "documents",
"weight": 336,
"weight": 335,
"cookies": false,
"type": "",
"demo": "databases\/upsert-document.md",
@@ -6924,7 +6905,7 @@
"x-appwrite": {
"method": "updateDocument",
"group": "documents",
"weight": 334,
"weight": 333,
"cookies": false,
"type": "",
"demo": "databases\/update-document.md",
@@ -7034,7 +7015,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 338,
"weight": 337,
"cookies": false,
"type": "",
"demo": "databases\/delete-document.md",
@@ -7135,7 +7116,7 @@
"x-appwrite": {
"method": "decrementDocumentAttribute",
"group": "documents",
"weight": 343,
"weight": 342,
"cookies": false,
"type": "",
"demo": "databases\/decrement-document-attribute.md",
@@ -7256,7 +7237,7 @@
"x-appwrite": {
"method": "incrementDocumentAttribute",
"group": "documents",
"weight": 342,
"weight": 341,
"cookies": false,
"type": "",
"demo": "databases\/increment-document-attribute.md",
@@ -7375,7 +7356,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 473,
"weight": 472,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@@ -7458,7 +7439,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 471,
"weight": 470,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@@ -7577,7 +7558,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 472,
"weight": 471,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@@ -7649,7 +7630,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 242,
"weight": 241,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@@ -7724,7 +7705,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 241,
"weight": 240,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@@ -8223,7 +8204,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 291,
"weight": 290,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@@ -8308,7 +8289,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 295,
"weight": 294,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@@ -9165,7 +9146,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 446,
"weight": 445,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-transactions.md",
@@ -9235,7 +9216,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 442,
"weight": 441,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-transaction.md",
@@ -9308,7 +9289,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 443,
"weight": 442,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-transaction.md",
@@ -9374,7 +9355,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 444,
"weight": 443,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-transaction.md",
@@ -9456,7 +9437,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 445,
"weight": 444,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-transaction.md",
@@ -9524,7 +9505,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 447,
"weight": 446,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-operations.md",
@@ -9608,7 +9589,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 438,
"weight": 437,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@@ -9711,7 +9692,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 430,
"weight": 429,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@@ -9865,7 +9846,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 431,
"weight": 430,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@@ -9967,7 +9948,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 434,
"weight": 433,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@@ -10113,7 +10094,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 432,
"weight": 431,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@@ -10222,7 +10203,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 436,
"weight": 435,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@@ -10322,7 +10303,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 441,
"weight": 440,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@@ -10442,7 +10423,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 440,
"weight": 439,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -3,4 +3,6 @@
use Utopia\Image\Image;
use Utopia\System\System;
Image::setResourceLimit('memory', intval(System::getEnv('_APP_IMAGES_RESOURCE_LIMIT_MEMORY', 1024*1024*64)));
if (\class_exists('Imagick')) {
Image::setResourceLimit('memory', intval(System::getEnv('_APP_IMAGES_RESOURCE_LIMIT_MEMORY', 1024*1024*64)));
}
+95 -80
View File
@@ -31,13 +31,14 @@ use Appwrite\URL\URL as URLParser;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Identities;
use Appwrite\Utopia\Database\Validator\Queries\Types;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use libphonenumber\PhoneNumberUtil;
use MaxMind\Db\Reader;
use Utopia\Abuse\Abuse;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Audit\Audit as EventAudit;
use Utopia\Auth\Hashes\Sha;
use Utopia\Auth\Proofs\Code as ProofsCode;
use Utopia\Auth\Proofs\Password as ProofsPassword;
@@ -55,10 +56,7 @@ use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Queries;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\UID;
use Utopia\Emails\Email;
use Utopia\Locale\Locale;
@@ -68,7 +66,6 @@ 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;
@@ -207,10 +204,10 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, arr
}
$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) {
$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, Authorization $authorization) {
/** @var Appwrite\Utopia\Database\Documents\User $userFromRequest */
$userFromRequest = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId));
$userFromRequest = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId));
if ($userFromRequest->isEmpty()) {
throw new Exception(Exception::USER_INVALID_TOKEN);
@@ -266,7 +263,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
$detector->getDevice()
));
Authorization::setRole(Role::user($user->getId())->toString());
$authorization->addRole(Role::user($user->getId())->toString());
$session = $dbForProject->createDocument('sessions', $session
->setAttribute('$permissions', [
@@ -275,7 +272,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
Permission::delete(Role::user($user->getId())),
]));
Authorization::skip(fn () => $dbForProject->deleteDocument('tokens', $verifiedToken->getId()));
$authorization->skip(fn () => $dbForProject->deleteDocument('tokens', $verifiedToken->getId()));
$dbForProject->purgeCachedDocument('users', $user->getId());
// Magic URL + Email OTP
@@ -376,8 +373,9 @@ App::post('/v1/account')
->inject('user')
->inject('project')
->inject('dbForProject')
->inject('authorization')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Hooks $hooks) {
->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Authorization $authorization, Hooks $hooks) {
$email = \strtolower($email);
if ('console' === $project->getId()) {
@@ -469,9 +467,9 @@ App::post('/v1/account')
]);
$user->removeAttribute('$sequence');
$user = Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
$user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
try {
$target = Authorization::skip(fn () => $dbForProject->createDocument('targets', new Document([
$target = $authorization->skip(fn () => $dbForProject->createDocument('targets', new Document([
'$permissions' => [
Permission::read(Role::user($user->getId())),
Permission::update(Role::user($user->getId())),
@@ -497,9 +495,9 @@ App::post('/v1/account')
throw new Exception(Exception::USER_ALREADY_EXISTS);
}
Authorization::unsetRole(Role::guests()->toString());
Authorization::setRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::users()->toString());
$authorization->removeRole(Role::guests()->toString());
$authorization->addRole(Role::user($user->getId())->toString());
$authorization->addRole(Role::users()->toString());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -975,7 +973,8 @@ 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, array $platform, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) {
->inject('authorization')
->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, Authorization $authorization) {
$email = \strtolower($email);
$protocol = $request->getProtocol();
@@ -1020,7 +1019,7 @@ App::post('/v1/account/sessions/email')
$detector->getDevice()
));
Authorization::setRole(Role::user($user->getId())->toString());
$authorization->addRole(Role::user($user->getId())->toString());
// Re-hash if not using recommended algo
if ($user->getAttribute('hash') !== $proofForPassword->getHash()->getName()) {
@@ -1119,7 +1118,8 @@ App::post('/v1/account/sessions/anonymous')
->inject('store')
->inject('proofForPassword')
->inject('proofForToken')
->action(function (Request $request, Response $response, Locale $locale, User $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) {
->inject('authorization')
->action(function (Request $request, Response $response, Locale $locale, User $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, Authorization $authorization) {
$protocol = $request->getProtocol();
if ('console' === $project->getId()) {
@@ -1164,7 +1164,7 @@ App::post('/v1/account/sessions/anonymous')
'accessedAt' => DateTime::now(),
]);
$user->removeAttribute('$sequence');
Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
$user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
// Create session token
$duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
@@ -1190,7 +1190,7 @@ App::post('/v1/account/sessions/anonymous')
$detector->getDevice()
));
Authorization::setRole(Role::user($user->getId())->toString());
$authorization->addRole(Role::user($user->getId())->toString());
$session = $dbForProject->createDocument('sessions', $session->setAttribute('$permissions', [
Permission::read(Role::user($user->getId())),
@@ -1272,6 +1272,7 @@ App::post('/v1/account/sessions/token')
->inject('store')
->inject('proofForToken')
->inject('proofForCode')
->inject('authorization')
->action($createSession);
App::get('/v1/account/sessions/oauth2/:provider')
@@ -1468,7 +1469,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
->inject('store')
->inject('proofForPassword')
->inject('proofForToken')
->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Validator $redirectValidator, Document $devKey, User $user, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) use ($oauthDefaultSuccess) {
->inject('authorization')
->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Validator $redirectValidator, Document $devKey, User $user, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, Authorization $authorization) use ($oauthDefaultSuccess) {
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$port = $request->getPort();
$callbackBase = $protocol . '://' . $request->getHostname();
@@ -1724,7 +1726,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
]);
$user->removeAttribute('$sequence');
$userDoc = Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
$userDoc = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
$dbForProject->createDocument('targets', new Document([
'$permissions' => [
Permission::read(Role::user($user->getId())),
@@ -1742,8 +1744,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
}
}
Authorization::setRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::users()->toString());
$authorization->addRole(Role::user($user->getId())->toString());
$authorization->addRole(Role::users()->toString());
if (false === $user->getAttribute('status')) { // Account is blocked
$failureRedirect(Exception::USER_BLOCKED); // User is in status blocked
@@ -1814,7 +1816,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$dbForProject->updateDocument('users', $user->getId(), $user);
Authorization::setRole(Role::user($user->getId())->toString());
$authorization->addRole(Role::user($user->getId())->toString());
$state['success'] = URLParser::parse($state['success']);
$query = URLParser::parseQuery($state['success']['query']);
@@ -1838,7 +1840,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
'ip' => $request->getIP(),
]);
Authorization::setRole(Role::user($user->getId())->toString());
$authorization->addRole(Role::user($user->getId())->toString());
$token = $dbForProject->createDocument('tokens', $token
->setAttribute('$permissions', [
@@ -2075,7 +2077,8 @@ App::post('/v1/account/tokens/magic-url')
->inject('queueForMails')
->inject('proofForPassword')
->inject('platform')
->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, User $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword, array $platform) {
->inject('authorization')
->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword, array $platform, Authorization $authorization) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
}
@@ -2148,7 +2151,7 @@ App::post('/v1/account/tokens/magic-url')
]);
$user->removeAttribute('$sequence');
Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
$user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
}
$proofForToken = new ProofsToken(TOKEN_LENGTH_MAGIC_URL);
@@ -2168,7 +2171,7 @@ App::post('/v1/account/tokens/magic-url')
'ip' => $request->getIP(),
]);
Authorization::setRole(Role::user($user->getId())->toString());
$authorization->addRole(Role::user($user->getId())->toString());
$token = $dbForProject->createDocument('tokens', $token
->setAttribute('$permissions', [
@@ -2354,7 +2357,8 @@ App::post('/v1/account/tokens/email')
->inject('queueForMails')
->inject('proofForPassword')
->inject('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) {
->inject('authorization')
->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, Authorization $authorization) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
}
@@ -2423,9 +2427,9 @@ App::post('/v1/account/tokens/email')
]);
$user->removeAttribute('$sequence');
$user = Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
$user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
try {
$target = Authorization::skip(fn () => $dbForProject->createDocument('targets', new Document([
$target = $authorization->skip(fn () => $dbForProject->createDocument('targets', new Document([
'$permissions' => [
Permission::read(Role::user($user->getId())),
Permission::update(Role::user($user->getId())),
@@ -2463,7 +2467,7 @@ App::post('/v1/account/tokens/email')
'ip' => $request->getIP(),
]);
Authorization::setRole(Role::user($user->getId())->toString());
$authorization->addRole(Role::user($user->getId())->toString());
$token = $dbForProject->createDocument('tokens', $token
->setAttribute('$permissions', [
@@ -2659,10 +2663,11 @@ App::put('/v1/account/sessions/magic-url')
->inject('queueForMails')
->inject('store')
->inject('proofForCode')
->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForCode) use ($createSession) {
->inject('authorization')
->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForCode, $authorization) use ($createSession) {
$proofForToken = new ProofsToken(TOKEN_LENGTH_MAGIC_URL);
$proofForToken->setHash(new Sha());
$createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForToken, $proofForCode);
$createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForToken, $proofForCode, $authorization);
});
App::put('/v1/account/sessions/phone')
@@ -2708,6 +2713,7 @@ App::put('/v1/account/sessions/phone')
->inject('store')
->inject('proofForToken')
->inject('proofForCode')
->inject('authorization')
->action($createSession);
App::post('/v1/account/tokens/phone')
@@ -2751,7 +2757,8 @@ 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, array $platform, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Store $store, ProofsCode $proofForCode) {
->inject('authorization')
->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, Authorization $authorization) {
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
}
@@ -2801,9 +2808,9 @@ App::post('/v1/account/tokens/phone')
]);
$user->removeAttribute('$sequence');
Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
$user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
try {
$target = Authorization::skip(fn () => $dbForProject->createDocument('targets', new Document([
$target = $authorization->skip(fn () => $dbForProject->createDocument('targets', new Document([
'$permissions' => [
Permission::read(Role::user($user->getId())),
Permission::update(Role::user($user->getId())),
@@ -2849,7 +2856,7 @@ App::post('/v1/account/tokens/phone')
'ip' => $request->getIP(),
]);
Authorization::setRole(Role::user($user->getId())->toString());
$authorization->addRole(Role::user($user->getId())->toString());
$token = $dbForProject->createDocument('tokens', $token
->setAttribute('$permissions', [
@@ -2954,22 +2961,20 @@ App::post('/v1/account/jwts')
],
contentType: ContentType::JSON,
))
->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-limit', 100)
->label('abuse-key', 'url:{url},userId:{userId}')
->inject('response')
->inject('user')
->inject('store')
->inject('proofForToken')
->action(function (int $duration, Response $response, User $user, Store $store, ProofsToken $proofForToken) {
->action(function (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', $duration, 0);
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 0);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -3026,15 +3031,14 @@ App::get('/v1/account/logs')
],
contentType: ContentType::JSON,
))
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->param('queries', [], new Types([Query::TYPE_LIMIT, Query::TYPE_OFFSET]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('user')
->inject('locale')
->inject('geodb')
->inject('dbForProject')
->inject('audit')
->action(function (array $queries, bool $includeTotal, Response $response, Document $user, Locale $locale, Reader $geodb, Database $dbForProject, Audit $audit) {
->action(function (array $queries, bool $includeTotal, Response $response, Document $user, Locale $locale, Reader $geodb, Database $dbForProject) {
try {
$queries = Query::parseQueries($queries);
@@ -3042,10 +3046,9 @@ App::get('/v1/account/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$logs = $audit->getLogsByUser($user->getSequence(), offset: $offset, limit: $limit);
$audit = new EventAudit($dbForProject);
$logs = $audit->getLogsByUser($user->getSequence(), $queries);
$output = [];
@@ -3074,7 +3077,7 @@ App::get('/v1/account/logs')
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByUser($user->getSequence()) : 0,
'total' => $includeTotal ? $audit->countLogsByUser($user->getSequence(), $queries) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -3240,7 +3243,8 @@ App::patch('/v1/account/email')
->inject('project')
->inject('hooks')
->inject('proofForPassword')
->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, User $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, ProofsPassword $proofForPassword) {
->inject('authorization')
->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, User $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, ProofsPassword $proofForPassword, Authorization $authorization) {
// passwordUpdate will be empty if the user has never set a password
$passwordUpdate = $user->getAttribute('passwordUpdate');
@@ -3292,7 +3296,7 @@ App::patch('/v1/account/email')
->setAttribute('passwordUpdate', DateTime::now());
}
$target = Authorization::skip(fn () => $dbForProject->findOne('targets', [
$target = $authorization->skip(fn () => $dbForProject->findOne('targets', [
Query::equal('identifier', [$email]),
]));
@@ -3308,7 +3312,7 @@ App::patch('/v1/account/email')
$oldTarget = $user->find('identifier', $oldEmail, 'targets');
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email)));
$authorization->skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email)));
}
$dbForProject->purgeCachedDocument('users', $user->getId());
} catch (Duplicate) {
@@ -3349,8 +3353,9 @@ App::patch('/v1/account/phone')
->inject('queueForEvents')
->inject('project')
->inject('hooks')
->inject('proofForPassword')
->action(function (string $phone, string $password, Response $response, User $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, ProofsPassword $proofForPassword) {
->inject('proofForPassword')
->inject('authorization')
->action(function (string $phone, string $password, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, ProofsPassword $proofForPassword, Authorization $authorization) {
// passwordUpdate will be empty if the user has never set a password
$passwordUpdate = $user->getAttribute('passwordUpdate');
@@ -3365,7 +3370,7 @@ App::patch('/v1/account/phone')
$hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, false]);
$target = Authorization::skip(fn () => $dbForProject->findOne('targets', [
$target = $authorization->skip(fn () => $dbForProject->findOne('targets', [
Query::equal('identifier', [$phone]),
]));
@@ -3396,7 +3401,7 @@ App::patch('/v1/account/phone')
$oldTarget = $user->find('identifier', $oldPhone, 'targets');
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone)));
$authorization->skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone)));
}
$dbForProject->purgeCachedDocument('users', $user->getId());
} catch (Duplicate $th) {
@@ -3532,7 +3537,9 @@ App::post('/v1/account/recovery')
->inject('queueForMails')
->inject('queueForEvents')
->inject('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) {
->inject('authorization')
->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, Authorization $authorization) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
}
@@ -3568,7 +3575,7 @@ App::post('/v1/account/recovery')
'ip' => $request->getIP(),
]);
Authorization::setRole(Role::user($profile->getId())->toString());
$authorization->addRole(Role::user($profile->getId())->toString());
$recovery = $dbForProject->createDocument('tokens', $recovery
->setAttribute('$permissions', [
@@ -3724,7 +3731,8 @@ App::put('/v1/account/recovery')
->inject('hooks')
->inject('proofForPassword')
->inject('proofForToken')
->action(function (string $userId, string $secret, string $password, Response $response, User $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks, ProofsPassword $proofForPassword, ProofsToken $proofForToken) {
->inject('authorization')
->action(function (string $userId, string $secret, string $password, Response $response, User $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks, ProofsPassword $proofForPassword, ProofsToken $proofForToken, Authorization $authorization) {
/** @var Appwrite\Utopia\Database\Documents\User $profile */
$profile = $dbForProject->getDocument('users', $userId);
@@ -3738,7 +3746,7 @@ App::put('/v1/account/recovery')
throw new Exception(Exception::USER_INVALID_TOKEN);
}
Authorization::setRole(Role::user($profile->getId())->toString());
$authorization->addRole(Role::user($profile->getId())->toString());
$newPassword = $proofForPassword->hash($password);
@@ -3841,7 +3849,8 @@ App::post('/v1/account/verifications/email')
->inject('queueForEvents')
->inject('queueForMails')
->inject('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) {
->inject('authorization')
->action(function (string $url, Request $request, Response $response, Document $project, array $platform, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsToken $proofForToken, Authorization $authorization) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
@@ -3870,7 +3879,7 @@ App::post('/v1/account/verifications/email')
'ip' => $request->getIP(),
]);
Authorization::setRole(Role::user($user->getId())->toString());
$authorization->addRole(Role::user($user->getId())->toString());
$verification = $dbForProject->createDocument('tokens', $verification
->setAttribute('$permissions', [
@@ -4069,9 +4078,10 @@ App::put('/v1/account/verifications/email')
->inject('dbForProject')
->inject('queueForEvents')
->inject('proofForToken')
->action(function (string $userId, string $secret, Response $response, User $user, Database $dbForProject, Event $queueForEvents, ProofsToken $proofForToken) {
->inject('authorization')
->action(function (string $userId, string $secret, Response $response, User $user, Database $dbForProject, Event $queueForEvents, ProofsToken $proofForToken, Authorization $authorization) {
/** @var Appwrite\Utopia\Database\Documents\User $profile */
$profile = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId));
$profile = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId));
if ($profile->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
@@ -4083,7 +4093,7 @@ App::put('/v1/account/verifications/email')
throw new Exception(Exception::USER_INVALID_TOKEN);
}
Authorization::setRole(Role::user($profile->getId())->toString());
$authorization->addRole(Role::user($profile->getId())->toString());
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true));
@@ -4143,7 +4153,8 @@ App::post('/v1/account/verifications/phone')
->inject('queueForStatsUsage')
->inject('plan')
->inject('proofForCode')
->action(function (Request $request, Response $response, User $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, ProofsCode $proofForCode) {
->inject('authorization')
->action(function (Request $request, Response $response, User $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, ProofsCode $proofForCode, Authorization $authorization) {
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
}
@@ -4182,7 +4193,7 @@ App::post('/v1/account/verifications/phone')
'ip' => $request->getIP(),
]);
Authorization::setRole(Role::user($user->getId())->toString());
$authorization->addRole(Role::user($user->getId())->toString());
$verification = $dbForProject->createDocument('tokens', $verification
->setAttribute('$permissions', [
@@ -4288,9 +4299,10 @@ App::put('/v1/account/verifications/phone')
->inject('dbForProject')
->inject('queueForEvents')
->inject('proofForCode')
->action(function (string $userId, string $secret, Response $response, User $user, Database $dbForProject, Event $queueForEvents, ProofsCode $proofForCode) {
->inject('authorization')
->action(function (string $userId, string $secret, Response $response, User $user, Database $dbForProject, Event $queueForEvents, ProofsCode $proofForCode, Authorization $authorization) {
/** @var Appwrite\Utopia\Database\Documents\User $profile */
$profile = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId));
$profile = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId));
if ($profile->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
@@ -4302,7 +4314,7 @@ App::put('/v1/account/verifications/phone')
throw new Exception(Exception::USER_INVALID_TOKEN);
}
Authorization::setRole(Role::user($profile->getId())->toString());
$authorization->addRole(Role::user($profile->getId())->toString());
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('phoneVerification', true));
@@ -4355,12 +4367,13 @@ App::post('/v1/account/targets/push')
->inject('dbForProject')
->inject('store')
->inject('proofForToken')
->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, User $user, Request $request, Response $response, Database $dbForProject, Store $store, ProofsToken $proofForToken) {
->inject('authorization')
->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, User $user, Request $request, Response $response, Database $dbForProject, Store $store, ProofsToken $proofForToken, Authorization $authorization) {
$targetId = $targetId == 'unique()' ? ID::unique() : $targetId;
$provider = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId));
$provider = $authorization->skip(fn () => $dbForProject->getDocument('providers', $providerId));
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
if (!$target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
@@ -4435,9 +4448,10 @@ App::put('/v1/account/targets/:targetId/push')
->inject('request')
->inject('response')
->inject('dbForProject')
->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject) {
->inject('authorization')
->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) {
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
@@ -4500,8 +4514,9 @@ App::delete('/v1/account/targets/:targetId/push')
->inject('request')
->inject('response')
->inject('dbForProject')
->action(function (string $targetId, Event $queueForEvents, Delete $queueForDeletes, Document $user, Request $request, Response $response, Database $dbForProject) {
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
->inject('authorization')
->action(function (string $targetId, Event $queueForEvents, Delete $queueForDeletes, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) {
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
+16 -13
View File
@@ -70,9 +70,9 @@ $avatarCallback = function (string $type, string $code, int $width, int $height,
unset($image);
};
$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForPlatform, ?Logger $logger) {
$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForPlatform, Authorization $authorization, ?Logger $logger) {
try {
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
$sessions = $user->getAttribute('sessions', []);
@@ -123,7 +123,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
->setAttribute('providerRefreshToken', $refreshToken)
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry('')));
Authorization::skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession));
$authorization->skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession));
$dbForProject->purgeCachedDocument('users', $user->getId());
} catch (Throwable $err) {
@@ -131,7 +131,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
do {
$previousAccessToken = $gitHubSession->getAttribute('providerAccessToken');
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
$sessions = $user->getAttribute('sessions', []);
$gitHubSession = new Document();
@@ -841,8 +841,9 @@ App::get('/v1/cards/cloud')
->inject('contributors')
->inject('employees')
->inject('logger')
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
->inject('authorization')
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization) use ($getUserGitHub) {
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
if ($user->isEmpty() && empty($mock)) {
throw new Exception(Exception::USER_NOT_FOUND);
@@ -853,7 +854,7 @@ App::get('/v1/cards/cloud')
$email = $user->getAttribute('email', '');
$createdAt = new \DateTime($user->getCreatedAt());
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger);
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $authorization, $logger);
$githubName = $gitHub['name'] ?? '';
$githubId = $gitHub['id'] ?? '';
@@ -1048,8 +1049,9 @@ App::get('/v1/cards/cloud-back')
->inject('contributors')
->inject('employees')
->inject('logger')
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
->inject('authorization')
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization) use ($getUserGitHub) {
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
if ($user->isEmpty() && empty($mock)) {
throw new Exception(Exception::USER_NOT_FOUND);
@@ -1059,7 +1061,7 @@ App::get('/v1/cards/cloud-back')
$userId = $user->getId();
$email = $user->getAttribute('email', '');
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger);
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $authorization, $logger);
$githubId = $gitHub['id'] ?? '';
$isHero = \array_key_exists($email, $heroes);
@@ -1126,8 +1128,9 @@ App::get('/v1/cards/cloud-og')
->inject('contributors')
->inject('employees')
->inject('logger')
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
->inject('authorization')
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization) use ($getUserGitHub) {
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
if ($user->isEmpty() && empty($mock)) {
throw new Exception(Exception::USER_NOT_FOUND);
@@ -1142,7 +1145,7 @@ App::get('/v1/cards/cloud-og')
$email = $user->getAttribute('email', '');
$createdAt = new \DateTime($user->getCreatedAt());
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger);
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $authorization, $logger);
$githubName = $gitHub['name'] ?? '';
$githubId = $gitHub['id'] ?? '';
+147
View File
@@ -0,0 +1,147 @@
<?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);
});
+3 -2
View File
@@ -28,11 +28,12 @@ use Utopia\Validator\Text;
App::init()
->groups(['graphql'])
->inject('project')
->action(function (Document $project) {
->inject('authorization')
->action(function (Document $project, Authorization $authorization) {
if (
array_key_exists('graphql', $project->getAttribute('apis', []))
&& !$project->getAttribute('apis', [])['graphql']
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles()))
&& !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
}
+31 -29
View File
@@ -120,7 +120,7 @@ App::get('/v1/health/db')
$output[] = new Document([
'name' => $key . " ($database)",
'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
'ping' => \round((\microtime(true) - $checkStart) * 1000)
]);
} else {
$failures[] = $database;
@@ -131,6 +131,8 @@ App::get('/v1/health/db')
}
}
// Only throw error if ALL databases failed (no successful pings)
// This allows partial failures in environments where not all DBs are ready
if (!empty($failures)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'DB failure on: ' . implode(", ", $failures));
}
@@ -180,7 +182,7 @@ App::get('/v1/health/cache')
$output[] = new Document([
'name' => $key . " ($cache)",
'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
'ping' => \round((\microtime(true) - $checkStart) * 1000)
]);
} else {
$failures[] = $cache;
@@ -240,7 +242,7 @@ App::get('/v1/health/pubsub')
$output[] = new Document([
'name' => $key . " ($pubsub)",
'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
'ping' => \round((\microtime(true) - $checkStart) * 1000)
]);
} else {
$failures[] = $pubsub;
@@ -822,7 +824,7 @@ App::get('/v1/health/storage/local')
$output = [
'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
'ping' => \round((\microtime(true) - $checkStart) * 1000)
];
$response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS);
@@ -874,7 +876,7 @@ App::get('/v1/health/storage')
$output = [
'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
'ping' => \round((\microtime(true) - $checkStart) * 1000)
];
$response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS);
@@ -945,18 +947,18 @@ App::get('/v1/health/queue/failed/:name')
contentType: ContentType::JSON
))
->param('name', '', new WhiteList([
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)
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
]), '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 +995,18 @@ App::get('/v1/health/queue/failed/:name')
/** @var Event $queue */
$queue = match ($name) {
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,
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,
};
$failed = $queue->getSize(failed: true);
+56 -69
View File
@@ -23,6 +23,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Providers;
use Appwrite\Utopia\Database\Validator\Queries\Subscribers;
use Appwrite\Utopia\Database\Validator\Queries\Targets;
use Appwrite\Utopia\Database\Validator\Queries\Topics;
use Appwrite\Utopia\Database\Validator\Queries\Types;
use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
use Utopia\App;
@@ -36,11 +37,9 @@ use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Authorization\Input;
use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\Queries;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\Roles;
use Utopia\Database\Validator\UID;
use Utopia\Locale\Locale;
@@ -1073,8 +1072,9 @@ App::get('/v1/messaging/providers')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('dbForProject')
->inject('authorization')
->inject('response')
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
@@ -1100,7 +1100,7 @@ App::get('/v1/messaging/providers')
}
$providerId = $cursor->getValue();
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId));
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('providers', $providerId));
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Provider '{$providerId}' for the 'cursor' value not found.");
@@ -1139,14 +1139,13 @@ App::get('/v1/messaging/providers/:providerId/logs')
]
))
->param('providerId', '', new UID(), 'Provider ID.')
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->param('queries', [], new Types([Query::TYPE_LIMIT, Query::TYPE_OFFSET]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->inject('audit')
->action(function (string $providerId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
->action(function (string $providerId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
$provider = $dbForProject->getDocument('providers', $providerId);
if ($provider->isEmpty()) {
@@ -1159,12 +1158,9 @@ App::get('/v1/messaging/providers/:providerId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$audit = new Audit($dbForProject);
$resource = 'provider/' . $providerId;
$logs = $audit->getLogsByResource($resource, offset: $offset, limit: $limit);
$logs = $audit->getLogsByResource($resource, $queries);
$output = [];
foreach ($logs as $i => &$log) {
@@ -1211,7 +1207,7 @@ App::get('/v1/messaging/providers/:providerId/logs')
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -2481,8 +2477,9 @@ App::get('/v1/messaging/topics')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('dbForProject')
->inject('authorization')
->inject('response')
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
@@ -2508,7 +2505,7 @@ App::get('/v1/messaging/topics')
}
$topicId = $cursor->getValue();
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Topic '{$topicId}' for the 'cursor' value not found.");
@@ -2547,14 +2544,13 @@ App::get('/v1/messaging/topics/:topicId/logs')
]
))
->param('topicId', '', new UID(), 'Topic ID.')
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->param('queries', [], new Types([Query::TYPE_LIMIT, Query::TYPE_OFFSET]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->inject('audit')
->action(function (string $topicId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
->action(function (string $topicId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
$topic = $dbForProject->getDocument('topics', $topicId);
if ($topic->isEmpty()) {
@@ -2567,12 +2563,9 @@ App::get('/v1/messaging/topics/:topicId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$audit = new Audit($dbForProject);
$resource = 'topic/' . $topicId;
$logs = $audit->getLogsByResource($resource, offset: $offset, limit: $limit);
$logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -2620,7 +2613,7 @@ App::get('/v1/messaging/topics/:topicId/logs')
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -2782,29 +2775,27 @@ App::post('/v1/messaging/topics/:topicId/subscribers')
->param('targetId', '', new UID(), 'Target ID. The target ID to link to the specified Topic ID.')
->inject('queueForEvents')
->inject('dbForProject')
->inject('authorization')
->inject('response')
->action(function (string $subscriberId, string $topicId, string $targetId, Event $queueForEvents, Database $dbForProject, Response $response) {
->action(function (string $subscriberId, string $topicId, string $targetId, Event $queueForEvents, Database $dbForProject, Authorization $authorization, Response $response) {
$subscriberId = $subscriberId == 'unique()' ? ID::unique() : $subscriberId;
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
if ($topic->isEmpty()) {
throw new Exception(Exception::TOPIC_NOT_FOUND);
}
$validator = new Authorization('subscribe');
if (!$validator->isValid($topic->getAttribute('subscribe'))) {
throw new Exception(Exception::USER_UNAUTHORIZED, $validator->getDescription());
if (!$authorization->isValid(new Input('subscribe', $topic->getAttribute('subscribe')))) {
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
}
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
}
$user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
$user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
$subscriber = new Document([
'$id' => $subscriberId,
@@ -2837,7 +2828,7 @@ App::post('/v1/messaging/topics/:topicId/subscribers')
default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE),
};
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute(
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute(
'topics',
$topicId,
$totalAttribute,
@@ -2882,8 +2873,9 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('dbForProject')
->inject('authorization')
->inject('response')
->action(function (string $topicId, array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
->action(function (string $topicId, array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
@@ -2894,7 +2886,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
$queries[] = Query::search('search', $search);
}
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
if ($topic->isEmpty()) {
throw new Exception(Exception::TOPIC_NOT_FOUND);
@@ -2917,7 +2909,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
}
$subscriberId = $cursor->getValue();
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId));
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId));
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Subscriber '{$subscriberId}' for the 'cursor' value not found.");
@@ -2931,10 +2923,10 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject) {
return function () use ($subscriber, $dbForProject) {
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
$user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
$subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject, $authorization) {
return function () use ($subscriber, $dbForProject, $authorization) {
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
$user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
return $subscriber
->setAttribute('target', $target)
@@ -2968,14 +2960,13 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
]
))
->param('subscriberId', '', new UID(), 'Subscriber ID.')
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->param('queries', [], new Types([Query::TYPE_LIMIT, Query::TYPE_OFFSET]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->inject('audit')
->action(function (string $subscriberId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
->action(function (string $subscriberId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
$subscriber = $dbForProject->getDocument('subscribers', $subscriberId);
if ($subscriber->isEmpty()) {
@@ -2988,12 +2979,9 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$audit = new Audit($dbForProject);
$resource = 'subscriber/' . $subscriberId;
$logs = $audit->getLogsByResource($resource, limit: $limit, offset: $offset);
$logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -3041,7 +3029,7 @@ App::get('/v1/messaging/subscribers/:subscriberId/logs')
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -3067,9 +3055,10 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
->param('topicId', '', new UID(), 'Topic ID. The topic ID subscribed to.')
->param('subscriberId', '', new UID(), 'Subscriber ID.')
->inject('dbForProject')
->inject('authorization')
->inject('response')
->action(function (string $topicId, string $subscriberId, Database $dbForProject, Response $response) {
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
->action(function (string $topicId, string $subscriberId, Database $dbForProject, Authorization $authorization, Response $response) {
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
if ($topic->isEmpty()) {
throw new Exception(Exception::TOPIC_NOT_FOUND);
@@ -3081,8 +3070,8 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
throw new Exception(Exception::SUBSCRIBER_NOT_FOUND);
}
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
$user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
$user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
$subscriber
->setAttribute('target', $target)
@@ -3118,9 +3107,10 @@ App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
->param('subscriberId', '', new UID(), 'Subscriber ID.')
->inject('queueForEvents')
->inject('dbForProject')
->inject('authorization')
->inject('response')
->action(function (string $topicId, string $subscriberId, Event $queueForEvents, Database $dbForProject, Response $response) {
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
->action(function (string $topicId, string $subscriberId, Event $queueForEvents, Database $dbForProject, Authorization $authorization, Response $response) {
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
if ($topic->isEmpty()) {
throw new Exception(Exception::TOPIC_NOT_FOUND);
@@ -3143,7 +3133,7 @@ App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE),
};
Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute(
$authorization->skip(fn () => $dbForProject->decreaseDocumentAttribute(
'topics',
$topicId,
$totalAttribute,
@@ -3702,8 +3692,9 @@ App::get('/v1/messaging/messages')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('dbForProject')
->inject('authorization')
->inject('response')
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
@@ -3729,7 +3720,7 @@ App::get('/v1/messaging/messages')
}
$messageId = $cursor->getValue();
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('messages', $messageId));
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('messages', $messageId));
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Message '{$messageId}' for the 'cursor' value not found.");
@@ -3768,14 +3759,13 @@ App::get('/v1/messaging/messages/:messageId/logs')
],
))
->param('messageId', '', new UID(), 'Message ID.')
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->param('queries', [], new Types([Query::TYPE_LIMIT, Query::TYPE_OFFSET]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->inject('audit')
->action(function (string $messageId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
->action(function (string $messageId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
$message = $dbForProject->getDocument('messages', $messageId);
if ($message->isEmpty()) {
@@ -3788,12 +3778,9 @@ App::get('/v1/messaging/messages/:messageId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$audit = new Audit($dbForProject);
$resource = 'message/' . $messageId;
$logs = $audit->getLogsByResource($resource, limit: $limit, offset: $offset);
$logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -3841,7 +3828,7 @@ App::get('/v1/messaging/messages/:messageId/logs')
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
+16 -9
View File
@@ -18,8 +18,9 @@ use Utopia\Database\Exception\Order as OrderException;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Query;
use Utopia\Database\QueryContext;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Queries\Documents;
use Utopia\Database\Validator\Queries\V2 as DocumentsValidator;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\UID;
use Utopia\Migration\Resource;
@@ -342,6 +343,7 @@ App::post('/v1/migrations/csv/imports')
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('authorization')
->inject('project')
->inject('platform')
->inject('deviceForFiles')
@@ -356,6 +358,7 @@ App::post('/v1/migrations/csv/imports')
Response $response,
Database $dbForProject,
Database $dbForPlatform,
Authorization $authorization,
Document $project,
array $platform,
Device $deviceForFiles,
@@ -363,7 +366,7 @@ App::post('/v1/migrations/csv/imports')
Event $queueForEvents,
Migration $queueForMigrations
) {
$bucket = Authorization::skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) {
$bucket = $authorization->skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) {
if ($internalFile) {
return $dbForPlatform->getDocument('buckets', 'default');
}
@@ -374,7 +377,7 @@ App::post('/v1/migrations/csv/imports')
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
$file = Authorization::skip(fn () => $internalFile ? $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $fileId) : $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
$file = $authorization->skip(fn () => $internalFile ? $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $fileId) : $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
if ($file->isEmpty()) {
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
@@ -491,6 +494,7 @@ App::post('/v1/migrations/csv/exports')
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('authorization')
->inject('project')
->inject('platform')
->inject('queueForEvents')
@@ -509,6 +513,7 @@ App::post('/v1/migrations/csv/exports')
Response $response,
Database $dbForProject,
Database $dbForPlatform,
Authorization $authorization,
Document $project,
array $platform,
Event $queueForEvents,
@@ -520,7 +525,7 @@ App::post('/v1/migrations/csv/exports')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$bucket = Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'default'));
$bucket = $authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'default'));
if ($bucket->isEmpty()) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
@@ -533,19 +538,21 @@ App::post('/v1/migrations/csv/exports')
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
if ($database->isEmpty()) {
throw new Exception(Exception::DATABASE_NOT_FOUND);
}
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
$collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
if ($collection->isEmpty()) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
$validator = new Documents(
attributes: $collection->getAttribute('attributes', []),
indexes: $collection->getAttribute('indexes', []),
$context = new QueryContext();
$context->add($collection);
$validator = new DocumentsValidator(
$context,
idAttributeType: $dbForProject->getAdapter()->getIdAttributeType(),
);
+5 -4
View File
@@ -45,9 +45,10 @@ App::get('/v1/project/usage')
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('authorization')
->inject('getLogsDB')
->inject('smsRates')
->action(function (string $startDate, string $endDate, string $period, Response $response, Document $project, Database $dbForProject, callable $getLogsDB, array $smsRates) {
->action(function (string $startDate, string $endDate, string $period, Response $response, Document $project, Database $dbForProject, Authorization $authorization, callable $getLogsDB, array $smsRates) {
$stats = $total = $usage = [];
$format = 'Y-m-d 00:00:00';
$firstDay = (new DateTime($startDate))->format($format);
@@ -102,7 +103,7 @@ App::get('/v1/project/usage')
'1d' => 'Y-m-d\T00:00:00.000P',
};
Authorization::skip(function () use ($dbForProject, $dbForLogs, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
$authorization->skip(function () use ($dbForProject, $dbForLogs, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
foreach ($metrics['total'] as $metric) {
$db = ($metric === METRIC_FILES_IMAGES_TRANSFORMED) ? $dbForLogs : $dbForProject;
@@ -286,7 +287,7 @@ App::get('/v1/project/usage')
}, $dbForProject->find('functions'));
// This total is includes free and paid SMS usage
$authPhoneTotal = Authorization::skip(fn () => $dbForProject->sum('stats', 'value', [
$authPhoneTotal = $authorization->skip(fn () => $dbForProject->sum('stats', 'value', [
Query::equal('metric', [METRIC_AUTH_METHOD_PHONE]),
Query::equal('period', ['1d']),
Query::greaterThanEqual('time', $firstDay),
@@ -294,7 +295,7 @@ App::get('/v1/project/usage')
]));
// This estimate is only for paid SMS usage
$authPhoneMetrics = Authorization::skip(fn () => $dbForProject->find('stats', [
$authPhoneMetrics = $authorization->skip(fn () => $dbForProject->find('stats', [
Query::startsWith('metric', METRIC_AUTH_METHOD_PHONE . '.'),
Query::equal('period', ['1d']),
Query::greaterThanEqual('time', $firstDay),
+10 -41
View File
@@ -21,7 +21,6 @@ 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;
@@ -248,15 +247,13 @@ App::post('/v1/projects')
}
if ($create || $projectTables) {
$adapter = new AdapterDatabase($dbForProject);
$audit = new Audit($adapter);
$audit = new Audit($dbForProject);
$audit->setup();
}
if (!$create && $sharedTablesV1) {
$adapter = new AdapterDatabase($dbForProject);
$attributes = $adapter->getAttributeDocuments();
$indexes = $adapter->getIndexDocuments();
$attributes = \array_map(fn ($attribute) => new Document($attribute), Audit::ATTRIBUTES);
$indexes = \array_map(fn (array $index) => new Document($index), Audit::INDEXES);
$dbForProject->createDocument(Database::METADATA, new Document([
'$id' => ID::custom('audit'),
'$permissions' => [Permission::create(Role::any())],
@@ -297,7 +294,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)
@@ -1502,9 +1499,6 @@ 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,
@@ -1552,13 +1546,7 @@ App::get('/v1/projects/:projectId/keys')
}
$keys = $dbForPlatform->find('keys', [
Query::or([
Query::equal('projectInternalId', [$project->getSequence()]),
Query::and([
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$project->getSequence()]),
])
]),
Query::equal('projectInternalId', [$project->getSequence()]),
Query::limit(5000),
]);
@@ -1599,13 +1587,7 @@ App::get('/v1/projects/:projectId/keys/:keyId')
$key = $dbForPlatform->findOne('keys', [
Query::equal('$id', [$keyId]),
Query::or([
Query::equal('projectInternalId', [$project->getSequence()]),
Query::and([
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$project->getSequence()]),
])
])
Query::equal('projectInternalId', [$project->getSequence()]),
]);
if ($key->isEmpty()) {
@@ -1649,13 +1631,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
$key = $dbForPlatform->findOne('keys', [
Query::equal('$id', [$keyId]),
Query::or([
Query::equal('projectInternalId', [$project->getSequence()]),
Query::and([
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$project->getSequence()]),
])
])
Query::equal('projectInternalId', [$project->getSequence()]),
]);
if ($key->isEmpty()) {
@@ -1706,13 +1682,7 @@ App::delete('/v1/projects/:projectId/keys/:keyId')
$key = $dbForPlatform->findOne('keys', [
Query::equal('$id', [$keyId]),
Query::or([
Query::equal('projectInternalId', [$project->getSequence()]),
Query::and([
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$project->getSequence()]),
])
])
Query::equal('projectInternalId', [$project->getSequence()]),
]);
if ($key->isEmpty()) {
@@ -2109,7 +2079,6 @@ 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;
@@ -2125,7 +2094,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, $error->getMessage());
throw new Exception(Exception::PROJECT_SMTP_CONFIG_INVALID, 'Could not connect to SMTP server: ' . $error->getMessage());
}
}
@@ -2696,7 +2665,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
+41 -38
View File
@@ -21,6 +21,7 @@ use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Memberships;
use Appwrite\Utopia\Database\Validator\Queries\Teams;
use Appwrite\Utopia\Database\Validator\Queries\Types;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use libphonenumber\PhoneNumberUtil;
@@ -49,7 +50,6 @@ use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\Queries;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\UID;
use Utopia\Emails\Email;
use Utopia\Locale\Locale;
@@ -86,16 +86,17 @@ App::post('/v1/teams')
->inject('response')
->inject('user')
->inject('dbForProject')
->inject('authorization')
->inject('queueForEvents')
->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
$isAppUser = User::isApp(Authorization::getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$isAppUser = User::isApp($authorization->getRoles());
$teamId = $teamId == 'unique()' ? ID::unique() : $teamId;
try {
$team = Authorization::skip(fn () => $dbForProject->createDocument('teams', new Document([
$team = $authorization->skip(fn () => $dbForProject->createDocument('teams', new Document([
'$id' => $teamId,
'$permissions' => [
Permission::read(Role::team($teamId)),
@@ -491,6 +492,7 @@ App::post('/v1/teams/:teamId/memberships')
->inject('project')
->inject('user')
->inject('dbForProject')
->inject('authorization')
->inject('locale')
->inject('queueForMails')
->inject('queueForMessaging')
@@ -500,9 +502,9 @@ App::post('/v1/teams/:teamId/memberships')
->inject('plan')
->inject('proofForPassword')
->inject('proofForToken')
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Password $proofForPassword, Token $proofForToken) {
$isAppUser = User::isApp(Authorization::getRoles());
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Authorization $authorization, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Password $proofForPassword, Token $proofForToken) {
$isAppUser = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$url = htmlentities($url);
if (empty($url)) {
@@ -619,13 +621,13 @@ App::post('/v1/teams/:teamId/memberships')
]);
try {
$invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', $userDocument));
$invitee = $authorization->skip(fn () => $dbForProject->createDocument('users', $userDocument));
} catch (Duplicate $th) {
throw new Exception(Exception::USER_ALREADY_EXISTS);
}
}
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
$isOwner = $authorization->hasRole('team:' . $team->getId() . '/owner');
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to send invitations for this team');
@@ -661,11 +663,11 @@ App::post('/v1/teams/:teamId/memberships')
]);
$membership = ($isPrivilegedUser || $isAppUser) ?
Authorization::skip(fn () => $dbForProject->createDocument('memberships', $membership)) :
$authorization->skip(fn () => $dbForProject->createDocument('memberships', $membership)) :
$dbForProject->createDocument('memberships', $membership);
if ($isPrivilegedUser || $isAppUser) {
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
}
} elseif ($membership->getAttribute('confirm') === false) {
$membership->setAttribute('secret', $proofForToken->hash($secret));
@@ -677,7 +679,7 @@ App::post('/v1/teams/:teamId/memberships')
}
$membership = ($isPrivilegedUser || $isAppUser) ?
Authorization::skip(fn () => $dbForProject->updateDocument('memberships', $membership->getId(), $membership)) :
$authorization->skip(fn () => $dbForProject->updateDocument('memberships', $membership->getId(), $membership)) :
$dbForProject->updateDocument('memberships', $membership->getId(), $membership);
} else {
throw new Exception(Exception::MEMBERSHIP_ALREADY_CONFIRMED);
@@ -863,7 +865,8 @@ App::get('/v1/teams/:teamId/memberships')
->inject('response')
->inject('project')
->inject('dbForProject')
->action(function (string $teamId, array $queries, string $search, bool $includeTotal, Response $response, Document $project, Database $dbForProject) {
->inject('authorization')
->action(function (string $teamId, array $queries, string $search, bool $includeTotal, Response $response, Document $project, Database $dbForProject, Authorization $authorization) {
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
@@ -933,7 +936,7 @@ App::get('/v1/teams/:teamId/memberships')
'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true,
];
$roles = Authorization::getRoles();
$roles = $authorization->getRoles();
$isPrivilegedUser = User::isPrivileged($roles);
$isAppUser = User::isApp($roles);
@@ -1004,7 +1007,8 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
->inject('response')
->inject('project')
->inject('dbForProject')
->action(function (string $teamId, string $membershipId, Response $response, Document $project, Database $dbForProject) {
->inject('authorization')
->action(function (string $teamId, string $membershipId, Response $response, Document $project, Database $dbForProject, Authorization $authorization) {
$team = $dbForProject->getDocument('teams', $teamId);
@@ -1024,7 +1028,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true,
];
$roles = Authorization::getRoles();
$roles = $authorization->getRoles();
$isPrivilegedUser = User::isPrivileged($roles);
$isAppUser = User::isApp($roles);
@@ -1103,8 +1107,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
->inject('user')
->inject('project')
->inject('dbForProject')
->inject('authorization')
->inject('queueForEvents')
->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents) {
->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
@@ -1121,9 +1126,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
throw new Exception(Exception::USER_NOT_FOUND);
}
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
$isAppUser = User::isApp(Authorization::getRoles());
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$isAppUser = User::isApp($authorization->getRoles());
$isOwner = $authorization->hasRole('team:' . $team->getId() . '/owner');
if ($project->getId() === 'console') {
// Quick check: fetch up to 2 owners to determine if only one exists
@@ -1204,12 +1209,13 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->inject('response')
->inject('user')
->inject('dbForProject')
->inject('authorization')
->inject('project')
->inject('geodb')
->inject('queueForEvents')
->inject('store')
->inject('proofForToken')
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Authorization $authorization, $project, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
$protocol = $request->getProtocol();
$membership = $dbForProject->getDocument('memberships', $membershipId);
@@ -1218,7 +1224,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
throw new Exception(Exception::MEMBERSHIP_NOT_FOUND);
}
$team = Authorization::skip(fn () => $dbForProject->getDocument('teams', $teamId));
$team = $authorization->skip(fn () => $dbForProject->getDocument('teams', $teamId));
if ($team->isEmpty()) {
throw new Exception(Exception::TEAM_NOT_FOUND);
@@ -1254,11 +1260,11 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->setAttribute('confirm', true)
;
Authorization::skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
$authorization->skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
// Create session for the user if not logged in
if (!$hasSession) {
Authorization::setRole(Role::user($user->getId())->toString());
$authorization->addRole(Role::user($user->getId())->toString());
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
@@ -1286,7 +1292,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$session = $dbForProject->createDocument('sessions', $session);
Authorization::setRole(Role::user($userId)->toString());
$authorization->addRole(Role::user($userId)->toString());
$encoded = $store
->setProperty('id', $user->getId())
@@ -1324,7 +1330,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$dbForProject->purgeCachedDocument('users', $user->getId());
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
$queueForEvents
->setParam('userId', $user->getId())
@@ -1368,8 +1374,9 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
->inject('project')
->inject('response')
->inject('dbForProject')
->inject('authorization')
->inject('queueForEvents')
->action(function (string $teamId, string $membershipId, Document $user, Document $project, Response $response, Database $dbForProject, Event $queueForEvents) {
->action(function (string $teamId, string $membershipId, Document $user, Document $project, Response $response, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
$membership = $dbForProject->getDocument('memberships', $membershipId);
@@ -1427,7 +1434,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
$dbForProject->purgeCachedDocument('users', $profile->getId());
if ($membership->getAttribute('confirm')) { // Count only confirmed members
Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0));
$authorization->skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0));
}
$queueForEvents
@@ -1458,14 +1465,13 @@ App::get('/v1/teams/:teamId/logs')
]
))
->param('teamId', '', new UID(), 'Team ID.')
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->param('queries', [], new Types([Query::TYPE_LIMIT, Query::TYPE_OFFSET]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->inject('audit')
->action(function (string $teamId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
->action(function (string $teamId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
$team = $dbForProject->getDocument('teams', $teamId);
@@ -1479,12 +1485,9 @@ App::get('/v1/teams/:teamId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$audit = new Audit($dbForProject);
$resource = 'team/' . $team->getId();
$logs = $audit->getLogsByResource($resource, offset: $offset, limit: $limit);
$logs = $audit->getLogsByResource($resource, $queries);
$output = [];
@@ -1531,7 +1534,7 @@ App::get('/v1/teams/:teamId/logs')
}
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByResource($resource) : 0,
'total' => $includeTotal ? $audit->countLogsByResource($resource, $queries) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
+9 -14
View File
@@ -25,6 +25,7 @@ use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Identities;
use Appwrite\Utopia\Database\Validator\Queries\Memberships;
use Appwrite\Utopia\Database\Validator\Queries\Targets;
use Appwrite\Utopia\Database\Validator\Queries\Types;
use Appwrite\Utopia\Database\Validator\Queries\Users;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
@@ -57,8 +58,6 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Queries;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Database\Validator\UID;
use Utopia\Emails\Email;
use Utopia\Locale\Locale;
@@ -939,14 +938,13 @@ App::get('/v1/users/:userId/logs')
]
))
->param('userId', '', new UID(), 'User ID.')
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->param('queries', [], new Types([Query::TYPE_LIMIT, Query::TYPE_OFFSET]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
->inject('response')
->inject('dbForProject')
->inject('locale')
->inject('geodb')
->inject('audit')
->action(function (string $userId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb, Audit $audit) {
->action(function (string $userId, array $queries, bool $includeTotal, Response $response, Database $dbForProject, Locale $locale, Reader $geodb) {
$user = $dbForProject->getDocument('users', $userId);
@@ -959,11 +957,8 @@ App::get('/v1/users/:userId/logs')
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$grouped = Query::groupByType($queries);
$limit = $grouped['limit'] ?? 25;
$offset = $grouped['offset'] ?? 0;
$logs = $audit->getLogsByUser($user->getSequence(), limit: $limit, offset: $offset);
$audit = new Audit($dbForProject);
$logs = $audit->getLogsByUser($user->getSequence(), $queries);
$output = [];
foreach ($logs as $i => &$log) {
$log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
@@ -1003,7 +998,7 @@ App::get('/v1/users/:userId/logs')
}
$response->dynamic(new Document([
'total' => $includeTotal ? $audit->countLogsByUser($user->getSequence()) : 0,
'total' => $includeTotal ? $audit->countLogsByUser($user->getSequence(), $queries) : 0,
'logs' => $output,
]), Response::MODEL_LOG_LIST);
});
@@ -2678,8 +2673,8 @@ App::get('/v1/users/usage')
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForProject')
->inject('register')
->action(function (string $range, Response $response, Database $dbForProject) {
->inject('authorization')
->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) {
$periods = Config::getParam('usage', []);
$stats = $usage = [];
@@ -2689,7 +2684,7 @@ App::get('/v1/users/usage')
METRIC_SESSIONS,
];
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
$authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) {
foreach ($metrics as $count => $metric) {
$result = $dbForProject->findOne('stats', [
Query::equal('metric', [$metric]),
+33 -33
View File
@@ -10,6 +10,7 @@ use Appwrite\SDK\Method;
use Appwrite\SDK\MethodType;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Queries\Installations;
use Appwrite\Utopia\Database\Validator\Queries\Types;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Appwrite\Vcs\Comment;
@@ -30,10 +31,7 @@ use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Queries;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Detector\Detection\Framework\Analog;
use Utopia\Detector\Detection\Framework\Angular;
use Utopia\Detector\Detection\Framework\Astro;
@@ -76,7 +74,7 @@ use Utopia\VCS\Exception\RepositoryNotFound;
use function Swoole\Coroutine\batch;
$createGitDeployments = function (GitHub $github, string $providerInstallationId, array $repositories, string $providerBranch, string $providerBranchUrl, string $providerRepositoryName, string $providerRepositoryUrl, string $providerRepositoryOwner, string $providerCommitHash, string $providerCommitAuthor, string $providerCommitAuthorUrl, string $providerCommitMessage, string $providerCommitUrl, string $providerPullRequestId, bool $external, Database $dbForPlatform, Build $queueForBuilds, callable $getProjectDB, array $platform) {
$createGitDeployments = function (GitHub $github, string $providerInstallationId, array $repositories, string $providerBranch, string $providerBranchUrl, string $providerRepositoryName, string $providerRepositoryUrl, string $providerRepositoryOwner, string $providerCommitHash, string $providerCommitAuthor, string $providerCommitAuthorUrl, string $providerCommitMessage, string $providerCommitUrl, string $providerPullRequestId, bool $external, Database $dbForPlatform, Authorization $authorization, Build $queueForBuilds, callable $getProjectDB, Request $request, array $platform) {
$errors = [];
foreach ($repositories as $repository) {
try {
@@ -87,12 +85,12 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
}
$projectId = $repository->getAttribute('projectId');
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
$dbForProject = $getProjectDB($project);
$resourceCollection = $resourceType === "function" ? 'functions' : 'sites';
$resourceId = $repository->getAttribute('resourceId');
$resource = Authorization::skip(fn () => $dbForProject->getDocument($resourceCollection, $resourceId));
$resource = $authorization->skip(fn () => $dbForProject->getDocument($resourceCollection, $resourceId));
$resourceInternalId = $resource->getSequence();
$deploymentId = ID::unique();
@@ -141,7 +139,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$latestCommentId = '';
if (!empty($providerPullRequestId) && $resource->getAttribute('providerSilentMode', false) === false) {
$latestComment = Authorization::skip(fn () => $dbForPlatform->findOne('vcsComments', [
$latestComment = $authorization->skip(fn () => $dbForPlatform->findOne('vcsComments', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::equal('providerPullRequestId', [$providerPullRequestId]),
Query::orderDesc('$createdAt'),
@@ -180,7 +178,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()));
} finally {
Authorization::skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId));
$authorization->skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId));
}
}
} else {
@@ -191,7 +189,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
if (!empty($latestCommentId)) {
$teamId = $project->getAttribute('teamId', '');
$latestComment = Authorization::skip(fn () => $dbForPlatform->createDocument('vcsComments', new Document([
$latestComment = $authorization->skip(fn () => $dbForPlatform->createDocument('vcsComments', new Document([
'$id' => ID::unique(),
'$permissions' => [
Permission::read(Role::team(ID::custom($teamId))),
@@ -212,7 +210,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
}
}
} elseif (!empty($providerBranch)) {
$latestComments = Authorization::skip(fn () => $dbForPlatform->find('vcsComments', [
$latestComments = $authorization->skip(fn () => $dbForPlatform->find('vcsComments', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::equal('providerBranch', [$providerBranch]),
Query::orderDesc('$createdAt'),
@@ -251,7 +249,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()));
} finally {
Authorization::skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId));
$authorization->skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId));
}
}
}
@@ -294,7 +292,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$commands[] = $resource->getAttribute('commands', '');
}
$deployment = Authorization::skip(fn () => $dbForProject->createDocument('deployments', new Document([
$deployment = $authorization->skip(fn () => $dbForProject->createDocument('deployments', new Document([
'$id' => $deploymentId,
'$permissions' => [
Permission::read(Role::any()),
@@ -334,7 +332,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
Authorization::skip(fn () => $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource));
$authorization->skip(fn () => $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource));
if ($resource->getCollection() === 'sites') {
$projectId = $project->getId();
@@ -344,7 +342,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$domain = ID::unique() . "." . $sitesDomain;
$ruleId = md5($domain);
$previewRuleId = $ruleId;
Authorization::skip(
$authorization->skip(
fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
@@ -377,7 +375,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}";
$ruleId = md5($domain);
try {
Authorization::skip(
$authorization->skip(
fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
@@ -408,7 +406,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$domain = "commit-" . substr($providerCommitHash, 0, 16) . ".{$sitesDomain}";
$ruleId = md5($domain);
try {
Authorization::skip(
$authorization->skip(
fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
@@ -460,7 +458,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
if ($lockAcquired) {
// Wrap in try/finally to ensure lock file gets deleted
try {
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', $previewRuleId));
$rule = $authorization->skip(fn () => $dbForPlatform->getDocument('rules', $previewRuleId));
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$previewUrl = !empty($rule) ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : '';
@@ -472,7 +470,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
$github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment());
}
} finally {
Authorization::skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId));
$authorization->skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId));
}
}
}
@@ -1035,7 +1033,7 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
->param('installationId', '', new Text(256), 'Installation Id')
->param('type', '', new WhiteList(['runtime', 'framework']), 'Detector type. Must be one of the following: runtime, framework')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->param('queries', [], new Types([Query::TYPE_LIMIT, Query::TYPE_OFFSET]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->inject('gitHub')
->inject('response')
->inject('dbForPlatform')
@@ -1476,11 +1474,12 @@ App::post('/v1/vcs/github/events')
->inject('request')
->inject('response')
->inject('dbForPlatform')
->inject('authorization')
->inject('getProjectDB')
->inject('queueForBuilds')
->inject('platform')
->action(
function (GitHub $github, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds, array $platform) use ($createGitDeployments) {
function (GitHub $github, Request $request, Response $response, Database $dbForPlatform, Authorization $authorization, callable $getProjectDB, Build $queueForBuilds, array $platform) use ($createGitDeployments) {
$payload = $request->getRawPayload();
$signatureRemote = $request->getHeader('x-hub-signature-256', '');
$signatureLocal = System::getEnv('_APP_VCS_GITHUB_WEBHOOK_SECRET', '');
@@ -1516,14 +1515,14 @@ App::post('/v1/vcs/github/events')
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
//find resourceId from relevant resources table
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
$repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::limit(100),
]));
// create new deployment only on push (not committed by us) and not when branch is created or deleted
if ($providerCommitAuthorEmail !== APP_VCS_GITHUB_EMAIL && !$providerBranchCreated && !$providerBranchDeleted) {
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthorName, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $queueForBuilds, $getProjectDB, $platform);
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthorName, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $request, $platform);
}
} elseif ($event == $github::EVENT_INSTALLATION) {
if ($parsedPayload["action"] == "deleted") {
@@ -1536,16 +1535,16 @@ App::post('/v1/vcs/github/events')
]);
foreach ($installations as $installation) {
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
$repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
Query::equal('installationInternalId', [$installation->getSequence()]),
Query::limit(1000)
]));
foreach ($repositories as $repository) {
Authorization::skip(fn () => $dbForPlatform->deleteDocument('repositories', $repository->getId()));
$authorization->skip(fn () => $dbForPlatform->deleteDocument('repositories', $repository->getId()));
}
Authorization::skip(fn () => $dbForPlatform->deleteDocument('installations', $installation->getId()));
$authorization->skip(fn () => $dbForPlatform->deleteDocument('installations', $installation->getId()));
}
}
} elseif ($event == $github::EVENT_PULL_REQUEST) {
@@ -1574,12 +1573,12 @@ App::post('/v1/vcs/github/events')
$providerCommitAuthor = $commitDetails["commitAuthor"] ?? '';
$providerCommitMessage = $commitDetails["commitMessage"] ?? '';
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
$repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::orderDesc('$createdAt')
]));
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForPlatform, $queueForBuilds, $getProjectDB, $platform);
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $request, $platform);
} elseif ($parsedPayload["action"] == "closed") {
// Allowed external contributions cleanup
@@ -1588,7 +1587,7 @@ App::post('/v1/vcs/github/events')
$external = $parsedPayload["external"] ?? true;
if ($external) {
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
$repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
Query::equal('providerRepositoryId', [$providerRepositoryId]),
Query::orderDesc('$createdAt')
]));
@@ -1599,7 +1598,7 @@ App::post('/v1/vcs/github/events')
if (\in_array($providerPullRequestId, $providerPullRequestIds)) {
$providerPullRequestIds = \array_diff($providerPullRequestIds, [$providerPullRequestId]);
$repository = $repository->setAttribute('providerPullRequestIds', $providerPullRequestIds);
$repository = Authorization::skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
$repository = $authorization->skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
}
}
}
@@ -1786,17 +1785,18 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
->inject('response')
->inject('project')
->inject('dbForPlatform')
->inject('authorization')
->inject('getProjectDB')
->inject('queueForBuilds')
->inject('platform')
->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Response $response, Document $project, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds, array $platform) use ($createGitDeployments) {
->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForPlatform, Authorization $authorization, callable $getProjectDB, Build $queueForBuilds, array $platform) use ($createGitDeployments) {
$installation = $dbForPlatform->getDocument('installations', $installationId);
if ($installation->isEmpty()) {
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
$repository = Authorization::skip(fn () => $dbForPlatform->findOne('repositories', [
$repository = $authorization->skip(fn () => $dbForPlatform->findOne('repositories', [
Query::equal('$id', [$repositoryId]),
Query::equal('projectInternalId', [$project->getSequence()])
]));
@@ -1814,7 +1814,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
// TODO: Delete from array when PR is closed
$repository = Authorization::skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
$repository = $authorization->skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
@@ -1846,7 +1846,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
$providerCommitMessage = $pullRequestResponse['title'] ?? '';
$providerCommitUrl = $pullRequestResponse['html_url'] ?? '';
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, true, $dbForPlatform, $queueForBuilds, $getProjectDB, $platform);
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, '', '', '', '', $providerCommitHash, '', '', '', '', $providerPullRequestId, true, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $request, $platform);
$response->noContent();
});
+47 -37
View File
@@ -59,7 +59,7 @@ Config::setParam('domainVerification', false);
Config::setParam('cookieDomain', 'localhost');
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey)
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey)
{
$host = $request->getHostname() ?? '';
if (!empty($previewHostname)) {
@@ -67,16 +67,16 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
}
// TODO: (@Meldiron) Remove after 1.7.x migration
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
$rule = Authorization::skip(function () use ($dbForPlatform, $host, $isMd5) {
if ($isMd5) {
return $dbForPlatform->getDocument('rules', md5($host));
}
return $dbForPlatform->findOne('rules', [
Query::equal('domain', [$host]),
]) ?? new Document();
});
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
$rule = $authorization->skip(fn () => $dbForPlatform->getDocument('rules', md5($host)));
} else {
$rule = $authorization->skip(
fn () => $dbForPlatform->find('rules', [
Query::equal('domain', [$host]),
Query::limit(1)
])
)[0] ?? new Document();
}
$errorView = __DIR__ . '/../views/general/error.phtml';
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
@@ -111,7 +111,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
}
$projectId = $rule->getAttribute('projectId');
$project = Authorization::skip(
$project = $authorization->skip(
fn () => $dbForPlatform->getDocument('projects', $projectId)
);
@@ -119,7 +119,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$accessedAt = $project->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
$project->setAttribute('accessedAt', DateTime::now());
Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
$authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
}
/**
@@ -158,7 +158,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
/** @var Document $deployment */
if (!empty($rule->getAttribute('deploymentId', ''))) {
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('deploymentId')));
$deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('deploymentId')));
} else {
// 1.6.x DB schema compatibility
// TODO: Make sure deploymentId is never empty, and remove this code
@@ -172,15 +172,15 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
// Document of site or function
$resource = $resourceType === 'function' ?
Authorization::skip(fn () => $dbForProject->getDocument('functions', $resourceId)) :
Authorization::skip(fn () => $dbForProject->getDocument('sites', $resourceId));
$authorization->skip(fn () => $dbForProject->getDocument('functions', $resourceId)) :
$authorization->skip(fn () => $dbForProject->getDocument('sites', $resourceId));
// ID of active deployments
// Attempts to use attribute from both schemas (1.6 and 1.7)
$activeDeploymentId = $resource->getAttribute('deploymentId', $resource->getAttribute('deployment', ''));
// Get deployment document, as intended originally
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $activeDeploymentId));
$deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $activeDeploymentId));
}
if ($deployment->getAttribute('resourceType', '') === 'functions') {
@@ -199,8 +199,8 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
}
$resource = $type === 'function' ?
Authorization::skip(fn () => $dbForProject->getDocument('functions', $deployment->getAttribute('resourceId', ''))) :
Authorization::skip(fn () => $dbForProject->getDocument('sites', $deployment->getAttribute('resourceId', '')));
$authorization->skip(fn () => $dbForProject->getDocument('functions', $deployment->getAttribute('resourceId', ''))) :
$authorization->skip(fn () => $dbForProject->getDocument('sites', $deployment->getAttribute('resourceId', '')));
$isPreview = $type === 'function' ? false : ($rule->getAttribute('trigger', '') !== 'manual');
@@ -242,7 +242,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$userExists = false;
$userId = $payload['userId'] ?? '';
if (!empty($userId)) {
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
if (!$user->isEmpty() && $user->getAttribute('status', false)) {
$userExists = true;
}
@@ -255,7 +255,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
}
$membershipExists = false;
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
if (!$project->isEmpty() && isset($user)) {
$teamId = $project->getAttribute('teamId', '');
$membership = $user->find('teamId', $teamId, 'memberships');
@@ -862,15 +862,16 @@ App::init()
->inject('devKey')
->inject('apiKey')
->inject('cors')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Func $queueForFunctions, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors) {
->inject('authorization')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Func $queueForFunctions, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization) {
/*
* Appwrite Router
*/
$hostname = $request->getHostname() ?? '';
$platformHostnames = $platform['hostnames'] ?? [];
// Only run Router when external domain
if (!in_array($hostname, $platformHostnames) || !empty($previewHostname)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
if (!\in_array($hostname, $platformHostnames) || !empty($previewHostname)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey)) {
$utopia->getRoute()?->label('router', true);
}
}
@@ -1033,7 +1034,8 @@ App::init()
->inject('dbForPlatform')
->inject('queueForCertificates')
->inject('platform')
->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform) {
->inject('authorization')
->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform, Authorization $authorization) {
$hostname = $request->getHostname();
$cache = Config::getParam('hostnames', []);
$platformHostnames = $platform['hostnames'] ?? [];
@@ -1048,20 +1050,23 @@ 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;
}
// 4. Check/create rule (requires DB access)
Authorization::disable();
$authorization->disable();
try {
// TODO: (@Meldiron) Remove after 1.7.x migration
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
@@ -1117,7 +1122,7 @@ App::init()
} finally {
$cache[$domain->get()] = true;
Config::setParam('hostnames', $cache);
Authorization::reset();
$authorization->reset();
}
});
@@ -1141,7 +1146,8 @@ App::options()
->inject('devKey')
->inject('apiKey')
->inject('cors')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors) {
->inject('authorization')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization) {
/*
* Appwrite Router
*/
@@ -1182,7 +1188,8 @@ App::error()
->inject('log')
->inject('queueForStatsUsage')
->inject('devKey')
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, StatsUsage $queueForStatsUsage) {
->inject('authorization')
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, StatsUsage $queueForStatsUsage, Document $devKey, Authorization $authorization) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
$route = $utopia->getRoute();
$class = \get_class($error);
@@ -1264,7 +1271,7 @@ App::error()
* If not a publishable error, track usage stats. Publishable errors are >= 500 or those explicitly marked as publish=true in errors.php
*/
if (!$publish && $project->getId() !== 'console') {
if (!DBUser::isPrivileged(Authorization::getRoles())) {
if (!DBUser::isPrivileged($authorization->getRoles())) {
$fileSize = 0;
$file = $request->getFiles('file');
if (!empty($file)) {
@@ -1326,7 +1333,7 @@ App::error()
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('roles', Authorization::getRoles());
$log->addExtra('roles', $authorization->getRoles());
$action = 'UNKNOWN_NAMESPACE.UNKNOWN.METHOD';
if (!empty($sdk)) {
@@ -1450,13 +1457,14 @@ App::get('/robots.txt')
->inject('platform')
->inject('previewHostname')
->inject('apiKey')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey) {
->inject('authorization')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization) {
$platformHostnames = $platform['hostnames'] ?? [];
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
$template = new View(__DIR__ . '/../views/general/robots.phtml');
$response->text($template->render(false));
} else {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey)) {
$utopia->getRoute()?->label('router', true);
}
}
@@ -1482,13 +1490,14 @@ App::get('/humans.txt')
->inject('platform')
->inject('previewHostname')
->inject('apiKey')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey) {
->inject('authorization')
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization) {
$platformHostnames = $platform['hostnames'] ?? [];
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
$template = new View(__DIR__ . '/../views/general/humans.phtml');
$response->text($template->render(false));
} else {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey)) {
$utopia->getRoute()?->label('router', true);
}
}
@@ -1572,7 +1581,8 @@ App::get('/v1/ping')
->inject('project')
->inject('dbForPlatform')
->inject('queueForEvents')
->action(function (Response $response, Document $project, Database $dbForPlatform, Event $queueForEvents) {
->inject('authorization')
->action(function (Response $response, Document $project, Database $dbForPlatform, Event $queueForEvents, Authorization $authorization) {
if ($project->isEmpty() || $project->getId() === 'console') {
throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND);
}
@@ -1584,7 +1594,7 @@ App::get('/v1/ping')
->setAttribute('pingCount', $pingCount)
->setAttribute('pingedAt', $pingedAt);
Authorization::skip(function () use ($dbForPlatform, $project) {
$authorization->skip(function () use ($dbForPlatform, $project) {
$dbForPlatform->updateDocument('projects', $project->getId(), $project);
});
+28 -25
View File
@@ -30,6 +30,7 @@ use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Authorization\Input;
use Utopia\Queue\Publisher;
use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry;
@@ -233,7 +234,8 @@ App::init()
->inject('mode')
->inject('team')
->inject('apiKey')
->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, User $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey) {
->inject('authorization')
->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization) {
$route = $utopia->getRoute();
/**
@@ -318,7 +320,7 @@ App::init()
// Handle special app role case
if ($apiKey->getRole() === User::ROLE_APPS) {
// Disable authorization checks for API keys
Authorization::setDefaultStatus(false);
$authorization->setDefaultStatus(false);
$user = new User([
'$id' => '',
@@ -392,14 +394,14 @@ App::init()
$scopes = \array_merge($scopes, $roles[$role]['scopes']);
}
Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
$authorization->setDefaultStatus(false); // Cancel security segmentation for admin users.
}
$scopes = \array_unique($scopes);
Authorization::setRole($role);
foreach ($user->getRoles() as $authRole) {
Authorization::setRole($authRole);
$authorization->addRole($role);
foreach ($user->getRoles($authorization) as $authRole) {
$authorization->addRole($authRole);
}
// Step 6: Update project and user last activity
@@ -407,7 +409,7 @@ App::init()
$accessedAt = $project->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
$project->setAttribute('accessedAt', DateTime::now());
Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
$authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
}
}
@@ -442,7 +444,7 @@ App::init()
if (
array_key_exists($namespace, $project->getAttribute('services', []))
&& !$project->getAttribute('services', [])[$namespace]
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles()))
&& !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
) {
throw new Exception(Exception::GENERAL_SERVICE_DISABLED);
}
@@ -509,14 +511,15 @@ App::init()
->inject('devKey')
->inject('telemetry')
->inject('platform')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Mail $queueForMails, Migration $queueForMigrations, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform) use ($usageDatabaseListener, $eventDatabaseListener) {
->inject('authorization')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Mail $queueForMails, Migration $queueForMigrations, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform, Authorization $authorization) use ($usageDatabaseListener, $eventDatabaseListener) {
$route = $utopia->getRoute();
if (
array_key_exists('rest', $project->getAttribute('apis', []))
&& !$project->getAttribute('apis', [])['rest']
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles()))
&& !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
}
@@ -546,7 +549,7 @@ App::init()
$closestLimit = null;
$roles = Authorization::getRoles();
$roles = $authorization->getRoles();
$isPrivilegedUser = User::isPrivileged($roles);
$isAppUser = User::isApp($roles);
@@ -657,10 +660,10 @@ App::init()
if ($useCache) {
$route = $utopia->match($request);
$isImageTransformation = $route->getPath() === '/v1/storage/buckets/:bucketId/files/:fileId/preview';
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && !User::isPrivileged(Authorization::getRoles());
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && !User::isPrivileged($authorization->getRoles());
$key = $request->cacheIdentifier();
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
$cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key));
$cache = new Cache(
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
);
@@ -677,10 +680,10 @@ App::init()
if ($type === 'bucket' && (!$isImageTransformation || !$isDisabled)) {
$bucketId = $parts[1] ?? null;
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAppUser && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -691,8 +694,7 @@ App::init()
}
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
$validator = new Authorization(Database::PERMISSION_READ);
$valid = $validator->isValid($bucket->getRead());
$valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
if (!$fileSecurity && !$valid && !$isToken) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
@@ -703,7 +705,7 @@ App::init()
if ($fileSecurity && !$valid && !$isToken) {
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
} else {
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
}
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) {
@@ -714,11 +716,11 @@ App::init()
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
//Do not update transformedAt if it's a console user
if (!User::isPrivileged(Authorization::getRoles())) {
if (!User::isPrivileged($authorization->getRoles())) {
$transformedAt = $file->getAttribute('transformedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
$file->setAttribute('transformedAt', DateTime::now());
Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
$authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
}
}
}
@@ -814,7 +816,8 @@ App::shutdown()
->inject('queueForWebhooks')
->inject('queueForRealtime')
->inject('dbForProject')
->action(function (App $utopia, Request $request, Response $response, Document $project, User $user, Event $queueForEvents, Audit $queueForAudits, StatsUsage $queueForStatsUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject) use ($parseLabel) {
->inject('authorization')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, StatsUsage $queueForStatsUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, Authorization $authorization) use ($parseLabel) {
$responsePayload = $response->getPayload();
@@ -940,11 +943,11 @@ App::shutdown()
$key = $request->cacheIdentifier();
$signature = md5($data['payload']);
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
$cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key));
$accessedAt = $cacheLog->getAttribute('accessedAt', 0);
$now = DateTime::now();
if ($cacheLog->isEmpty()) {
Authorization::skip(fn () => $dbForProject->createDocument('cache', new Document([
$authorization->skip(fn () => $dbForProject->createDocument('cache', new Document([
'$id' => $key,
'resource' => $resource,
'resourceType' => $resourceType,
@@ -954,7 +957,7 @@ App::shutdown()
])));
} elseif (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) {
$cacheLog->setAttribute('accessedAt', $now);
Authorization::skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog));
$authorization->skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog));
// Overwrite the file every APP_CACHE_UPDATE seconds to update the file modified time that is used in the TTL checks in cache->load()
$cache->save($key, $data['payload']);
}
@@ -966,7 +969,7 @@ App::shutdown()
}
if ($project->getId() !== 'console') {
if (!User::isPrivileged(Authorization::getRoles())) {
if (!User::isPrivileged($authorization->getRoles())) {
$fileSize = 0;
$file = $request->getFiles('file');
if (!empty($file)) {
+4 -3
View File
@@ -36,7 +36,8 @@ App::init()
->inject('request')
->inject('project')
->inject('geodb')
->action(function (App $utopia, Request $request, Document $project, Reader $geodb) {
->inject('authorization')
->action(function (App $utopia, Request $request, Document $project, Reader $geodb, Authorization $authorization) {
$denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', '');
if (!empty($denylist && $project->getId() === 'console')) {
$countries = explode(',', $denylist);
@@ -49,8 +50,8 @@ App::init()
$route = $utopia->match($request);
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
$isAppUser = User::isApp(Authorization::getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$isAppUser = User::isApp($authorization->getRoles());
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
return;
+43
View File
@@ -0,0 +1,43 @@
<?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);
});
+21 -19
View File
@@ -12,8 +12,6 @@ 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;
@@ -27,7 +25,6 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Logger\Log;
use Utopia\Logger\Log\User;
use Utopia\Pools\Group;
@@ -261,10 +258,11 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
createDatabase($app, 'getLogsDB', 'logs', $collections['logs'], $pools);
// create appwrite database, `dbForPlatform` is a direct access call.
createDatabase($app, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections) {
if ($dbForPlatform->getCollection(AuditAdapterSQL::COLLECTION)->isEmpty()) {
$adapter = new AdapterDatabase($dbForPlatform);
$audit = new Audit($adapter);
createDatabase($app, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections, $app) {
$authorization = $app->getResource('authorization');
if ($dbForPlatform->getCollection(Audit::COLLECTION)->isEmpty()) {
$audit = new Audit($dbForPlatform);
$audit->setup();
}
@@ -321,9 +319,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
$dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes);
}
if (Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')->isEmpty())) {
if ($authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')->isEmpty())) {
Console::info(" └── Creating screenshots bucket...");
Authorization::skip(fn () => $dbForPlatform->createDocument('buckets', new Document([
$authorization->skip(fn () => $dbForPlatform->createDocument('buckets', new Document([
'$id' => ID::custom('screenshots'),
'$collection' => ID::custom('buckets'),
'name' => 'Screenshots',
@@ -338,7 +336,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
'search' => 'buckets Screenshots',
])));
$bucket = Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots'));
$bucket = $authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots'));
Console::info(" └── Creating files collection for screenshots bucket...");
$files = $collections['buckets']['files'] ?? [];
@@ -366,7 +364,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
'orders' => $index['orders'],
]), $files['indexes']);
Authorization::skip(fn () => $dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes));
$authorization->skip(fn () => $dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes));
}
});
@@ -392,9 +390,8 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
Console::success('[Setup] - Skip: metadata table already exists');
}
if ($dbForProject->getCollection(AuditAdapterSQL::COLLECTION)->isEmpty()) {
$adapter = new AdapterDatabase($dbForProject);
$audit = new Audit($adapter);
if ($dbForProject->getCollection(Audit::COLLECTION)->isEmpty()) {
$audit = new Audit($dbForProject);
$audit->setup();
}
@@ -458,8 +455,12 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
App::setResource('pools', fn () => $pools);
try {
Authorization::cleanRoles();
Authorization::setRole(Role::any()->toString());
$authorization = $app->getResource('authorization');
$request->setAuthorization($authorization);
$response->setAuthorization($authorization);
$authorization->cleanRoles();
$authorization->addRole(Role::any()->toString());
$app->run($request, $response);
} catch (\Throwable $th) {
@@ -501,7 +502,7 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
$log->addExtra('file', $th->getFile());
$log->addExtra('line', $th->getLine());
$log->addExtra('trace', $th->getTraceAsString());
$log->addExtra('roles', Authorization::getRoles());
$log->addExtra('roles', isset($authorization) ? $authorization->getRoles() : []);
$sdk = $route->getLabel("sdk", false);
@@ -560,7 +561,7 @@ $http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
/** @var Utopia\Database\Database $dbForPlatform */
$dbForPlatform = $app->getResource('dbForPlatform');
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $domains, &$lastSyncUpdate) {
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $domains, &$lastSyncUpdate, $app) {
try {
$time = DateTime::now();
$limit = 1000;
@@ -577,7 +578,8 @@ $http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
}
$results = [];
try {
$results = Authorization::skip(fn () => $dbForPlatform->find('rules', $queries));
$authorization = $app->getResource('authorization');
$results = $authorization->skip(fn () => $dbForPlatform->find('rules', $queries));
} catch (Throwable $th) {
Console::error($th->getMessage());
}
+24 -31
View File
@@ -4,7 +4,6 @@ use Appwrite\OpenSSL\OpenSSL;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\System\System;
Database::addFilter(
@@ -70,11 +69,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
$attributes = $database->find('attributes', [
$attributes = $database->getAuthorization()->skip(fn () => $database->find('attributes', [
Query::equal('collectionInternalId', [$document->getSequence()]),
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
Query::limit($database->getLimitForAttributes()),
]);
]));
foreach ($attributes as $attribute) {
$attributeType = $attribute->getAttribute('type');
@@ -105,12 +104,12 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database
return $database->getAuthorization()->skip(fn () => $database
->find('indexes', [
Query::equal('collectionInternalId', [$document->getSequence()]),
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
Query::limit($database->getLimitForIndexes()),
]);
]));
}
);
@@ -120,11 +119,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database
return $database->getAuthorization()->skip(fn () => $database
->find('platforms', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]);
]));
}
);
@@ -134,17 +133,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database
return $database->getAuthorization()->skip(fn () => $database
->find('keys', [
Query::or([
Query::equal('projectInternalId', [$document->getSequence()]),
Query::and([
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$document->getSequence()]),
])
]),
Query::equal('projectInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]);
]));
}
);
@@ -154,11 +147,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database
return $database->getAuthorization()->skip(fn () => $database
->find('devKeys', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]);
]));
}
);
@@ -168,11 +161,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database
return $database->getAuthorization()->skip(fn () => $database
->find('webhooks', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]);
]));
}
);
@@ -182,7 +175,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database->find('sessions', [
return $database->getAuthorization()->skip(fn () => $database->find('sessions', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
@@ -195,7 +188,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database
return $database->getAuthorization()->skip(fn () => $database
->find('tokens', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
@@ -209,7 +202,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database
return $database->getAuthorization()->skip(fn () => $database
->find('challenges', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
@@ -223,7 +216,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database
return $database->getAuthorization()->skip(fn () => $database
->find('authenticators', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
@@ -237,7 +230,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database
return $database->getAuthorization()->skip(fn () => $database
->find('memberships', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
@@ -257,14 +250,14 @@ Database::addFilter(
default => ['function', 'site']
};
return $database
return $database->getAuthorization()->skip(fn () => $database
->find('variables', [
Query::equal('resourceInternalId', [$document->getSequence()]),
Query::equal('resourceType', $resourceType),
Query::orderAsc('resourceType'),
Query::orderAsc(),
Query::limit(APP_LIMIT_SUBQUERY),
]);
]));
}
);
@@ -300,11 +293,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database
return $database->getAuthorization()->skip(fn () => $database
->find('variables', [
Query::equal('resourceType', ['project']),
Query::limit(APP_LIMIT_SUBQUERY)
]);
]));
}
);
@@ -337,7 +330,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return Authorization::skip(fn () => $database
return $database->getAuthorization()->skip(fn () => $database
->find('targets', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY)
@@ -351,7 +344,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
$targetIds = Authorization::skip(fn () => \array_map(
$targetIds = $database->getAuthorization()->skip(fn () => \array_map(
fn ($document) => $document->getAttribute('targetInternalId'),
$database->find('subscribers', [
Query::equal('topicInternalId', [$document->getSequence()]),
+60 -58
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 as AuditEvent;
use Appwrite\Event\Audit;
use Appwrite\Event\Build;
use Appwrite\Event\Certificate;
use Appwrite\Event\Database as EventDatabase;
@@ -30,8 +30,6 @@ 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;
@@ -148,7 +146,7 @@ App::setResource('queueForStatsUsage', function (Publisher $publisher) {
return new StatsUsage($publisher);
}, ['publisher']);
App::setResource('queueForAudits', function (Publisher $publisher) {
return new AuditEvent($publisher);
return new Audit($publisher);
}, ['publisher']);
App::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
@@ -226,7 +224,7 @@ App::setResource('allowedSchemes', function (Document $project) {
/**
* Rule associated with a request origin.
*/
App::setResource('rule', function (Request $request, Database $dbForPlatform, Document $project) {
App::setResource('rule', function (Request $request, Database $dbForPlatform, Document $project, Authorization $authorization) {
$domain = \parse_url($request->getOrigin(), PHP_URL_HOST);
if (empty($domain)) {
return new Document();
@@ -234,7 +232,7 @@ App::setResource('rule', function (Request $request, Database $dbForPlatform, Do
// TODO: (@Meldiron) Remove after 1.7.x migration
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
$rule = Authorization::skip(function () use ($dbForPlatform, $domain, $isMd5) {
$rule = $authorization->skip(function () use ($dbForPlatform, $domain, $isMd5) {
if ($isMd5) {
return $dbForPlatform->getDocument('rules', md5($domain));
}
@@ -249,7 +247,7 @@ App::setResource('rule', function (Request $request, Database $dbForPlatform, Do
}
return $rule;
}, ['request', 'dbForPlatform', 'project']);
}, ['request', 'dbForPlatform', 'project', 'authorization']);
/**
* CORS service
@@ -277,7 +275,6 @@ 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',
@@ -317,7 +314,7 @@ App::setResource('redirectValidator', function (Document $devKey, array $allowed
return new Redirect($allowedHostnames, $allowedSchemes);
}, ['devKey', 'allowedHostnames', 'allowedSchemes']);
App::setResource('user', function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken) {
App::setResource('user', function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken, $authorization) {
/**
* Handles user authentication and session validation.
*
@@ -337,7 +334,7 @@ App::setResource('user', function (string $mode, Document $project, Document $co
* overwriting the previous value.
*/
Authorization::setDefaultStatus(true);
$authorization->setDefaultStatus(true);
$store->setKey('a_session_' . $project->getId());
@@ -404,7 +401,7 @@ App::setResource('user', function (string $mode, Document $project, Document $co
}
// if (APP_MODE_ADMIN === $mode) {
// if ($user->find('teamInternalId', $project->getAttribute('teamInternalId'), 'memberships')) {
// Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
// $authorization->setDefaultStatus(false); // Cancel security segmentation for admin users.
// } else {
// $user = new Document([]);
// }
@@ -436,9 +433,9 @@ App::setResource('user', function (string $mode, Document $project, Document $co
$dbForPlatform->setMetadata('user', $user->getId());
return $user;
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken']);
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken', 'authorization']);
App::setResource('project', function ($dbForPlatform, $request, $console) {
App::setResource('project', function ($dbForPlatform, $request, $console, $authorization) {
/** @var Appwrite\Utopia\Request $request */
/** @var Utopia\Database\Database $dbForPlatform */
/** @var Utopia\Database\Document $console */
@@ -449,10 +446,10 @@ App::setResource('project', function ($dbForPlatform, $request, $console) {
return $console;
}
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
return $project;
}, ['dbForPlatform', 'request', 'console']);
}, ['dbForPlatform', 'request', 'console', 'authorization']);
App::setResource('session', function (User $user, Store $store, Token $proofForToken) {
if ($user->isEmpty()) {
@@ -475,10 +472,6 @@ App::setResource('session', function (User $user, Store $store, Token $proofForT
return;
}, ['user', 'store', 'proofForToken']);
App::setResource('console', function () {
return new Document(Config::getParam('console'));
}, []);
App::setResource('store', function (): Store {
return new Store();
});
@@ -509,7 +502,15 @@ App::setResource('proofForCode', function (): Code {
return $code;
});
App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project) {
App::setResource('console', function () {
return new Document(Config::getParam('console'));
}, []);
App::setResource('authorization', function () {
return new Authorization();
}, []);
App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project, Authorization $authorization) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
@@ -525,6 +526,7 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform
$database = new Database($adapter, $cache);
$database
->setAuthorization($authorization)
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId())
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
@@ -546,13 +548,15 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform
}
return $database;
}, ['pools', 'dbForPlatform', 'cache', 'project']);
}, ['pools', 'dbForPlatform', 'cache', 'project', 'authorization']);
App::setResource('dbForPlatform', function (Group $pools, Cache $cache, Authorization $authorization) {
App::setResource('dbForPlatform', function (Group $pools, Cache $cache) {
$adapter = new DatabasePool($pools->get('console'));
$database = new Database($adapter, $cache);
$database
->setAuthorization($authorization)
->setNamespace('_console')
->setMetadata('host', \gethostname())
->setMetadata('project', 'console')
@@ -562,12 +566,12 @@ App::setResource('dbForPlatform', function (Group $pools, Cache $cache) {
$database->setDocumentType('users', User::class);
return $database;
}, ['pools', 'cache']);
}, ['pools', 'cache', 'authorization']);
App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, Authorization $authorization) {
$databases = [];
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) {
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
@@ -579,13 +583,15 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
$configure = (function (Database $database) use ($project, $dsn) {
$configure = (function (Database $database) use ($project, $dsn, $authorization) {
$database
->setAuthorization($authorization)
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId())
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
$database->setDocumentType('users', User::class);
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES)
->setDocumentType('users', User::class)
;
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
@@ -615,12 +621,12 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
return $database;
};
}, ['pools', 'dbForPlatform', 'cache']);
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
App::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, &$database) {
return function (?Document $project = null) use ($pools, $cache, $authorization, &$database) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int) $project->getSequence());
return $database;
@@ -630,6 +636,7 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database = new Database($adapter, $cache);
$database
->setAuthorization($authorization)
->setSharedTables(true)
->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
@@ -642,12 +649,7 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
return $database;
};
}, ['pools', 'cache']);
App::setResource('audit', function ($dbForProject) {
$adapter = new AdapterDatabase($dbForProject);
return new Audit($adapter);
}, ['dbForProject']);
}, ['pools', 'cache', 'authorization']);
App::setResource('telemetry', fn () => new NoTelemetry());
@@ -841,7 +843,7 @@ App::setResource('promiseAdapter', function ($register) {
return $register->get('promiseAdapter');
}, ['register']);
App::setResource('schema', function ($utopia, $dbForProject) {
App::setResource('schema', function ($utopia, $dbForProject, $authorization) {
$complexity = function (int $complexity, array $args) {
$queries = Query::parseQueries($args['queries'] ?? []);
@@ -851,8 +853,8 @@ App::setResource('schema', function ($utopia, $dbForProject) {
return $complexity * $limit;
};
$attributes = function (int $limit, int $offset) use ($dbForProject) {
$attrs = Authorization::skip(fn () => $dbForProject->find('attributes', [
$attributes = function (int $limit, int $offset) use ($dbForProject, $authorization) {
$attrs = $authorization->skip(fn () => $dbForProject->find('attributes', [
Query::limit($limit),
Query::offset($offset),
]));
@@ -926,7 +928,7 @@ App::setResource('schema', function ($utopia, $dbForProject) {
$urls,
$params,
);
}, ['utopia', 'dbForProject']);
}, ['utopia', 'dbForProject', 'authorization']);
App::setResource('gitHub', function (Cache $cache) {
return new VcsGitHub($cache);
@@ -954,7 +956,7 @@ App::setResource('smsRates', function () {
return [];
});
App::setResource('devKey', function (Request $request, Document $project, array $servers, Database $dbForPlatform) {
App::setResource('devKey', function (Request $request, Document $project, array $servers, Database $dbForPlatform, Authorization $authorization) {
$devKey = $request->getHeader('x-appwrite-dev-key', $request->getParam('devKey', ''));
// Check if given key match project's development keys
@@ -973,7 +975,7 @@ App::setResource('devKey', function (Request $request, Document $project, array
$accessedAt = $key->getAttribute('accessedAt', 0);
if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
$key->setAttribute('accessedAt', DatabaseDateTime::now());
Authorization::skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), $key));
$authorization->skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), $key));
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
}
@@ -990,15 +992,15 @@ App::setResource('devKey', function (Request $request, Document $project, array
/** Update access time as well */
$key->setAttribute('accessedAt', DatabaseDateTime::now());
$key = Authorization::skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), $key));
$key = $authorization->skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), $key));
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
}
}
return $key;
}, ['request', 'project', 'servers', 'dbForPlatform']);
}, ['request', 'project', 'servers', 'dbForPlatform', 'authorization']);
App::setResource('team', function (Document $project, Database $dbForPlatform, App $utopia, Request $request) {
App::setResource('team', function (Document $project, Database $dbForPlatform, App $utopia, Request $request, Authorization $authorization) {
$teamInternalId = '';
if ($project->getId() !== 'console') {
$teamInternalId = $project->getAttribute('teamInternalId', '');
@@ -1008,7 +1010,7 @@ App::setResource('team', function (Document $project, Database $dbForPlatform, A
if (str_starts_with($path, '/v1/projects/:projectId')) {
$uri = $request->getURI();
$pid = explode('/', $uri)[3];
$p = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $pid));
$p = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $pid));
$teamInternalId = $p->getAttribute('teamInternalId', '');
} elseif ($path === '/v1/projects') {
$teamId = $request->getParam('teamId', '');
@@ -1017,7 +1019,7 @@ App::setResource('team', function (Document $project, Database $dbForPlatform, A
return new Document([]);
}
$team = Authorization::skip(fn () => $dbForPlatform->getDocument('teams', $teamId));
$team = $authorization->skip(fn () => $dbForPlatform->getDocument('teams', $teamId));
return $team;
}
}
@@ -1026,14 +1028,14 @@ App::setResource('team', function (Document $project, Database $dbForPlatform, A
return new Document([]);
}
$team = Authorization::skip(function () use ($dbForPlatform, $teamInternalId) {
$team = $authorization->skip(function () use ($dbForPlatform, $teamInternalId) {
return $dbForPlatform->findOne('teams', [
Query::equal('$sequence', [$teamInternalId]),
]);
});
return $team;
}, ['project', 'dbForPlatform', 'utopia', 'request']);
}, ['project', 'dbForPlatform', 'utopia', 'request', 'authorization']);
App::setResource(
'isResourceBlocked',
@@ -1071,7 +1073,7 @@ App::setResource('apiKey', function (Request $request, Document $project): ?Key
App::setResource('executor', fn () => new Executor());
App::setResource('resourceToken', function ($project, $dbForProject, $request) {
App::setResource('resourceToken', function ($project, $dbForProject, $request, Authorization $authorization) {
$tokenJWT = $request->getParam('token');
if (!empty($tokenJWT) && !$project->isEmpty()) { // JWT authentication
@@ -1089,7 +1091,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
return new Document([]);
}
$token = Authorization::skip(fn () => $dbForProject->getDocument('resourceTokens', $tokenId));
$token = $authorization->skip(fn () => $dbForProject->getDocument('resourceTokens', $tokenId));
if ($token->isEmpty()) {
return new Document([]);
@@ -1107,7 +1109,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
}
return match ($token->getAttribute('resourceType')) {
TOKENS_RESOURCE_TYPE_FILES => (function () use ($token, $dbForProject) {
TOKENS_RESOURCE_TYPE_FILES => (function () use ($token, $dbForProject, $authorization) {
$sequences = explode(':', $token->getAttribute('resourceInternalId'));
$ids = explode(':', $token->getAttribute('resourceId'));
@@ -1118,7 +1120,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
$accessedAt = $token->getAttribute('accessedAt', 0);
if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), -APP_RESOURCE_TOKEN_ACCESS)) > $accessedAt) {
$token->setAttribute('accessedAt', DatabaseDateTime::now());
Authorization::skip(fn () => $dbForProject->updateDocument('resourceTokens', $token->getId(), $token));
$authorization->skip(fn () => $dbForProject->updateDocument('resourceTokens', $token->getId(), $token));
}
return new Document([
@@ -1133,8 +1135,8 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
};
}
return new Document([]);
}, ['project', 'dbForProject', 'request']);
}, ['project', 'dbForProject', 'request', 'authorization']);
App::setResource('transactionState', function (Database $dbForProject) {
return new TransactionState($dbForProject);
}, ['dbForProject']);
App::setResource('transactionState', function (Database $dbForProject, Authorization $authorization) {
return new TransactionState($dbForProject, $authorization);
}, ['dbForProject', 'authorization']);
+30 -11
View File
@@ -32,7 +32,6 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\DSN\DSN;
use Utopia\Logger\Log;
use Utopia\Pools\Group;
@@ -309,7 +308,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
'value' => '{}'
]);
$statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document));
$statsDocument = $database->getAuthorization()->skip(fn () => $database->createDocument('realtime', $document));
break;
} catch (Throwable) {
Console::warning("Collection not ready. Retrying connection ({$attempts})...");
@@ -339,7 +338,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
->setAttribute('timestamp', DateTime::now())
->setAttribute('value', json_encode($payload));
Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
$database->getAuthorization()->skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
} catch (Throwable $th) {
$logError($th, "updateWorkerDocument");
}
@@ -370,7 +369,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
$payload = [];
$list = Authorization::skip(fn () => $database->find('realtime', [
$list = $database->getAuthorization()->skip(fn () => $database->find('realtime', [
Query::greaterThan('timestamp', DateTime::addSeconds(new \DateTime(), -15)),
]));
@@ -464,13 +463,13 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
if ($realtime->hasSubscriber($projectId, 'user:' . $userId)) {
$connection = array_key_first(reset($realtime->subscriptions[$projectId]['user:' . $userId]));
$consoleDatabase = getConsoleDB();
$project = Authorization::skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
$project = $consoleDatabase->getAuthorization()->skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
$database = getProjectDB($project);
/** @var Appwrite\Utopia\Database\Documents\User $user */
$user = $database->getDocument('users', $userId);
$roles = $user->getRoles();
$roles = $user->getRoles($database->getAuthorization());
$channels = $realtime->connections[$connection]['channels'];
$realtime->unsubscribe($connection);
@@ -526,6 +525,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
try {
/** @var Document $project */
$project = $app->getResource('project');
$authorization = $app->getResource('authorization');
/*
* Project Check
@@ -537,7 +537,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
if (
array_key_exists('realtime', $project->getAttribute('apis', []))
&& !$project->getAttribute('apis', [])['realtime']
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles()))
&& !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
}
@@ -573,7 +573,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
}
$roles = $user->getRoles();
$roles = $user->getRoles($authorization);
$channels = Realtime::convertChannels($request->getQuery('channels', []), $user->getId());
@@ -586,6 +586,8 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$realtime->subscribe($project->getId(), $connection, $roles, $channels);
$realtime->connections[$connection]['authorization'] = $authorization;
$user = empty($user->getId()) ? null : $response->output($user, Response::MODEL_ACCOUNT);
$server->send([$connection], json_encode([
@@ -614,6 +616,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$code = 500;
}
$message = $th->getMessage();
// sanitize 0 && 5xx errors
@@ -643,12 +646,19 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) {
try {
$response = new Response(new SwooleResponse());
$projectId = $realtime->connections[$connection]['projectId'];
$projectId = $realtime->connections[$connection]['projectId'] ?? null;
// Get authorization from connection (stored during onOpen)
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
$database = getConsoleDB();
$database->setAuthorization($authorization);
if ($projectId !== 'console') {
$project = Authorization::skip(fn () => $database->getDocument('projects', $projectId));
$project = $authorization->skip(fn () => $database->getDocument('projects', $projectId));
$database = getProjectDB($project);
$database->setAuthorization($authorization);
} else {
$project = null;
}
@@ -712,10 +722,19 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.');
}
$roles = $user->getRoles();
$roles = $user->getRoles($database->getAuthorization());
$channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId());
// Preserve authorization before subscribe overwrites the connection array
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
$realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels);
// Restore authorization after subscribe
if ($authorization !== null) {
$realtime->connections[$connection]['authorization'] = $authorization;
}
$user = $response->output($user, Response::MODEL_ACCOUNT);
$server->send([$connection], json_encode([
'type' => 'response',
-3
View File
@@ -11,7 +11,6 @@ $httpsPort = $this->getParam('httpsPort', '');
$version = $this->getParam('version', '');
$organization = $this->getParam('organization', '');
$image = $this->getParam('image', '');
$enableAssistant = $this->getParam('enableAssistant', false);
?>services:
traefik:
image: traefik:2.11
@@ -849,7 +848,6 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- _APP_DB_USER
- _APP_DB_PASS
<?php if ($enableAssistant): ?>
appwrite-assistant:
image: appwrite/assistant:0.8.4
container_name: appwrite-assistant
@@ -859,7 +857,6 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- appwrite
environment:
- _APP_ASSISTANT_OPENAI_API_KEY
<?php endif; ?>
appwrite-browser:
image: appwrite/browser:0.3.2
+35 -33
View File
@@ -21,8 +21,6 @@ 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;
@@ -48,19 +46,30 @@ use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry;
use Utopia\Telemetry\Adapter\None as NoTelemetry;
Authorization::disable();
Runtime::enableCoroutine();
Server::setResource('register', fn () => $register);
Server::setResource('dbForPlatform', function (Cache $cache, Registry $register) {
Server::setResource('authorization', function () {
$authorization = new Authorization();
$authorization->disable();
return $authorization;
}, []);
Server::setResource('dbForPlatform', function (Cache $cache, Registry $register, Authorization $authorization) {
$pools = $register->get('pools');
$adapter = new DatabasePool($pools->get('console'));
$dbForPlatform = new Database($adapter, $cache);
$dbForPlatform->setNamespace('_console');
$dbForPlatform->setDocumentType('users', User::class);
$dbForPlatform
->setAuthorization($authorization)
->setNamespace('_console')
->setDocumentType('users', User::class)
;
return $dbForPlatform;
}, ['cache', 'register']);
}, ['cache', 'register', 'authorization']);
Server::setResource('project', function (Message $message, Database $dbForPlatform) {
$payload = $message->getPayload() ?? [];
@@ -73,7 +82,7 @@ Server::setResource('project', function (Message $message, Database $dbForPlatfo
return $dbForPlatform->getDocument('projects', $project->getId());
}, ['message', 'dbForPlatform']);
Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForPlatform) {
Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForPlatform, Authorization $authorization) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
@@ -105,15 +114,17 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
->setNamespace('_' . $project->getSequence());
}
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
$database
->setAuthorization($authorization)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database;
}, ['cache', 'register', 'message', 'project', 'dbForPlatform']);
}, ['cache', 'register', 'message', 'project', 'dbForPlatform', 'authorization']);
Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, Authorization $authorization) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases): Database {
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases): Database {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
@@ -127,7 +138,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
if (isset($databases[$dsn->getHost()])) {
$database = $databases[$dsn->getHost()];
$database->setAuthorization($authorization);
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
if (\in_array($dsn->getHost(), $sharedTables)) {
@@ -164,15 +175,17 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
->setNamespace('_' . $project->getSequence());
}
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
$database
->setAuthorization($authorization)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database;
};
}, ['pools', 'dbForPlatform', 'cache']);
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $database) {
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int)$project->getSequence());
return $database;
@@ -182,6 +195,7 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database = new Database($adapter, $cache);
$database
->setAuthorization($authorization)
->setSharedTables(true)
->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER)
@@ -194,7 +208,7 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
return $database;
};
}, ['pools', 'cache']);
}, ['pools', 'cache', 'authorization']);
Server::setResource('abuseRetention', function () {
return time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400); // 1 day
@@ -452,19 +466,6 @@ Server::setResource('logError', function (Registry $register, Document $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');
@@ -509,7 +510,8 @@ $worker
->inject('log')
->inject('pools')
->inject('project')
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project) use ($worker, $queueName) {
->inject('authorization')
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project, Authorization $authorization) use ($worker, $queueName) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
if ($logger) {
@@ -525,7 +527,7 @@ $worker
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('roles', Authorization::getRoles());
$log->addExtra('roles', $authorization->getRoles());
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php doctor "$@"
php /usr/src/code/app/cli.php doctor $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php install "$@"
php /usr/src/code/app/cli.php install $@
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php interval "$@"
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php maintenance "$@"
php /usr/src/code/app/cli.php maintenance $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php migrate "$@"
php /usr/src/code/app/cli.php migrate $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php queue-count --type=failed "$@"
php /usr/src/code/app/cli.php queue-count --type=failed $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php queue-count --type=processing "$@"
php /usr/src/code/app/cli.php queue-count --type=processing $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php queue-count --type=success "$@"
php /usr/src/code/app/cli.php queue-count --type=success $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php queue-retry "$@"
php /usr/src/code/app/cli.php queue-retry $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/realtime.php "$@"
php /usr/src/code/app/realtime.php $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php schedule-executions "$@"
php /usr/src/code/app/cli.php schedule-executions $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php schedule-functions "$@"
php /usr/src/code/app/cli.php schedule-functions $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php schedule-messages "$@"
php /usr/src/code/app/cli.php schedule-messages $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php screenshot "$@"
php /usr/src/code/app/cli.php screenshot $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php sdks "$@"
php /usr/src/code/app/cli.php sdks $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php specs "$@"
php /usr/src/code/app/cli.php specs $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php ssl "$@"
php /usr/src/code/app/cli.php ssl $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php stats-resources "$@"
php /usr/src/code/app/cli.php stats-resources $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec /usr/src/code/vendor/bin/phpunit --configuration /usr/src/code/phpunit.xml "$@"
/usr/src/code/vendor/bin/phpunit --configuration /usr/src/code/phpunit.xml $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php upgrade "$@"
php /usr/src/code/app/cli.php upgrade $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/cli.php vars "$@"
php /usr/src/code/app/cli.php vars $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php audits "$@"
php /usr/src/code/app/worker.php audits $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php builds "$@"
php /usr/src/code/app/worker.php builds $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php certificates "$@"
php /usr/src/code/app/worker.php certificates $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php databases "$@"
php /usr/src/code/app/worker.php databases $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php deletes "$@"
php /usr/src/code/app/worker.php deletes $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php functions "$@"
php /usr/src/code/app/worker.php functions $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php mails "$@"
php /usr/src/code/app/worker.php mails $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php messaging "$@"
php /usr/src/code/app/worker.php messaging $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php migrations "$@"
php /usr/src/code/app/worker.php migrations $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php stats-resources "$@"
php /usr/src/code/app/worker.php stats-resources $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php stats-usage "$@"
php /usr/src/code/app/worker.php stats-usage $@
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php webhooks "$@"
php /usr/src/code/app/worker.php webhooks $@
+4 -4
View File
@@ -47,12 +47,12 @@
"appwrite/php-clamav": "2.0.*",
"utopia-php/abuse": "1.*",
"utopia-php/analytics": "0.10.*",
"utopia-php/audit": "2.0.2-rc1",
"utopia-php/audit": "1.*",
"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": "dev-joins8 as 4.3.0",
"utopia-php/detector": "0.2.*",
"utopia-php/domains": "0.9.*",
"utopia-php/emails": "0.6.*",
@@ -64,7 +64,7 @@
"utopia-php/locale": "0.8.*",
"utopia-php/logger": "0.6.*",
"utopia-php/messaging": "0.20.*",
"utopia-php/migration": "1.3.*",
"utopia-php/migration": "dev-joins as 1.3.5",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.7.*",
"utopia-php/pools": "0.8.*",
@@ -109,4 +109,4 @@
"tbachert/spi": true
}
}
}
}
Generated
+75 -64
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": "b873febd2b03c32ec61a57b690cc44a2",
"content-hash": "fe779980651e51a4cb8cde7515d14db1",
"packages": [
{
"name": "adhocore/jwt",
@@ -756,16 +756,16 @@
},
{
"name": "google/protobuf",
"version": "v4.33.2",
"version": "v4.33.3",
"source": {
"type": "git",
"url": "https://github.com/protocolbuffers/protobuf-php.git",
"reference": "fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318"
"reference": "281537d44d6c270606354e65bfa75a0969dbd629"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318",
"reference": "fbd96b7bf1343f4b0d8fb358526c7ba4d72f1318",
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/281537d44d6c270606354e65bfa75a0969dbd629",
"reference": "281537d44d6c270606354e65bfa75a0969dbd629",
"shasum": ""
},
"require": {
@@ -794,9 +794,9 @@
"proto"
],
"support": {
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.2"
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.3"
},
"time": "2025-12-05T22:12:22+00:00"
"time": "2026-01-09T21:49:37+00:00"
},
{
"name": "league/csv",
@@ -3552,23 +3552,21 @@
},
{
"name": "utopia-php/audit",
"version": "2.0.2-rc1",
"version": "1.0.3",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/audit.git",
"reference": "7b35dab40bce66bda56eeeacd2bbcbf1e823f05f"
"reference": "15656acfddb9d6f03c395b73673fc66c793c10a5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/7b35dab40bce66bda56eeeacd2bbcbf1e823f05f",
"reference": "7b35dab40bce66bda56eeeacd2bbcbf1e823f05f",
"url": "https://api.github.com/repos/utopia-php/audit/zipball/15656acfddb9d6f03c395b73673fc66c793c10a5",
"reference": "15656acfddb9d6f03c395b73673fc66c793c10a5",
"shasum": ""
},
"require": {
"php": ">=8.0",
"utopia-php/database": "3.*",
"utopia-php/fetch": "^0.4.2",
"utopia-php/validators": "^0.1.0"
"utopia-php/database": "4.*"
},
"require-dev": {
"laravel/pint": "1.*",
@@ -3595,9 +3593,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/audit/issues",
"source": "https://github.com/utopia-php/audit/tree/2.0.2-rc1"
"source": "https://github.com/utopia-php/audit/tree/1.0.3"
},
"time": "2025-12-24T01:20:43+00:00"
"time": "2025-11-04T11:27:42+00:00"
},
{
"name": "utopia-php/auth",
@@ -3898,16 +3896,16 @@
},
{
"name": "utopia-php/database",
"version": "3.6.1",
"version": "dev-joins8",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "c8c1b2f5770245dd4006e2680681e3efbe8b1fa7"
"reference": "467a9ea2aed836e83fc435f0379d177c16f8e45c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/c8c1b2f5770245dd4006e2680681e3efbe8b1fa7",
"reference": "c8c1b2f5770245dd4006e2680681e3efbe8b1fa7",
"url": "https://api.github.com/repos/utopia-php/database/zipball/467a9ea2aed836e83fc435f0379d177c16f8e45c",
"reference": "467a9ea2aed836e83fc435f0379d177c16f8e45c",
"shasum": ""
},
"require": {
@@ -3950,9 +3948,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/3.6.1"
"source": "https://github.com/utopia-php/database/tree/joins8"
},
"time": "2025-12-16T09:55:41+00:00"
"time": "2025-12-25T08:22:28+00:00"
},
{
"name": "utopia-php/detector",
@@ -4515,16 +4513,16 @@
},
{
"name": "utopia-php/migration",
"version": "1.3.9",
"version": "dev-joins",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/migration.git",
"reference": "c55ec67c74663190cda10fd79297422147be7e85"
"reference": "01e06cf23b120b20eff6b159bc6531dd412677d8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/c55ec67c74663190cda10fd79297422147be7e85",
"reference": "c55ec67c74663190cda10fd79297422147be7e85",
"url": "https://api.github.com/repos/utopia-php/migration/zipball/01e06cf23b120b20eff6b159bc6531dd412677d8",
"reference": "01e06cf23b120b20eff6b159bc6531dd412677d8",
"shasum": ""
},
"require": {
@@ -4533,7 +4531,7 @@
"ext-openssl": "*",
"php": ">=8.1",
"utopia-php/console": "0.0.*",
"utopia-php/database": "3.*",
"utopia-php/database": "dev-joins8 as 4.3.0",
"utopia-php/dsn": "0.2.*",
"utopia-php/storage": "0.18.*"
},
@@ -4564,9 +4562,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/migration/issues",
"source": "https://github.com/utopia-php/migration/tree/1.3.9"
"source": "https://github.com/utopia-php/migration/tree/joins"
},
"time": "2025-12-08T08:45:09+00:00"
"time": "2025-12-23T05:59:18+00:00"
},
{
"name": "utopia-php/mongo",
@@ -5438,16 +5436,16 @@
"packages-dev": [
{
"name": "appwrite/sdk-generator",
"version": "1.8.6",
"version": "1.8.10",
"source": {
"type": "git",
"url": "https://github.com/appwrite/sdk-generator.git",
"reference": "b6cc29d3bd247e193f3c06b4168dc69d884645f0"
"reference": "28217b1d722ad2b217559c3a371eafd4d070614d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/b6cc29d3bd247e193f3c06b4168dc69d884645f0",
"reference": "b6cc29d3bd247e193f3c06b4168dc69d884645f0",
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/28217b1d722ad2b217559c3a371eafd4d070614d",
"reference": "28217b1d722ad2b217559c3a371eafd4d070614d",
"shasum": ""
},
"require": {
@@ -5483,9 +5481,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.8.6"
"source": "https://github.com/appwrite/sdk-generator/tree/1.8.10"
},
"time": "2025-12-31T10:22:17+00:00"
"time": "2026-01-09T11:40:35+00:00"
},
{
"name": "doctrine/annotations",
@@ -5566,30 +5564,29 @@
},
{
"name": "doctrine/instantiator",
"version": "2.0.0",
"version": "2.1.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
"reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0"
"reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0",
"reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/23da848e1a2308728fe5fdddabf4be17ff9720c7",
"reference": "23da848e1a2308728fe5fdddabf4be17ff9720c7",
"shasum": ""
},
"require": {
"php": "^8.1"
"php": "^8.4"
},
"require-dev": {
"doctrine/coding-standard": "^11",
"doctrine/coding-standard": "^14",
"ext-pdo": "*",
"ext-phar": "*",
"phpbench/phpbench": "^1.2",
"phpstan/phpstan": "^1.9.4",
"phpstan/phpstan-phpunit": "^1.3",
"phpunit/phpunit": "^9.5.27",
"vimeo/psalm": "^5.4"
"phpstan/phpstan": "^2.1",
"phpstan/phpstan-phpunit": "^2.0",
"phpunit/phpunit": "^10.5.58"
},
"type": "library",
"autoload": {
@@ -5616,7 +5613,7 @@
],
"support": {
"issues": "https://github.com/doctrine/instantiator/issues",
"source": "https://github.com/doctrine/instantiator/tree/2.0.0"
"source": "https://github.com/doctrine/instantiator/tree/2.1.0"
},
"funding": [
{
@@ -5632,7 +5629,7 @@
"type": "tidelift"
}
],
"time": "2022-12-30T00:23:10+00:00"
"time": "2026-01-05T06:47:08+00:00"
},
{
"name": "doctrine/lexer",
@@ -5713,16 +5710,16 @@
},
{
"name": "laravel/pint",
"version": "v1.26.0",
"version": "v1.27.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/pint.git",
"reference": "69dcca060ecb15e4b564af63d1f642c81a241d6f"
"reference": "c67b4195b75491e4dfc6b00b1c78b68d86f54c90"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pint/zipball/69dcca060ecb15e4b564af63d1f642c81a241d6f",
"reference": "69dcca060ecb15e4b564af63d1f642c81a241d6f",
"url": "https://api.github.com/repos/laravel/pint/zipball/c67b4195b75491e4dfc6b00b1c78b68d86f54c90",
"reference": "c67b4195b75491e4dfc6b00b1c78b68d86f54c90",
"shasum": ""
},
"require": {
@@ -5733,9 +5730,9 @@
"php": "^8.2.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.90.0",
"illuminate/view": "^12.40.1",
"larastan/larastan": "^3.8.0",
"friendsofphp/php-cs-fixer": "^3.92.4",
"illuminate/view": "^12.44.0",
"larastan/larastan": "^3.8.1",
"laravel-zero/framework": "^12.0.4",
"mockery/mockery": "^1.6.12",
"nunomaduro/termwind": "^2.3.3",
@@ -5776,7 +5773,7 @@
"issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint"
},
"time": "2025-11-25T21:15:52+00:00"
"time": "2026-01-05T16:49:17+00:00"
},
{
"name": "matthiasmullie/minify",
@@ -8562,16 +8559,16 @@
},
{
"name": "symfony/process",
"version": "v8.0.0",
"version": "v8.0.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "a0a750500c4ce900d69ba4e9faf16f82c10ee149"
"reference": "0cbbd88ec836f8757641c651bb995335846abb78"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/a0a750500c4ce900d69ba4e9faf16f82c10ee149",
"reference": "a0a750500c4ce900d69ba4e9faf16f82c10ee149",
"url": "https://api.github.com/repos/symfony/process/zipball/0cbbd88ec836f8757641c651bb995335846abb78",
"reference": "0cbbd88ec836f8757641c651bb995335846abb78",
"shasum": ""
},
"require": {
@@ -8603,7 +8600,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v8.0.0"
"source": "https://github.com/symfony/process/tree/v8.0.3"
},
"funding": [
{
@@ -8623,7 +8620,7 @@
"type": "tidelift"
}
],
"time": "2025-10-16T16:25:44+00:00"
"time": "2025-12-19T10:01:18+00:00"
},
{
"name": "symfony/string",
@@ -8943,10 +8940,24 @@
"time": "2024-03-07T20:33:40+00:00"
}
],
"aliases": [],
"aliases": [
{
"package": "utopia-php/database",
"version": "dev-joins8",
"alias": "4.3.0",
"alias_normalized": "4.3.0.0"
},
{
"package": "utopia-php/migration",
"version": "dev-joins",
"alias": "1.3.5",
"alias_normalized": "1.3.5.0"
}
],
"minimum-stability": "stable",
"stability-flags": {
"utopia-php/audit": 5
"utopia-php/database": 20,
"utopia-php/migration": 20
},
"prefer-stable": false,
"prefer-lowest": false,
@@ -8971,5 +8982,5 @@
"platform-overrides": {
"php": "8.3"
},
"plugin-api-version": "2.9.0"
"plugin-api-version": "2.6.0"
}
+21 -42
View File
@@ -555,7 +555,6 @@ services:
- _APP_DOMAIN_TARGET_CAA
- _APP_DNS
- _APP_DOMAIN_FUNCTIONS
- _APP_DOMAIN_SITES
- _APP_EMAIL_CERTIFICATES
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -754,7 +753,6 @@ services:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- mariadb
- redis
environment:
- _APP_ENV
@@ -787,43 +785,6 @@ 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
@@ -1067,6 +1028,27 @@ 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
@@ -1149,9 +1131,6 @@ 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
+1 -1
View File
@@ -152,7 +152,7 @@ As the name implies, `param()` is used to define a request parameter.
```php
App::get('/v1/account/logs')
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->param('queries', [], new Types([Query::TYPE_LIMIT, Query::TYPE_OFFSET]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
```
### 6. inject
+1 -1
View File
@@ -6,7 +6,7 @@
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
stopOnFailure="true"
>
<extensions>
<extension class="Appwrite\Tests\TestHook" />
-4
View File
@@ -8,10 +8,6 @@ interface Adapter
{
public function issueCertificate(string $certName, string $domain, ?string $domainType): ?string;
public function isInstantGeneration(string $domain, ?string $domainType): bool;
public function getCertificateStatus(string $domain, ?string $domainType): string;
public function isRenewRequired(string $domain, ?string $domainType, Log $log): bool;
public function deleteCertificate(string $domain): void;
@@ -1,10 +0,0 @@
<?php
namespace Appwrite\Certificates\Exception;
use Exception;
// Exception thrown during certificate status retrieval
class CertificateStatus extends Exception
{
}
-11
View File
@@ -2,7 +2,6 @@
namespace Appwrite\Certificates;
use Appwrite\Certificates\Exception\CertificateStatus as CertificateStatusException;
use Exception;
use Utopia\App;
use Utopia\CLI\Console;
@@ -85,16 +84,6 @@ class LetsEncrypt implements Adapter
return DateTime::addSeconds($dt, -60 * 60 * 24 * 30);
}
public function isInstantGeneration(string $domain, ?string $domainType): bool
{
return true;
}
public function getCertificateStatus(string $domain, ?string $domainType): string
{
throw new CertificateStatusException('Certificate status retrieval is not supported for LetsEncrypt.');
}
public function isRenewRequired(string $domain, ?string $domainType, Log $log): bool
{
$certPath = APP_STORAGE_CERTIFICATES . '/' . $domain . '/cert.pem';
+6 -4
View File
@@ -20,10 +20,12 @@ use Utopia\Database\Validator\Authorization;
class TransactionState
{
private Database $dbForProject;
public function __construct(Database $dbForProject)
private Authorization $authorization;
/** @var Authorization $authorization */
public function __construct(Database $dbForProject, Authorization $authorization)
{
$this->dbForProject = $dbForProject;
$this->authorization = $authorization;
}
@@ -342,12 +344,12 @@ class TransactionState
*/
private function getTransactionState(string $transactionId): array
{
$transaction = Authorization::skip(fn () => $this->dbForProject->getDocument('transactions', $transactionId));
$transaction = $this->authorization->skip(fn () => $this->dbForProject->getDocument('transactions', $transactionId));
if ($transaction->isEmpty() || $transaction->getAttribute('status') !== 'pending') {
return [];
}
$operations = Authorization::skip(fn () => $this->dbForProject->find('transactionLogs', [
$operations = $this->authorization->skip(fn () => $this->dbForProject->find('transactionLogs', [
Query::equal('transactionInternalId', [$transaction->getSequence()]),
Query::orderAsc(),
Query::limit(PHP_INT_MAX)
+2 -3
View File
@@ -3,7 +3,6 @@
namespace Appwrite\Event;
use Utopia\Queue\Publisher;
use Utopia\System\System;
class Audit extends Event
{
@@ -20,8 +19,8 @@ class Audit extends Event
parent::__construct($publisher);
$this
->setQueue(System::getEnv('_APP_AUDITS_QUEUE_NAME', Event::AUDITS_QUEUE_NAME))
->setClass(System::getEnv('_APP_AUDITS_CLASS_NAME', Event::AUDITS_CLASS_NAME));
->setQueue(Event::AUDITS_QUEUE_NAME)
->setClass(Event::AUDITS_CLASS_NAME);
}
/**
+2 -3
View File
@@ -5,7 +5,6 @@ namespace Appwrite\Event;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Queue\Publisher;
use Utopia\System\System;
class Build extends Event
{
@@ -19,8 +18,8 @@ class Build extends Event
parent::__construct($publisher);
$this
->setQueue(System::getEnv('_APP_BUILDS_QUEUE_NAME', Event::BUILDS_QUEUE_NAME))
->setClass(System::getEnv('_APP_BUILDS_CLASS_NAME', Event::BUILDS_CLASS_NAME));
->setQueue(Event::BUILDS_QUEUE_NAME)
->setClass(Event::BUILDS_CLASS_NAME);
}
/**
+3 -30
View File
@@ -4,14 +4,10 @@ namespace Appwrite\Event;
use Utopia\Database\Document;
use Utopia\Queue\Publisher;
use Utopia\System\System;
class Certificate extends Event
{
public const string ACTION_DOMAIN_VERIFICATION = 'verification';
public const string ACTION_GENERATION = 'generation';
protected bool $skipRenewCheck = false;
protected string $action = self::ACTION_GENERATION;
protected ?Document $domain = null;
protected ?string $validationDomain = null;
@@ -20,8 +16,8 @@ class Certificate extends Event
parent::__construct($publisher);
$this
->setQueue(System::getEnv('_APP_CERTIFICATES_QUEUE_NAME', Event::CERTIFICATES_QUEUE_NAME))
->setClass(System::getEnv('_APP_CERTIFICATES_CLASS_NAME', Event::CERTIFICATES_CLASS_NAME));
->setQueue(Event::CERTIFICATES_QUEUE_NAME)
->setClass(Event::CERTIFICATES_CLASS_NAME);
}
/**
@@ -94,28 +90,6 @@ class Certificate extends Event
return $this->skipRenewCheck;
}
/**
* Set action for this certificate event.
*
* @param string $action
* @return self
*/
public function setAction(string $action): self
{
$this->action = $action;
return $this;
}
/**
* Get action for this certificate event.
*
* @return string
*/
public function getAction(): string
{
return $this->action;
}
/**
* Prepare the payload for the event
@@ -128,8 +102,7 @@ class Certificate extends Event
'project' => $this->project,
'domain' => $this->domain,
'skipRenewCheck' => $this->skipRenewCheck,
'validationDomain' => $this->validationDomain,
'action' => $this->action
'validationDomain' => $this->validationDomain
];
}
}
+1 -2
View File
@@ -5,7 +5,6 @@ namespace Appwrite\Event;
use Utopia\Database\Document;
use Utopia\DSN\DSN;
use Utopia\Queue\Publisher;
use Utopia\System\System;
class Database extends Event
{
@@ -25,7 +24,7 @@ class Database extends Event
{
parent::__construct($publisher);
$this->setClass(System::getEnv('_APP_DATABASE_CLASS_NAME', Event::DATABASE_CLASS_NAME));
$this->setClass(Event::DATABASE_CLASS_NAME);
}
/**
+2 -3
View File
@@ -4,7 +4,6 @@ namespace Appwrite\Event;
use Utopia\Database\Document;
use Utopia\Queue\Publisher;
use Utopia\System\System;
class Delete extends Event
{
@@ -21,8 +20,8 @@ class Delete extends Event
parent::__construct($publisher);
$this
->setQueue(System::getEnv('_APP_DELETE_QUEUE_NAME', Event::DELETE_QUEUE_NAME))
->setClass(System::getEnv('_APP_DELETE_CLASS_NAME', Event::DELETE_CLASS_NAME));
->setQueue(Event::DELETE_QUEUE_NAME)
->setClass(Event::DELETE_CLASS_NAME);
}
/**
+2 -3
View File
@@ -5,7 +5,6 @@ namespace Appwrite\Event;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Queue\Publisher;
use Utopia\System\System;
class Func extends Event
{
@@ -26,8 +25,8 @@ class Func extends Event
parent::__construct($publisher);
$this
->setQueue(System::getEnv('_APP_FUNCTIONS_QUEUE_NAME', Event::FUNCTIONS_QUEUE_NAME))
->setClass(System::getEnv('_APP_FUNCTIONS_CLASS_NAME', Event::FUNCTIONS_CLASS_NAME));
->setQueue(Event::FUNCTIONS_QUEUE_NAME)
->setClass(Event::FUNCTIONS_CLASS_NAME);
}
/**
+2 -3
View File
@@ -4,7 +4,6 @@ namespace Appwrite\Event;
use Utopia\Config\Config;
use Utopia\Queue\Publisher;
use Utopia\System\System;
class Mail extends Event
{
@@ -25,8 +24,8 @@ class Mail extends Event
parent::__construct($publisher);
$this
->setQueue(System::getEnv('_APP_MAILS_QUEUE_NAME', Event::MAILS_QUEUE_NAME))
->setClass(System::getEnv('_APP_MAILS_CLASS_NAME', Event::MAILS_CLASS_NAME));
->setQueue(Event::MAILS_QUEUE_NAME)
->setClass(Event::MAILS_CLASS_NAME);
}
/**
+2 -3
View File
@@ -4,7 +4,6 @@ namespace Appwrite\Event;
use Utopia\Database\Document;
use Utopia\Queue\Publisher;
use Utopia\System\System;
class Messaging extends Event
{
@@ -20,8 +19,8 @@ class Messaging extends Event
parent::__construct($publisher);
$this
->setQueue(System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME))
->setClass(System::getEnv('_APP_MESSAGING_CLASS_NAME', Event::MESSAGING_CLASS_NAME));
->setQueue(Event::MESSAGING_QUEUE_NAME)
->setClass(Event::MESSAGING_CLASS_NAME);
}
/**
+2 -3
View File
@@ -5,7 +5,6 @@ namespace Appwrite\Event;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Queue\Publisher;
use Utopia\System\System;
class Migration extends Event
{
@@ -17,8 +16,8 @@ class Migration extends Event
parent::__construct($publisher);
$this
->setQueue(System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME))
->setClass(System::getEnv('_APP_MIGRATIONS_CLASS_NAME', Event::MIGRATIONS_CLASS_NAME));
->setQueue(Event::MIGRATIONS_QUEUE_NAME)
->setClass(Event::MIGRATIONS_CLASS_NAME);
}
/**
+2 -3
View File
@@ -3,7 +3,6 @@
namespace Appwrite\Event;
use Utopia\Queue\Publisher;
use Utopia\System\System;
class StatsResources extends Event
{
@@ -14,8 +13,8 @@ class StatsResources extends Event
parent::__construct($publisher);
$this
->setQueue(System::getEnv('_APP_STATS_RESOURCES_QUEUE_NAME', Event::STATS_RESOURCES_QUEUE_NAME))
->setClass(System::getEnv('_APP_STATS_RESOURCES_CLASS_NAME', Event::STATS_RESOURCES_CLASS_NAME));
->setQueue(Event::STATS_RESOURCES_QUEUE_NAME)
->setClass(Event::STATS_RESOURCES_CLASS_NAME);
}
/**
+2 -3
View File
@@ -4,7 +4,6 @@ namespace Appwrite\Event;
use Utopia\Database\Document;
use Utopia\Queue\Publisher;
use Utopia\System\System;
class StatsUsage extends Event
{
@@ -19,8 +18,8 @@ class StatsUsage extends Event
parent::__construct($publisher);
$this
->setQueue(System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME))
->setClass(System::getEnv('_APP_STATS_USAGE_CLASS_NAME', Event::STATS_USAGE_CLASS_NAME));
->setQueue(Event::STATS_USAGE_QUEUE_NAME)
->setClass(Event::STATS_USAGE_CLASS_NAME);
}
/**
+2 -3
View File
@@ -3,7 +3,6 @@
namespace Appwrite\Event;
use Utopia\Queue\Publisher;
use Utopia\System\System;
class Webhook extends Event
{
@@ -12,8 +11,8 @@ class Webhook extends Event
parent::__construct($publisher);
$this
->setQueue(System::getEnv('_APP_WEBHOOK_QUEUE_NAME', Event::WEBHOOK_QUEUE_NAME))
->setClass(System::getEnv('_APP_WEBHOOK_CLASS_NAME', Event::WEBHOOK_CLASS_NAME));
->setQueue(Event::WEBHOOK_QUEUE_NAME)
->setClass(Event::WEBHOOK_CLASS_NAME);
}
/**
+4 -2
View File
@@ -99,8 +99,6 @@ abstract class Migration
public function __construct()
{
Authorization::disable();
Authorization::setDefaultStatus(false);
$this->collections = Config::getParam('collections', []);
@@ -128,6 +126,7 @@ abstract class Migration
Document $project,
Database $dbForProject,
Database $dbForPlatform,
Authorization $authorization,
?callable $getProjectDB = null
): self {
$this->project = $project;
@@ -135,6 +134,9 @@ abstract class Migration
$this->dbForPlatform = $dbForPlatform;
$this->getProjectDB = $getProjectDB;
$authorization->disable();
$authorization->setDefaultStatus(false);
return $this;
}
-67
View File
@@ -1,67 +0,0 @@
# Modules AGENTS.md
> Before reading this file, also read Appwrite's base [AGENTS.md](../../../AGENTS.md).
Modules are the building blocks of the Appwrite platform. They are responsible for handling specific tasks, defining background workers, and providing API endpoints. Each module should have its own directory within the `src/Appwrite/Platform/Modules` directory.
Generally-speaking, each service is its own module, but there are some exceptions. The goal is to always put related code that achieves a specific goal under one roof.
## Structure and Naming Conventions
When adding a module, always add a new directory under `src/Appwrite/Platform/Modules`. The directory name should be PascalCase, and if possible, use only one word. For example, `User`, `Database`, `Storage`, etc. Avoid using shorthands, unless they are standardized, such as `DB`, `JWT`, or `SMTP`.
A module consists of:
- `Module.php` - Simple register class registering all module's services (from `Services` directory)
- `Workers` directory - Contains behavior for module-specific workers
- `Tasks` directory - Contains behavior for module-specific CLI tasks
- `Http` directory - Contains HTTP endpoints for the module
- `Services` directory - Contains register classes for all relevant types of services
Inside module, the `Services` directory can contain:
- `Http.php` - Register HTTP endpoints and hooks from `Http` directory
- `Workers.php` - Register workers from `Workers` directory
- `Tasks.php` - Register CLI tasks from `Tasks` directory
> After implementing a module, make sure to register it in `src/Appwrite/Platform/Appwrite.php`.
### HTTP directory structure
Inside module's `Http` directory, there are multiple rules to follow:
1. Directly in `Http` directory, there should only be directories for services (and hooks, check point number 2). If a module is a single service, it's okay to only have one directory, with the same name as the service, for example `src/Appwrite/Platform/Modules/Account/Http/Account`. An example with multiple services is `src/Appwrite/Platform/Modules/Databases/Http/Databases` and `src/Appwrite/Platform/Modules/Databases/Http/TablesDB`.
2. Hooks should live in `Hooks` directory, under `Init`, `Shutdown`, or `Error` directories, inside `Http` directory. For example, an init hook to prevent unauthorized access might live in `src/Appwrite/Platform/Modules/Functions/Http/Hooks/Init/Authentication.php`.
3. Inside `Http` directories for services, file names can only be `Get.php`, `Update.php`, `Create.php`, `Delete.php` or `XList.php`. We call it `XList`, because `List` is a reserved keyword and PHP would not like that. Never use any other words! Let's say you want a method to be `blockUser`, tempting to add `Users/Block.php`, instead, think of the resource and property it affects. Better naming would be `Users/Status/Update.php` (update user's status). Doing so also nicely reflects in the HTTP endpoint, `PATCH /v1/users/:userId/status`.
4. It's allowed to nest directories in `Http` service directories. For example, if you want to create a new deployment for a function based on a template, an endpoint might live in `src/Appwrite/Platform/Modules/Functions/Http/Functions/Deployments/Template/Create.php`. In this example, notice functions and deployments are resources, and template is property - both resources and properties can be nested, and have separate directories.
### Sample module directory structure
```bash
src/Appwrite/Platform/Modules/Functions
├── Module.php
├── Workers
│ └── Builds.php
├── Tasks
│ └── Block.php
├── Http
│ └── Functions
│ ├── Create.php
│ ├── XList.php
│ ├── Update.php
│ ├── Delete.php
│ ├── Get.php
│ └── Deployments
│ ├── XList.php
│ ├── Delete.php
│ ├── Get.php
│ └── Template
│ └── Create.php
└── Services
├── Http.php
├── Workers.php
└── Tasks.php
```

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