Compare commits

..
Author SHA1 Message Date
Atharva Deosthale f8f83285c8 fix spec generation 2026-01-07 00:46:44 +05:30
Atharva Deosthale 0a3b879140 fix sdk method names and use new auth methods after auth refactor 2026-01-07 00:36:25 +05:30
Atharva Deosthale c1c53789ae specs 2026-01-07 00:18:30 +05:30
Atharva Deosthale 131bf76848 merge conflicts 2026-01-06 23:59:32 +05:30
Atharva Deosthale e352eb8ced specs 2026-01-06 23:39:49 +05:30
Atharva Deosthale c187efadb4 improve the code a bit 2026-01-06 23:37:15 +05:30
Atharva Deosthale 9786b4f6cd progress, very close to be v0 2026-01-06 19:51:35 +05:30
Atharva Deosthale c915a00bac change back to traefik, check tests 2025-11-21 21:50:46 +05:30
Atharva Deosthale d03d9e5795 add functionality to cancel immediately rather than period end 2025-11-21 20:12:20 +05:30
Atharva Deosthale 7cde35d7a3 get proper projectDB when passing to adapter in webhook 2025-11-21 18:58:12 +05:30
Atharva Deosthale 27932854c8 format 2025-11-21 18:24:16 +05:30
Atharva Deosthale a01d689ae2 merge conflicts? 2025-11-21 18:21:56 +05:30
Atharva Deosthale f319841fd1 Merge branch '1.8.x' into auth-subscriptions 2025-11-21 16:31:56 +05:30
Atharva Deosthale 92f9ddfc33 remove legacy hackathon code 2025-11-21 16:28:13 +05:30
Atharva Deosthale 86878e3b55 move payments collections to projects db instead of platformDB 2025-11-21 15:12:10 +05:30
Atharva Deosthale df8c696b86 users already subscribed should not be able to do so again 2025-11-21 14:32:31 +05:30
Atharva Deosthale 650da7ba94 ensure customer before any checkout session 2025-11-15 16:10:36 +05:30
Atharva Deosthale efb8ff7bb1 store subscription id while in webhooks 2025-11-15 16:00:23 +05:30
Atharva Deosthale 16f7031799 get webhooks to sync subscription status 2025-11-15 15:42:13 +05:30
Atharva Deosthale 90e5824f9d progress, but dbplatform query always returns empty 2025-11-14 02:21:41 +05:30
Atharva Deosthale d7677a8992 progressing on webhook stuff 2025-11-14 01:21:23 +05:30
Atharva Deosthale 94390f4a65 add route to get team/user subscription 2025-11-13 23:30:37 +05:30
Atharva Deosthale f59cd158ef checkout gets created, yay 2025-11-13 18:41:32 +05:30
Atharva Deosthale 3967b2e338 remove temp files 2025-11-13 01:32:01 +05:30
Atharva Deosthale eb08a3379a very rough progress 2025-11-13 01:29:29 +05:30
Atharva Deosthale fee6ad43d6 progress 2025-11-12 19:09:55 +05:30
Atharva Deosthale 350e651c53 progress 2025-10-03 17:23:28 +05:30
Atharva Deosthale d49bad0de5 revert env 2025-09-30 23:46:44 +05:30
Atharva Deosthale e885cece85 format + lil cleanup 2025-09-30 23:44:14 +05:30
Atharva Deosthale 9b71c70d1a progress 2025-09-30 12:53:48 +05:30
Atharva Deosthale 414692083f remove env 2025-09-29 16:45:04 +05:30
Atharva Deosthale be1e681aa8 init payments 2025-09-29 16:44:11 +05:30
998 changed files with 38840 additions and 56976 deletions
-4
View File
@@ -2,7 +2,6 @@ _APP_ENV=development
_APP_EDITION=self-hosted
_APP_LOCALE=en
_APP_WORKER_PER_CORE=6
_APP_COMPRESSION_ENABLED=enabled
_APP_COMPRESSION_MIN_SIZE_BYTES=1024
_APP_CONSOLE_WHITELIST_ROOT=disabled
_APP_CONSOLE_WHITELIST_EMAILS=
@@ -103,11 +102,9 @@ _APP_STATS_RESOURCES_INTERVAL=30
_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
_APP_INTERVAL_DOMAIN_VERIFICATION=60
_APP_INTERVAL_CLEANUP_STALE_EXECUTIONS=300
_APP_USAGE_STATS=enabled
_APP_LOGGING_CONFIG=
_APP_LOGGING_CONFIG_REALTIME=
_APP_GRAPHQL_INTROSPECTION=enabled
_APP_GRAPHQL_MAX_BATCH_SIZE=10
_APP_GRAPHQL_MAX_COMPLEXITY=250
_APP_GRAPHQL_MAX_DEPTH=4
@@ -130,4 +127,3 @@ _APP_PROJECT_REGIONS=default
_APP_FUNCTIONS_CREATION_ABUSE_LIMIT=5000
_APP_STATS_USAGE_DUAL_WRITING_DBS=database_db_main
_APP_TRUSTED_HEADERS=x-forwarded-for
_APP_POOL_ADAPTER=stack
+4 -3
View File
@@ -12,7 +12,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
--no-plugins --no-scripts --prefer-dist \
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
FROM appwrite/base:0.11.5 AS base
FROM appwrite/base:0.10.6 AS base
LABEL maintainer="team@appwrite.io"
@@ -77,7 +77,6 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/queue-count-success && \
chmod +x /usr/local/bin/worker-audits && \
chmod +x /usr/local/bin/worker-builds && \
chmod +x /usr/local/bin/worker-screenshots && \
chmod +x /usr/local/bin/worker-certificates && \
chmod +x /usr/local/bin/worker-databases && \
chmod +x /usr/local/bin/worker-deletes && \
@@ -88,7 +87,9 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/worker-webhooks && \
chmod +x /usr/local/bin/worker-stats-usage && \
chmod +x /usr/local/bin/stats-resources && \
chmod +x /usr/local/bin/worker-stats-resources
chmod +x /usr/local/bin/worker-stats-resources && \
chmod +x /usr/local/bin/worker-payments-usage && \
chmod +x /usr/local/bin/schedule-payments-usage
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
+14 -21
View File
@@ -5,6 +5,7 @@ require_once __DIR__ . '/init.php';
use Appwrite\Event\Certificate;
use Appwrite\Event\Delete;
use Appwrite\Event\Func;
use Appwrite\Event\PaymentsUsage;
use Appwrite\Event\StatsResources;
use Appwrite\Event\StatsUsage;
use Appwrite\Platform\Appwrite;
@@ -41,6 +42,8 @@ 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) {
@@ -58,13 +61,7 @@ CLI::setResource('pools', function (Registry $register) {
return $register->get('pools');
}, ['register']);
CLI::setResource('authorization', function () {
$authorization = new Authorization();
$authorization->disable();
return $authorization;
}, []);
CLI::setResource('dbForPlatform', function ($pools, $cache, $authorization) {
CLI::setResource('dbForPlatform', function ($pools, $cache) {
$sleep = 3;
$maxAttempts = 5;
$attempts = 0;
@@ -78,8 +75,6 @@ CLI::setResource('dbForPlatform', function ($pools, $cache, $authorization) {
$dbForPlatform = new Database($adapter, $cache);
$dbForPlatform
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setNamespace('_console')
->setMetadata('host', \gethostname())
->setMetadata('project', 'console');
@@ -105,7 +100,7 @@ CLI::setResource('dbForPlatform', function ($pools, $cache, $authorization) {
}
return $dbForPlatform;
}, ['pools', 'cache', 'authorization']);
}, ['pools', 'cache']);
CLI::setResource('console', function () {
return new Document(Config::getParam('console'));
@@ -116,10 +111,10 @@ CLI::setResource(
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
);
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, $authorization) {
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases) {
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
@@ -152,7 +147,6 @@ 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', ''));
@@ -169,19 +163,17 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
}
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId());
return $database;
};
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
}, ['pools', 'dbForPlatform', 'cache']);
CLI::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
return function (?Document $project = null) use ($pools, $cache, $database) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int)$project->getSequence());
return $database;
@@ -191,8 +183,6 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorizatio
$database = new Database($adapter, $cache);
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setSharedTables(true)
->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_TASK)
@@ -205,7 +195,7 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorizatio
return $database;
};
}, ['pools', 'cache', 'authorization']);
}, ['pools', 'cache']);
CLI::setResource('publisher', function (Group $pools) {
return new BrokerPool(publisher: $pools->get('publisher'));
}, ['pools']);
@@ -239,6 +229,9 @@ CLI::setResource('queueForDeletes', function (Publisher $publisher) {
CLI::setResource('queueForCertificates', function (Publisher $publisher) {
return new Certificate($publisher);
}, ['publisher']);
CLI::setResource('queueForPaymentsUsage', function (Publisher $publisher) {
return new PaymentsUsage($publisher);
}, ['publisher']);
CLI::setResource('logError', function (Registry $register) {
return function (Throwable $error, string $namespace, string $action) use ($register) {
Console::error('[Error] Timestamp: ' . date('c', time()));
+2 -2
View File
@@ -26,8 +26,8 @@ unset($common['files']);
$collections = [
'buckets' => $buckets,
'databases' => $databases,
'projects' => array_merge_recursive($projects, $common),
'console' => array_merge_recursive($platform, $common),
'projects' => array_merge($projects, $common),
'console' => array_merge($platform, $common),
'logs' => $logs,
];
-11
View File
@@ -1288,17 +1288,6 @@ return [
'array' => false,
'filters' => ['json'],
],
[
'$id' => ID::custom('labels'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => [],
],
],
'indexes' => [
[
+48 -8
View File
@@ -232,6 +232,17 @@ $platformCollections = [
'array' => false,
'filters' => ['json'],
],
[
'$id' => ID::custom('payments'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 65536,
'signed' => true,
'required' => false,
'default' => [],
'array' => false,
'filters' => ['json', 'encrypt'],
],
[
'$id' => ID::custom('auths'),
'type' => Database::VAR_STRING,
@@ -633,7 +644,29 @@ $platformCollections = [
'name' => 'keys',
'attributes' => [
[
'$id' => ID::custom('resourceType'),
'$id' => ID::custom('projectInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('projectId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => 'resourceType',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -644,7 +677,7 @@ $platformCollections = [
'filters' => [],
],
[
'$id' => ID::custom('resourceId'),
'$id' => 'resourceId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -655,7 +688,7 @@ $platformCollections = [
'filters' => [],
],
[
'$id' => ID::custom('resourceInternalId'),
'$id' => 'resourceInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
@@ -734,14 +767,21 @@ $platformCollections = [
],
'indexes' => [
[
'$id' => ID::custom('_key_resource'),
'$id' => ID::custom('_key_project'),
'type' => Database::INDEX_KEY,
'attributes' => ['resourceType', 'resourceInternalId'],
'lengths' => [],
'orders' => [],
'attributes' => ['projectInternalId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_accessedAt'),
'$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,
'attributes' => ['accessedAt'],
'lengths' => [],
+121 -106
View File
@@ -567,17 +567,6 @@ return [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentRetention'),
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentInternalId'),
'type' => Database::VAR_STRING,
@@ -776,17 +765,6 @@ return [
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('startCommand'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 20000,
'signed' => true,
'required' => false,
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('specification'),
@@ -798,28 +776,6 @@ return [
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('buildSpecification'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => false,
'required' => false,
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('runtimeSpecification'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => false,
'required' => false,
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
'$id' => ID::custom('scopes'),
'type' => Database::VAR_STRING,
@@ -1079,17 +1035,6 @@ return [
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('startCommand'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 20000,
'signed' => true,
'required' => false,
'default' => null,
'filters' => [],
],
[
'$id' => ID::custom('fallbackFile'),
'type' => Database::VAR_STRING,
@@ -1101,17 +1046,6 @@ return [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentRetention'),
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('deploymentInternalId'),
'type' => Database::VAR_STRING,
@@ -1266,28 +1200,6 @@ return [
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('buildSpecification'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => false,
'required' => false,
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('runtimeSpecification'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 128,
'signed' => false,
'required' => false,
'default' => APP_COMPUTE_SPECIFICATION_DEFAULT,
'filters' => [],
],
[
'$id' => ID::custom('buildRuntime'),
'type' => Database::VAR_STRING,
@@ -1445,17 +1357,6 @@ return [
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('startCommand'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 20000,
'signed' => true,
'required' => false,
'default' => null,
'filters' => [],
],
[
'array' => false,
'$id' => ID::custom('buildOutput'),
@@ -2197,13 +2098,6 @@ return [
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_resourceType'),
'type' => Database::INDEX_KEY,
'attributes' => ['resourceType'],
'lengths' => [],
'orders' => [Database::ORDER_ASC],
],
],
],
@@ -2751,4 +2645,125 @@ return [
],
],
],
'payments_plans' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('payments_plans'),
'name' => 'Payments Plans',
'attributes' => [
[ '$id' => ID::custom('planId'), 'type' => Database::VAR_STRING, 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => true, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('name'), 'type' => Database::VAR_STRING, 'size' => 2048, 'signed' => true, 'required' => false, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('description'), 'type' => Database::VAR_STRING, 'size' => 8192, 'signed' => true, 'required' => false, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('pricing'), 'type' => Database::VAR_STRING, 'size' => 65536, 'signed' => true, 'required' => false, 'default' => [], 'array' => false, 'filters' => ['json'] ],
[ '$id' => ID::custom('isDefault'), 'type' => Database::VAR_BOOLEAN, 'size' => 0, 'signed' => true, 'required' => false, 'default' => false, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('isFree'), 'type' => Database::VAR_BOOLEAN, 'size' => 0, 'signed' => true, 'required' => false, 'default' => false, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('status'), 'type' => Database::VAR_STRING, 'size' => 32, 'signed' => true, 'required' => false, 'default' => 'active', 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('providers'), 'type' => Database::VAR_STRING, 'size' => 65536, 'signed' => true, 'required' => false, 'default' => [], 'array' => false, 'filters' => ['json'] ],
[ '$id' => ID::custom('features'), 'type' => Database::VAR_STRING, 'size' => 65536, 'signed' => true, 'required' => false, 'default' => [], 'array' => false, 'filters' => ['json'] ],
[ '$id' => ID::custom('search'), 'type' => Database::VAR_STRING, 'size' => 16384, 'signed' => true, 'required' => false, 'default' => null, 'array' => false, 'filters' => [] ],
],
'indexes' => [
[ '$id' => ID::custom('_key_plan'), 'type' => Database::INDEX_UNIQUE, 'attributes' => ['planId'], 'lengths' => [Database::LENGTH_KEY], 'orders' => [Database::ORDER_ASC] ],
[ '$id' => ID::custom('_key_status'), 'type' => Database::INDEX_KEY, 'attributes' => ['status'], 'lengths' => [32], 'orders' => [Database::ORDER_ASC] ],
[ '$id' => ID::custom('_fulltext_search'), 'type' => Database::INDEX_FULLTEXT, 'attributes' => ['search'], 'lengths' => [], 'orders' => [] ],
],
],
'payments_features' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('payments_features'),
'name' => 'Payments Features',
'attributes' => [
[ '$id' => ID::custom('featureId'), 'type' => Database::VAR_STRING, 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => true, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('name'), 'type' => Database::VAR_STRING, 'size' => 2048, 'signed' => true, 'required' => false, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('type'), 'type' => Database::VAR_STRING, 'size' => 32, 'signed' => true, 'required' => true, 'default' => 'boolean', 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('description'), 'type' => Database::VAR_STRING, 'size' => 8192, 'signed' => true, 'required' => false, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('providers'), 'type' => Database::VAR_STRING, 'size' => 65536, 'signed' => true, 'required' => false, 'default' => [], 'array' => false, 'filters' => ['json'] ],
],
'indexes' => [
[ '$id' => ID::custom('_key_feature'), 'type' => Database::INDEX_UNIQUE, 'attributes' => ['featureId'], 'lengths' => [Database::LENGTH_KEY], 'orders' => [Database::ORDER_ASC] ],
[ '$id' => ID::custom('_key_type'), 'type' => Database::INDEX_KEY, 'attributes' => ['type'], 'lengths' => [32], 'orders' => [Database::ORDER_ASC] ],
],
],
'payments_plan_features' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('payments_plan_features'),
'name' => 'Payments Plan Features',
'attributes' => [
[ '$id' => ID::custom('planId'), 'type' => Database::VAR_STRING, 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => true, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('featureId'), 'type' => Database::VAR_STRING, 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => true, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('type'), 'type' => Database::VAR_STRING, 'size' => 32, 'signed' => true, 'required' => true, 'default' => 'boolean', 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('enabled'), 'type' => Database::VAR_BOOLEAN, 'size' => 0, 'signed' => true, 'required' => false, 'default' => true, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('currency'), 'type' => Database::VAR_STRING, 'size' => 8, 'signed' => true, 'required' => false, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('interval'), 'type' => Database::VAR_STRING, 'size' => 16, 'signed' => true, 'required' => false, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('includedUnits'), 'type' => Database::VAR_INTEGER, 'size' => 0, 'signed' => false, 'required' => false, 'default' => 0, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('tiersMode'), 'type' => Database::VAR_STRING, 'size' => 16, 'signed' => true, 'required' => false, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('tiers'), 'type' => Database::VAR_STRING, 'size' => 65536, 'signed' => true, 'required' => false, 'default' => [], 'array' => false, 'filters' => ['json'] ],
[ '$id' => ID::custom('usageCap'), 'type' => Database::VAR_INTEGER, 'size' => 0, 'signed' => false, 'required' => false, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('overagePrice'), 'type' => Database::VAR_INTEGER, 'size' => 0, 'signed' => false, 'required' => false, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('providers'), 'type' => Database::VAR_STRING, 'size' => 65536, 'signed' => true, 'required' => false, 'default' => [], 'array' => false, 'filters' => ['json'] ],
[ '$id' => ID::custom('metadata'), 'type' => Database::VAR_STRING, 'size' => 65536, 'signed' => true, 'required' => false, 'default' => [], 'array' => false, 'filters' => ['json'] ],
],
'indexes' => [
[ '$id' => ID::custom('_key_unique'), 'type' => Database::INDEX_UNIQUE, 'attributes' => ['planId','featureId'], 'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY], 'orders' => [Database::ORDER_ASC, Database::ORDER_ASC] ],
[ '$id' => ID::custom('_key_type'), 'type' => Database::INDEX_KEY, 'attributes' => ['type'], 'lengths' => [16], 'orders' => [Database::ORDER_ASC] ],
],
],
'payments_subscriptions' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('payments_subscriptions'),
'name' => 'Payments Subscriptions',
'attributes' => [
[ '$id' => ID::custom('subscriptionId'), 'type' => Database::VAR_STRING, 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => true, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('providerSubscriptionId'), 'type' => Database::VAR_STRING, 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => false, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('providerCheckoutId'), 'type' => Database::VAR_STRING, 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => false, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('actorType'), 'type' => Database::VAR_STRING, 'size' => 16, 'signed' => true, 'required' => true, 'default' => 'user', 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('actorId'), 'type' => Database::VAR_STRING, 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => true, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('actorInternalId'), 'type' => Database::VAR_STRING, 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => true, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('planId'), 'type' => Database::VAR_STRING, 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => true, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('priceId'), 'type' => Database::VAR_STRING, 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => false, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('status'), 'type' => Database::VAR_STRING, 'size' => 32, 'signed' => true, 'required' => false, 'default' => 'active', 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('trialEndsAt'), 'type' => Database::VAR_DATETIME, 'size' => 0, 'signed' => false, 'required' => false, 'default' => null, 'array' => false, 'filters' => ['datetime'] ],
[ '$id' => ID::custom('currentPeriodStart'), 'type' => Database::VAR_DATETIME, 'size' => 0, 'signed' => false, 'required' => false, 'default' => null, 'array' => false, 'filters' => ['datetime'] ],
[ '$id' => ID::custom('currentPeriodEnd'), 'type' => Database::VAR_DATETIME, 'size' => 0, 'signed' => false, 'required' => false, 'default' => null, 'array' => false, 'filters' => ['datetime'] ],
[ '$id' => ID::custom('cancelAtPeriodEnd'), 'type' => Database::VAR_BOOLEAN, 'size' => 0, 'signed' => true, 'required' => false, 'default' => false, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('canceledAt'), 'type' => Database::VAR_DATETIME, 'size' => 0, 'signed' => false, 'required' => false, 'default' => null, 'array' => false, 'filters' => ['datetime'] ],
[ '$id' => ID::custom('providers'), 'type' => Database::VAR_STRING, 'size' => 65536, 'signed' => true, 'required' => false, 'default' => [], 'array' => false, 'filters' => ['json'] ],
[ '$id' => ID::custom('usageSummary'), 'type' => Database::VAR_STRING, 'size' => 65536, 'signed' => true, 'required' => false, 'default' => [], 'array' => false, 'filters' => ['json'] ],
[ '$id' => ID::custom('tags'), 'type' => Database::VAR_STRING, 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => false, 'default' => [], 'array' => true, 'filters' => [] ],
[ '$id' => ID::custom('search'), 'type' => Database::VAR_STRING, 'size' => 16384, 'signed' => true, 'required' => false, 'default' => null, 'array' => false, 'filters' => [] ],
],
'indexes' => [
[ '$id' => ID::custom('_key_actor'), 'type' => Database::INDEX_KEY, 'attributes' => ['actorType','actorId'], 'lengths' => [16, Database::LENGTH_KEY], 'orders' => [Database::ORDER_ASC, Database::ORDER_ASC] ],
[ '$id' => ID::custom('_key_status'), 'type' => Database::INDEX_KEY, 'attributes' => ['status'], 'lengths' => [32], 'orders' => [Database::ORDER_ASC] ],
[ '$id' => ID::custom('_key_plan'), 'type' => Database::INDEX_KEY, 'attributes' => ['planId'], 'lengths' => [Database::LENGTH_KEY], 'orders' => [Database::ORDER_ASC] ],
[ '$id' => ID::custom('_key_provider_subscription'), 'type' => Database::INDEX_KEY, 'attributes' => ['providerSubscriptionId'], 'lengths' => [Database::LENGTH_KEY], 'orders' => [Database::ORDER_ASC] ],
[ '$id' => ID::custom('_key_provider_checkout'), 'type' => Database::INDEX_KEY, 'attributes' => ['providerCheckoutId'], 'lengths' => [Database::LENGTH_KEY], 'orders' => [Database::ORDER_ASC] ],
[ '$id' => ID::custom('_fulltext_search'), 'type' => Database::INDEX_FULLTEXT, 'attributes' => ['search'], 'lengths' => [], 'orders' => [] ],
],
],
'payments_usage_events' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('payments_usage_events'),
'name' => 'Payments Usage Events',
'attributes' => [
[ '$id' => ID::custom('subscriptionId'), 'type' => Database::VAR_STRING, 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => true, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('actorType'), 'type' => Database::VAR_STRING, 'size' => 16, 'signed' => true, 'required' => true, 'default' => 'user', 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('actorId'), 'type' => Database::VAR_STRING, 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => true, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('planId'), 'type' => Database::VAR_STRING, 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => true, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('featureId'), 'type' => Database::VAR_STRING, 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => true, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('quantity'), 'type' => Database::VAR_INTEGER, 'size' => 0, 'signed' => false, 'required' => true, 'default' => 0, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('timestamp'), 'type' => Database::VAR_DATETIME, 'size' => 0, 'signed' => false, 'required' => true, 'default' => null, 'array' => false, 'filters' => ['datetime'] ],
[ '$id' => ID::custom('providerSyncState'), 'type' => Database::VAR_STRING, 'size' => 32, 'signed' => true, 'required' => false, 'default' => 'pending', 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('providerEventId'), 'type' => Database::VAR_STRING, 'size' => 256, 'signed' => true, 'required' => false, 'default' => null, 'array' => false, 'filters' => [] ],
[ '$id' => ID::custom('metadata'), 'type' => Database::VAR_STRING, 'size' => 65536, 'signed' => true, 'required' => false, 'default' => [], 'array' => false, 'filters' => ['json'] ],
],
'indexes' => [
[ '$id' => ID::custom('_key_subscription_feature_time'), 'type' => Database::INDEX_KEY, 'attributes' => ['subscriptionId','featureId','timestamp'], 'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY, 0], 'orders' => [Database::ORDER_ASC, Database::ORDER_ASC, Database::ORDER_ASC] ],
[ '$id' => ID::custom('_key_sync_state'), 'type' => Database::INDEX_KEY, 'attributes' => ['providerSyncState'], 'lengths' => [32], 'orders' => [Database::ORDER_ASC] ],
],
],
];
+40 -22
View File
@@ -357,11 +357,6 @@ return [
'description' => 'API key and session used in the same request. Use either `setSession` or `setKey`. Learn about which authentication method to use in the SSR docs: https://appwrite.io/docs/products/auth/server-side-rendering',
'code' => 403,
],
Exception::USER_JWT_AND_COOKIE_SET => [
'name' => Exception::USER_JWT_AND_COOKIE_SET,
'description' => 'JWT and cookie used in the same request. Use either `setJWT` or `setCookie`. Learn about which authentication method to use in the SSR docs: https://appwrite.io/docs/products/auth/server-side-rendering',
'code' => 403,
],
Exception::API_KEY_EXPIRED => [
'name' => Exception::API_KEY_EXPIRED,
'description' => 'The dynamic API key has expired. Please don\'t use dynamic API keys for more than duration of the execution.',
@@ -1079,11 +1074,6 @@ return [
'description' => 'The project key has expired. Please generate a new key using the Appwrite console.',
'code' => 401,
],
Exception::ACCOUNT_KEY_EXPIRED => [
'name' => Exception::ACCOUNT_KEY_EXPIRED,
'description' => 'The account API key has expired. Please generate a new key using the Appwrite console.',
'code' => 401,
],
Exception::ROUTER_HOST_NOT_FOUND => [
'name' => Exception::ROUTER_HOST_NOT_FOUND,
'description' => 'Host is not trusted. This could occur because you have not configured a custom domain. Add a custom domain to your project first and try again.',
@@ -1113,6 +1103,7 @@ return [
'name' => Exception::RULE_VERIFICATION_FAILED,
'description' => 'Domain verification failed. Please check if your DNS records are correct and try again.',
'code' => 400,
'publish' => true
],
Exception::PROJECT_SMTP_CONFIG_INVALID => [
'name' => Exception::PROJECT_SMTP_CONFIG_INVALID,
@@ -1338,19 +1329,46 @@ return [
'description' => 'Target has an invalid provider type.',
'code' => 400,
],
Exception::USER_ID_MISSING => [
'name' => Exception::USER_ID_MISSING,
'description' => 'When using account API key, make sure to pass x-appwrite-user header with your user ID.',
'code' => 403,
/** Payments */
Exception::PAYMENT_PLAN_NOT_FOUND => [
'name' => Exception::PAYMENT_PLAN_NOT_FOUND,
'description' => 'Payment plan with the requested ID could not be found.',
'code' => 404,
],
Exception::ORGANIZATION_ID_MISSING => [
'name' => Exception::ORGANIZATION_ID_MISSING,
'description' => 'When using organization API key, make sure to pass x-appwrite-organization header with your organization ID.',
'code' => 403,
Exception::PAYMENT_PLAN_ALREADY_EXISTS => [
'name' => Exception::PAYMENT_PLAN_ALREADY_EXISTS,
'description' => 'Payment plan with the requested ID already exists.',
'code' => 409,
],
Exception::PROJECT_ID_MISSING => [
'name' => Exception::PROJECT_ID_MISSING,
'description' => 'When using project API key, make sure to pass x-appwrite-project header with your project ID.',
'code' => 403,
Exception::PAYMENT_SUBSCRIPTION_NOT_FOUND => [
'name' => Exception::PAYMENT_SUBSCRIPTION_NOT_FOUND,
'description' => 'Payment subscription with the requested ID could not be found.',
'code' => 404,
],
Exception::PAYMENT_SUBSCRIPTION_ALREADY_EXISTS => [
'name' => Exception::PAYMENT_SUBSCRIPTION_ALREADY_EXISTS,
'description' => 'Payment subscription already exists for this actor.',
'code' => 409,
],
Exception::PAYMENT_PROVIDER_NOT_CONFIGURED => [
'name' => Exception::PAYMENT_PROVIDER_NOT_CONFIGURED,
'description' => 'No payment provider has been configured for this project.',
'code' => 400,
],
Exception::PAYMENT_PROVIDER_ALREADY_CONFIGURED => [
'name' => Exception::PAYMENT_PROVIDER_ALREADY_CONFIGURED,
'description' => 'Payment provider is already configured. Disconnect the existing provider before configuring a new one.',
'code' => 409,
],
Exception::PAYMENT_WEBHOOK_FAILED => [
'name' => Exception::PAYMENT_WEBHOOK_FAILED,
'description' => 'Failed to create payment provider webhook.',
'code' => 500,
],
Exception::PAYMENT_FEATURE_NOT_FOUND => [
'name' => Exception::PAYMENT_FEATURE_NOT_FOUND,
'description' => 'Payment feature with the requested ID could not be found.',
'code' => 404,
],
];
+38 -21
View File
@@ -50,12 +50,17 @@
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Poppins';
src: url('https://assets.appwrite.io/fonts/poppins/poppins-v23-latin-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
</style>
<style>
.main {
max-width: 650px;
margin: 0 auto;
margin-top: 32px;
body {
padding: 32px;
line-height: 1.5;
color: #616b7c;
@@ -63,12 +68,15 @@
font-weight: 400;
font-family: "Inter", sans-serif;
background-color: #ffffff;
margin: 0;
padding: 0;
line-height: 150%;
}
.main a {
a {
color: currentColor;
word-break: break-all;
}
.main a.button {
a.button {
box-sizing: border-box;
display: inline-block;
text-align: center;
@@ -79,8 +87,8 @@
border: 1px solid #414146;
border-radius: 8px;
}
.main a.button:hover,
.main a.button:focus {
a.button:hover,
a.button:focus {
opacity: 0.8;
}
table {
@@ -94,6 +102,11 @@
td {
vertical-align: top;
}
.main {
max-width: 650px;
margin: 0 auto;
margin-top: 32px;
}
h1 {
font-size: 22px;
margin-bottom: 0px;
@@ -125,6 +138,9 @@
border: none;
border-top: 1px solid #e8e9f0;
}
h* {
font-family: 'Poppins', sans-serif;
}
p {
margin-bottom: 10px;
}
@@ -139,20 +155,21 @@
<body style="direction: {{direction}}">
<div style="display: none; overflow: hidden; max-height: 0; max-width: 0; opacity: 0; line-height: 1px;">
{{preview}}
<div>{{previewWhitespace}}</div>
</div>
<div style="display: none; overflow: hidden; max-height: 0; max-width: 0; opacity: 0; line-height: 1px;">
{{preview}}
<div>{{previewWhitespace}}</div>
</div>
<div class="main" style="max-width:650px; word-wrap: break-word; overflow-wrap: break-word; word-break: normal; margin:0 auto;">
<table style="margin-top: 32px">
<tr>
<td>
{{body}}
</td>
</tr>
</table>
</div>
<div style="max-width:650px; word-wrap: break-word; overflow-wrap: break-word;
word-break: normal; margin:0 auto;">
<table style="margin-top: 32px">
<tr>
<td>
{{body}}
</td>
</tr>
</table>
</div>
</body>
</html>
-1
View File
@@ -23,5 +23,4 @@ return [
'privacyUrl' => APP_EMAIL_PRIVACY_URL,
'websiteUrl' => 'https://' . APP_DOMAIN,
'emailSenderName' => APP_EMAIL_PLATFORM_NAME,
'sitePreviewDomain' => System::getEnv('_APP_DOMAIN_SITES', ''),
];
@@ -166,4 +166,13 @@ return [ // List of publicly visible scopes
'tokens.write' => [
'description' => 'Access to create, update, and delete your project\'s tokens',
],
'payments.read' => [
'description' => 'Access to read your project\'s payments data (plans, subscriptions, usage)'
],
'payments.write' => [
'description' => 'Access to create, update, and delete your project\'s payments resources'
],
'payments.subscribe' => [
'description' => 'Access to create and manage subscriptions as an actor (user/team)'
],
];
-15
View File
@@ -1,15 +0,0 @@
<?php
// List of scopes for Account API keys (Tokens)
return [
"account" => [
"description" => 'Access to manage account, its organizations, sessions, tokens, and billing.',
],
"teams.read" => [
"description" => 'Access to read account\'s organizations.',
],
"teams.write" => [
"description" => 'Access to create, update and delete account\'s organizations and its memberships.',
],
];
-42
View File
@@ -1,42 +0,0 @@
<?php
// List of scopes for organization (teams) API keys
return [
"platforms.read" => [
"description" => 'Access to read project\'s platforms',
],
"platforms.write" => [
"description" =>
'Access to create, update, and delete project\'s platforms',
],
"projects.read" => [
"description" => 'Access to read organization\'s projects',
],
"projects.write" => [
"description" =>
"Access to create, update, and delete projects in organization",
],
"keys.read" => [
"description" => 'Access to read project\'s API keys',
],
"keys.write" => [
"description" =>
"Access to create, update, and delete project\'s API keys",
],
"devKeys.read" => [
"description" => 'Access to read project\'s development keys',
],
"devKeys.write" => [
"description" =>
"Access to create, update, and delete project\'s development keys",
],
"webhooks.read" => [
"description" =>
"Access to read project\'s webhooks",
],
"webhooks.write" => [
"description" =>
"Access to create, update, and delete project\'s webhooks",
],
];
+15 -35
View File
@@ -11,7 +11,7 @@ return [
[
'key' => 'web',
'name' => 'Web',
'version' => '22.0.0',
'version' => '21.5.0',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
@@ -60,7 +60,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '21.0.0',
'version' => '20.3.2',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@@ -79,7 +79,7 @@ return [
[
'key' => 'apple',
'name' => 'Apple',
'version' => '14.0.0',
'version' => '13.5.0',
'url' => 'https://github.com/appwrite/sdk-for-apple',
'package' => 'https://github.com/appwrite/sdk-for-apple',
'enabled' => true,
@@ -117,7 +117,7 @@ return [
'key' => 'android',
'name' => 'Android',
'namespace' => 'io.appwrite',
'version' => '12.0.0',
'version' => '11.4.0',
'url' => 'https://github.com/appwrite/sdk-for-android',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
'enabled' => true,
@@ -140,7 +140,7 @@ return [
[
'key' => 'react-native',
'name' => 'React Native',
'version' => '0.20.0',
'version' => '0.19.0',
'url' => 'https://github.com/appwrite/sdk-for-react-native',
'package' => 'https://npmjs.com/package/react-native-appwrite',
'enabled' => true,
@@ -208,7 +208,7 @@ return [
[
'key' => 'web',
'name' => 'Console',
'version' => '0.3.0',
'version' => '0.2.0',
'url' => '',
'package' => '',
'enabled' => true,
@@ -227,7 +227,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '14.0.0',
'version' => '12.0.1',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@@ -250,26 +250,6 @@ return [
],
],
],
[
'key' => 'markdown',
'name' => 'Markdown',
'version' => '0.2.0',
'url' => 'https://github.com/appwrite/sdk-for-md.git',
'package' => 'https://www.npmjs.com/package/@appwrite.io/docs',
'enabled' => true,
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_SDK_PLATFORM_CONSOLE,
'prism' => 'markdown',
'source' => \realpath(__DIR__ . '/../sdks/console-md'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-md.git',
'gitRepoName' => 'sdk-for-md',
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
'repoBranch' => 'main',
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/md/CHANGELOG.md'),
],
],
],
@@ -283,7 +263,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '22.0.0',
'version' => '21.1.0',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@@ -302,7 +282,7 @@ return [
[
'key' => 'php',
'name' => 'PHP',
'version' => '20.0.0',
'version' => '19.1.0',
'url' => 'https://github.com/appwrite/sdk-for-php',
'package' => 'https://packagist.org/packages/appwrite/appwrite',
'enabled' => true,
@@ -321,7 +301,7 @@ return [
[
'key' => 'python',
'name' => 'Python',
'version' => '15.0.0',
'version' => '14.1.0',
'url' => 'https://github.com/appwrite/sdk-for-python',
'package' => 'https://pypi.org/project/appwrite/',
'enabled' => true,
@@ -340,7 +320,7 @@ return [
[
'key' => 'ruby',
'name' => 'Ruby',
'version' => '21.0.0',
'version' => '20.1.0',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,
@@ -378,7 +358,7 @@ return [
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.25.0',
'version' => '0.24.0',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => true,
@@ -397,7 +377,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '21.0.0',
'version' => '20.1.0',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
@@ -417,7 +397,7 @@ return [
'key' => 'kotlin',
'name' => 'Kotlin',
'namespace' => 'io.appwrite',
'version' => '14.0.0',
'version' => '13.1.0',
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
'enabled' => true,
@@ -440,7 +420,7 @@ return [
[
'key' => 'swift',
'name' => 'Swift',
'version' => '15.0.0',
'version' => '14.1.0',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,
+16 -3
View File
@@ -48,7 +48,7 @@ return [
'name' => 'Avatars',
'subtitle' => 'The Avatars service aims to help you complete everyday tasks related to your app image, icons, and avatars.',
'description' => '/docs/services/avatars.md',
'controller' => '', // Uses modules
'controller' => 'api/avatars.php',
'sdk' => true,
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/client/avatars',
@@ -104,7 +104,7 @@ return [
'name' => 'Health',
'subtitle' => 'The Health service allows you to both validate and monitor your Appwrite server\'s health.',
'description' => '/docs/services/health.md',
'controller' => '', // Uses modules
'controller' => 'api/health.php',
'sdk' => true,
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/server/health',
@@ -146,7 +146,7 @@ return [
'name' => 'Storage',
'subtitle' => 'The Storage service allows you to manage your project files.',
'description' => '/docs/services/storage.md',
'controller' => '', // Uses modules
'controller' => '',
'sdk' => true,
'docs' => true,
'docsUrl' => 'https://appwrite.io/docs/client/storage',
@@ -225,6 +225,19 @@ return [
'icon' => '/images/services/functions.png',
'platforms' => ['client', 'server', 'console'],
],
'payments' => [
'key' => 'payments',
'name' => 'Payments',
'subtitle' => 'Create and manage plans, subscriptions, usage and billing providers.',
'description' => '/docs/services/payments.md',
'controller' => '', // Uses modules
'sdk' => true,
'docs' => true,
'docsUrl' => '',
'tests' => false,
'optional' => true,
'icon' => '/images/services/databases.png',
],
'proxy' => [
'key' => 'proxy',
'name' => 'Proxy',
+117 -157
View File
@@ -1,7 +1,7 @@
{
"openapi": "3.0.0",
"info": {
"version": "1.8.1",
"version": "1.8.0",
"title": "Appwrite",
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
@@ -140,8 +140,7 @@
"email": {
"type": "string",
"description": "User email.",
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"password": {
"type": "string",
@@ -225,14 +224,12 @@
"email": {
"type": "string",
"description": "User email.",
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"password": {
"type": "string",
"description": "User password. Must be at least 8 chars.",
"x-example": "password",
"format": "password"
"x-example": "password"
}
},
"required": [
@@ -442,8 +439,7 @@
"duration": {
"type": "integer",
"description": "Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.",
"x-example": 0,
"format": "int32"
"x-example": 0
}
}
}
@@ -555,7 +551,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 253,
"weight": 288,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@@ -627,7 +623,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 255,
"weight": 290,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@@ -751,7 +747,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 256,
"weight": 291,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@@ -891,7 +887,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 257,
"weight": 292,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@@ -1015,7 +1011,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 261,
"weight": 296,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@@ -1149,7 +1145,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 262,
"weight": 297,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@@ -1287,7 +1283,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 254,
"weight": 289,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@@ -1388,7 +1384,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 260,
"weight": 295,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@@ -1487,7 +1483,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 258,
"weight": 293,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@@ -1586,7 +1582,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 259,
"weight": 294,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@@ -1800,8 +1796,7 @@
"oldPassword": {
"type": "string",
"description": "Current user password. Must be at least 8 chars.",
"x-example": "password",
"format": "password"
"x-example": "password"
}
},
"required": [
@@ -1873,14 +1868,12 @@
"phone": {
"type": "string",
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
"x-example": "+12065550100",
"format": "phone"
"x-example": "+12065550100"
},
"password": {
"type": "string",
"description": "User password. Must be at least 8 chars.",
"x-example": "password",
"format": "password"
"x-example": "password"
}
},
"required": [
@@ -2079,14 +2072,12 @@
"email": {
"type": "string",
"description": "User email.",
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"url": {
"type": "string",
"description": "URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.",
"x-example": "https:\/\/example.com",
"format": "url"
"x-example": "https:\/\/example.com"
}
},
"required": [
@@ -2391,14 +2382,12 @@
"email": {
"type": "string",
"description": "User email.",
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"password": {
"type": "string",
"description": "User password. Must be at least 8 chars.",
"x-example": "password",
"format": "password"
"x-example": "password"
}
},
"required": [
@@ -2584,7 +2573,9 @@
"yammer",
"yandex",
"zoho",
"zoom"
"zoom",
"mock",
"mock-unverified"
],
"x-enum-name": "OAuthProvider",
"x-enum-keys": []
@@ -3313,8 +3304,7 @@
"email": {
"type": "string",
"description": "User email.",
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"phrase": {
"type": "boolean",
@@ -3400,14 +3390,12 @@
"email": {
"type": "string",
"description": "User email.",
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"url": {
"type": "string",
"description": "URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.",
"x-example": "https:\/\/example.com",
"format": "url"
"x-example": "https:\/\/example.com"
},
"phrase": {
"type": "boolean",
@@ -3516,7 +3504,9 @@
"yammer",
"yandex",
"zoho",
"zoom"
"zoom",
"mock",
"mock-unverified"
],
"x-enum-name": "OAuthProvider",
"x-enum-keys": []
@@ -3631,8 +3621,7 @@
"phone": {
"type": "string",
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
"x-example": "+12065550100",
"format": "phone"
"x-example": "+12065550100"
}
},
"required": [
@@ -3757,8 +3746,7 @@
"url": {
"type": "string",
"description": "URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.",
"x-example": "https:\/\/example.com",
"format": "url"
"x-example": "https:\/\/example.com"
}
},
"required": [
@@ -4051,7 +4039,7 @@
"x-appwrite": {
"method": "getBrowser",
"group": null,
"weight": 264,
"weight": 50,
"cookies": false,
"type": "location",
"demo": "avatars\/get-browser.md",
@@ -4179,7 +4167,7 @@
"x-appwrite": {
"method": "getCreditCard",
"group": null,
"weight": 263,
"weight": 49,
"cookies": false,
"type": "location",
"demo": "avatars\/get-credit-card.md",
@@ -4313,7 +4301,7 @@
"x-appwrite": {
"method": "getFavicon",
"group": null,
"weight": 267,
"weight": 53,
"cookies": false,
"type": "location",
"demo": "avatars\/get-favicon.md",
@@ -4373,7 +4361,7 @@
"x-appwrite": {
"method": "getFlag",
"group": null,
"weight": 265,
"weight": 51,
"cookies": false,
"type": "location",
"demo": "avatars\/get-flag.md",
@@ -4863,7 +4851,7 @@
"x-appwrite": {
"method": "getImage",
"group": null,
"weight": 266,
"weight": 52,
"cookies": false,
"type": "location",
"demo": "avatars\/get-image.md",
@@ -4947,7 +4935,7 @@
"x-appwrite": {
"method": "getInitials",
"group": null,
"weight": 269,
"weight": 55,
"cookies": false,
"type": "location",
"demo": "avatars\/get-initials.md",
@@ -5041,7 +5029,7 @@
"x-appwrite": {
"method": "getQR",
"group": null,
"weight": 268,
"weight": 54,
"cookies": false,
"type": "location",
"demo": "avatars\/get-qr.md",
@@ -5135,7 +5123,7 @@
"x-appwrite": {
"method": "getScreenshot",
"group": null,
"weight": 270,
"weight": 56,
"cookies": false,
"type": "location",
"demo": "avatars\/get-screenshot.md",
@@ -5888,7 +5876,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 346,
"weight": 362,
"cookies": false,
"type": "",
"demo": "databases\/list-transactions.md",
@@ -5955,7 +5943,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 342,
"weight": 358,
"cookies": false,
"type": "",
"demo": "databases\/create-transaction.md",
@@ -5992,8 +5980,7 @@
"ttl": {
"type": "integer",
"description": "Seconds before the transaction expires.",
"x-example": 60,
"format": "int32"
"x-example": 60
}
}
}
@@ -6026,7 +6013,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 343,
"weight": 359,
"cookies": false,
"type": "",
"demo": "databases\/get-transaction.md",
@@ -6090,7 +6077,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 344,
"weight": 360,
"cookies": false,
"type": "",
"demo": "databases\/update-transaction.md",
@@ -6168,7 +6155,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 345,
"weight": 361,
"cookies": false,
"type": "",
"demo": "databases\/delete-transaction.md",
@@ -6234,7 +6221,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 347,
"weight": 363,
"cookies": false,
"type": "",
"demo": "databases\/create-operations.md",
@@ -6319,7 +6306,7 @@
"x-appwrite": {
"method": "listDocuments",
"group": "documents",
"weight": 297,
"weight": 321,
"cookies": false,
"type": "",
"demo": "databases\/list-documents.md",
@@ -6431,7 +6418,7 @@
"x-appwrite": {
"method": "createDocument",
"group": "documents",
"weight": 289,
"weight": 313,
"cookies": false,
"type": "",
"demo": "databases\/create-document.md",
@@ -6592,7 +6579,7 @@
"x-appwrite": {
"method": "getDocument",
"group": "documents",
"weight": 290,
"weight": 314,
"cookies": false,
"type": "",
"demo": "databases\/get-document.md",
@@ -6703,7 +6690,7 @@
"x-appwrite": {
"method": "upsertDocument",
"group": "documents",
"weight": 293,
"weight": 317,
"cookies": false,
"type": "",
"demo": "databases\/upsert-document.md",
@@ -6858,7 +6845,7 @@
"x-appwrite": {
"method": "updateDocument",
"group": "documents",
"weight": 291,
"weight": 315,
"cookies": false,
"type": "",
"demo": "databases\/update-document.md",
@@ -6970,7 +6957,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 295,
"weight": 319,
"cookies": false,
"type": "",
"demo": "databases\/delete-document.md",
@@ -7077,7 +7064,7 @@
"x-appwrite": {
"method": "decrementDocumentAttribute",
"group": "documents",
"weight": 300,
"weight": 324,
"cookies": false,
"type": "",
"demo": "databases\/decrement-document-attribute.md",
@@ -7159,14 +7146,12 @@
"value": {
"type": "number",
"description": "Value to increment the attribute by. The value must be a number.",
"x-example": null,
"format": "float"
"x-example": null
},
"min": {
"type": "number",
"description": "Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.",
"x-example": null,
"format": "float",
"x-nullable": true
},
"transactionId": {
@@ -7206,7 +7191,7 @@
"x-appwrite": {
"method": "incrementDocumentAttribute",
"group": "documents",
"weight": 299,
"weight": 323,
"cookies": false,
"type": "",
"demo": "databases\/increment-document-attribute.md",
@@ -7288,14 +7273,12 @@
"value": {
"type": "number",
"description": "Value to increment the attribute by. The value must be a number.",
"x-example": null,
"format": "float"
"x-example": null
},
"max": {
"type": "number",
"description": "Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.",
"x-example": null,
"format": "float",
"x-nullable": true
},
"transactionId": {
@@ -7335,7 +7318,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 447,
"weight": 454,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@@ -7422,7 +7405,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 445,
"weight": 452,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@@ -7540,7 +7523,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 446,
"weight": 453,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@@ -7615,7 +7598,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 190,
"weight": 225,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@@ -7669,7 +7652,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 189,
"weight": 224,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@@ -7723,7 +7706,7 @@
"x-appwrite": {
"method": "get",
"group": null,
"weight": 49,
"weight": 60,
"cookies": false,
"type": "",
"demo": "locale\/get.md",
@@ -7777,7 +7760,7 @@
"x-appwrite": {
"method": "listCodes",
"group": null,
"weight": 50,
"weight": 61,
"cookies": false,
"type": "",
"demo": "locale\/list-codes.md",
@@ -7831,7 +7814,7 @@
"x-appwrite": {
"method": "listContinents",
"group": null,
"weight": 54,
"weight": 65,
"cookies": false,
"type": "",
"demo": "locale\/list-continents.md",
@@ -7885,7 +7868,7 @@
"x-appwrite": {
"method": "listCountries",
"group": null,
"weight": 51,
"weight": 62,
"cookies": false,
"type": "",
"demo": "locale\/list-countries.md",
@@ -7939,7 +7922,7 @@
"x-appwrite": {
"method": "listCountriesEU",
"group": null,
"weight": 52,
"weight": 63,
"cookies": false,
"type": "",
"demo": "locale\/list-countries-eu.md",
@@ -7993,7 +7976,7 @@
"x-appwrite": {
"method": "listCountriesPhones",
"group": null,
"weight": 53,
"weight": 64,
"cookies": false,
"type": "",
"demo": "locale\/list-countries-phones.md",
@@ -8047,7 +8030,7 @@
"x-appwrite": {
"method": "listCurrencies",
"group": null,
"weight": 55,
"weight": 66,
"cookies": false,
"type": "",
"demo": "locale\/list-currencies.md",
@@ -8101,7 +8084,7 @@
"x-appwrite": {
"method": "listLanguages",
"group": null,
"weight": 56,
"weight": 67,
"cookies": false,
"type": "",
"demo": "locale\/list-languages.md",
@@ -8155,7 +8138,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 237,
"weight": 272,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@@ -8239,7 +8222,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 241,
"weight": 276,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@@ -8315,7 +8298,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 543,
"weight": 525,
"cookies": false,
"type": "",
"demo": "storage\/list-files.md",
@@ -8414,7 +8397,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 541,
"weight": 523,
"cookies": false,
"type": "upload",
"demo": "storage\/create-file.md",
@@ -8469,8 +8452,7 @@
"file": {
"type": "string",
"description": "Binary file. Appwrite SDKs provide helpers to handle file input. [Learn about file input](https:\/\/appwrite.io\/docs\/products\/storage\/upload-download#input-file).",
"x-example": null,
"format": "binary"
"x-example": null
},
"permissions": {
"type": "array",
@@ -8516,7 +8498,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 542,
"weight": 524,
"cookies": false,
"type": "",
"demo": "storage\/get-file.md",
@@ -8590,7 +8572,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 544,
"weight": 526,
"cookies": false,
"type": "",
"demo": "storage\/update-file.md",
@@ -8682,7 +8664,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 545,
"weight": 527,
"cookies": false,
"type": "",
"demo": "storage\/delete-file.md",
@@ -8751,7 +8733,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 547,
"weight": 529,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-download.md",
@@ -8831,7 +8813,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 546,
"weight": 528,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-preview.md",
@@ -9061,7 +9043,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 548,
"weight": 530,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-view.md",
@@ -9148,7 +9130,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 419,
"weight": 427,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-transactions.md",
@@ -9218,7 +9200,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 415,
"weight": 423,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-transaction.md",
@@ -9258,8 +9240,7 @@
"ttl": {
"type": "integer",
"description": "Seconds before the transaction expires.",
"x-example": 60,
"format": "int32"
"x-example": 60
}
}
}
@@ -9292,7 +9273,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 416,
"weight": 424,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-transaction.md",
@@ -9359,7 +9340,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 417,
"weight": 425,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-transaction.md",
@@ -9440,7 +9421,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 418,
"weight": 426,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-transaction.md",
@@ -9509,7 +9490,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 420,
"weight": 428,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-operations.md",
@@ -9597,7 +9578,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 411,
"weight": 419,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@@ -9708,7 +9689,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 403,
"weight": 411,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@@ -9864,7 +9845,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 404,
"weight": 412,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@@ -9974,7 +9955,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 407,
"weight": 415,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@@ -10124,7 +10105,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 405,
"weight": 413,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@@ -10235,7 +10216,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 409,
"weight": 417,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@@ -10341,7 +10322,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 414,
"weight": 422,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@@ -10422,14 +10403,12 @@
"value": {
"type": "number",
"description": "Value to increment the column by. The value must be a number.",
"x-example": null,
"format": "float"
"x-example": null
},
"min": {
"type": "number",
"description": "Minimum value for the column. If the current value is lesser than this value, an exception will be thrown.",
"x-example": null,
"format": "float",
"x-nullable": true
},
"transactionId": {
@@ -10469,7 +10448,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 413,
"weight": 421,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",
@@ -10550,14 +10529,12 @@
"value": {
"type": "number",
"description": "Value to increment the column by. The value must be a number.",
"x-example": null,
"format": "float"
"x-example": null
},
"max": {
"type": "number",
"description": "Maximum value for the column. If the current value is greater than this value, an error will be thrown.",
"x-example": null,
"format": "float",
"x-nullable": true
},
"transactionId": {
@@ -10597,7 +10574,7 @@
"x-appwrite": {
"method": "list",
"group": "teams",
"weight": 110,
"weight": 145,
"cookies": false,
"type": "",
"demo": "teams\/list.md",
@@ -10686,7 +10663,7 @@
"x-appwrite": {
"method": "create",
"group": "teams",
"weight": 109,
"weight": 144,
"cookies": false,
"type": "",
"demo": "teams\/create.md",
@@ -10773,7 +10750,7 @@
"x-appwrite": {
"method": "get",
"group": "teams",
"weight": 111,
"weight": 146,
"cookies": false,
"type": "",
"demo": "teams\/get.md",
@@ -10837,7 +10814,7 @@
"x-appwrite": {
"method": "updateName",
"group": "teams",
"weight": 113,
"weight": 148,
"cookies": false,
"type": "",
"demo": "teams\/update-name.md",
@@ -10913,7 +10890,7 @@
"x-appwrite": {
"method": "delete",
"group": "teams",
"weight": 115,
"weight": 150,
"cookies": false,
"type": "",
"demo": "teams\/delete.md",
@@ -10979,7 +10956,7 @@
"x-appwrite": {
"method": "listMemberships",
"group": "memberships",
"weight": 117,
"weight": 152,
"cookies": false,
"type": "",
"demo": "teams\/list-memberships.md",
@@ -11078,7 +11055,7 @@
"x-appwrite": {
"method": "createMembership",
"group": "memberships",
"weight": 116,
"weight": 151,
"cookies": false,
"type": "",
"demo": "teams\/create-membership.md",
@@ -11127,8 +11104,7 @@
"email": {
"type": "string",
"description": "Email of the new team member.",
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"userId": {
"type": "string",
@@ -11138,8 +11114,7 @@
"phone": {
"type": "string",
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
"x-example": "+12065550100",
"format": "phone"
"x-example": "+12065550100"
},
"roles": {
"type": "array",
@@ -11159,8 +11134,7 @@
"url": {
"type": "string",
"description": "URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.",
"x-example": "https:\/\/example.com",
"format": "url"
"x-example": "https:\/\/example.com"
},
"name": {
"type": "string",
@@ -11201,7 +11175,7 @@
"x-appwrite": {
"method": "getMembership",
"group": "memberships",
"weight": 118,
"weight": 153,
"cookies": false,
"type": "",
"demo": "teams\/get-membership.md",
@@ -11275,7 +11249,7 @@
"x-appwrite": {
"method": "updateMembership",
"group": "memberships",
"weight": 119,
"weight": 154,
"cookies": false,
"type": "",
"demo": "teams\/update-membership.md",
@@ -11371,7 +11345,7 @@
"x-appwrite": {
"method": "deleteMembership",
"group": "memberships",
"weight": 121,
"weight": 156,
"cookies": false,
"type": "",
"demo": "teams\/delete-membership.md",
@@ -11447,7 +11421,7 @@
"x-appwrite": {
"method": "updateMembershipStatus",
"group": "memberships",
"weight": 120,
"weight": 155,
"cookies": false,
"type": "",
"demo": "teams\/update-membership-status.md",
@@ -11547,7 +11521,7 @@
"x-appwrite": {
"method": "getPrefs",
"group": "teams",
"weight": 112,
"weight": 147,
"cookies": false,
"type": "",
"demo": "teams\/get-prefs.md",
@@ -11610,7 +11584,7 @@
"x-appwrite": {
"method": "updatePrefs",
"group": "teams",
"weight": 114,
"weight": 149,
"cookies": false,
"type": "",
"demo": "teams\/update-prefs.md",
@@ -13454,16 +13428,6 @@
"description": "Total number of chunks uploaded",
"x-example": 17890,
"format": "int32"
},
"encryption": {
"type": "boolean",
"description": "Whether file contents are encrypted at rest.",
"x-example": true
},
"compression": {
"type": "string",
"description": "Compression algorithm used for the file. Will be one of none, [gzip](https:\/\/en.wikipedia.org\/wiki\/Gzip), or [zstd](https:\/\/en.wikipedia.org\/wiki\/Zstd).",
"x-example": "gzip"
}
},
"required": [
@@ -13477,9 +13441,7 @@
"mimeType",
"sizeOriginal",
"chunksTotal",
"chunksUploaded",
"encryption",
"compression"
"chunksUploaded"
],
"example": {
"$id": "5e5ea5c16897e",
@@ -13494,9 +13456,7 @@
"mimeType": "image\/png",
"sizeOriginal": 17890,
"chunksTotal": 17890,
"chunksUploaded": 17890,
"encryption": true,
"compression": "gzip"
"chunksUploaded": 17890
}
},
"team": {
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+116 -156
View File
@@ -140,8 +140,7 @@
"email": {
"type": "string",
"description": "User email.",
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"password": {
"type": "string",
@@ -225,14 +224,12 @@
"email": {
"type": "string",
"description": "User email.",
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"password": {
"type": "string",
"description": "User password. Must be at least 8 chars.",
"x-example": "password",
"format": "password"
"x-example": "password"
}
},
"required": [
@@ -442,8 +439,7 @@
"duration": {
"type": "integer",
"description": "Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.",
"x-example": 0,
"format": "int32"
"x-example": 0
}
}
}
@@ -555,7 +551,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 253,
"weight": 288,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@@ -627,7 +623,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 255,
"weight": 290,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@@ -751,7 +747,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 256,
"weight": 291,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@@ -891,7 +887,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 257,
"weight": 292,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@@ -1015,7 +1011,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 261,
"weight": 296,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@@ -1149,7 +1145,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 262,
"weight": 297,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@@ -1287,7 +1283,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 254,
"weight": 289,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@@ -1388,7 +1384,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 260,
"weight": 295,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@@ -1487,7 +1483,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 258,
"weight": 293,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@@ -1586,7 +1582,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 259,
"weight": 294,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@@ -1800,8 +1796,7 @@
"oldPassword": {
"type": "string",
"description": "Current user password. Must be at least 8 chars.",
"x-example": "password",
"format": "password"
"x-example": "password"
}
},
"required": [
@@ -1873,14 +1868,12 @@
"phone": {
"type": "string",
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
"x-example": "+12065550100",
"format": "phone"
"x-example": "+12065550100"
},
"password": {
"type": "string",
"description": "User password. Must be at least 8 chars.",
"x-example": "password",
"format": "password"
"x-example": "password"
}
},
"required": [
@@ -2079,14 +2072,12 @@
"email": {
"type": "string",
"description": "User email.",
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"url": {
"type": "string",
"description": "URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.",
"x-example": "https:\/\/example.com",
"format": "url"
"x-example": "https:\/\/example.com"
}
},
"required": [
@@ -2391,14 +2382,12 @@
"email": {
"type": "string",
"description": "User email.",
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"password": {
"type": "string",
"description": "User password. Must be at least 8 chars.",
"x-example": "password",
"format": "password"
"x-example": "password"
}
},
"required": [
@@ -2584,7 +2573,9 @@
"yammer",
"yandex",
"zoho",
"zoom"
"zoom",
"mock",
"mock-unverified"
],
"x-enum-name": "OAuthProvider",
"x-enum-keys": []
@@ -3313,8 +3304,7 @@
"email": {
"type": "string",
"description": "User email.",
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"phrase": {
"type": "boolean",
@@ -3400,14 +3390,12 @@
"email": {
"type": "string",
"description": "User email.",
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"url": {
"type": "string",
"description": "URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.",
"x-example": "https:\/\/example.com",
"format": "url"
"x-example": "https:\/\/example.com"
},
"phrase": {
"type": "boolean",
@@ -3516,7 +3504,9 @@
"yammer",
"yandex",
"zoho",
"zoom"
"zoom",
"mock",
"mock-unverified"
],
"x-enum-name": "OAuthProvider",
"x-enum-keys": []
@@ -3631,8 +3621,7 @@
"phone": {
"type": "string",
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
"x-example": "+12065550100",
"format": "phone"
"x-example": "+12065550100"
}
},
"required": [
@@ -3757,8 +3746,7 @@
"url": {
"type": "string",
"description": "URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.",
"x-example": "https:\/\/example.com",
"format": "url"
"x-example": "https:\/\/example.com"
}
},
"required": [
@@ -4051,7 +4039,7 @@
"x-appwrite": {
"method": "getBrowser",
"group": null,
"weight": 264,
"weight": 50,
"cookies": false,
"type": "location",
"demo": "avatars\/get-browser.md",
@@ -4179,7 +4167,7 @@
"x-appwrite": {
"method": "getCreditCard",
"group": null,
"weight": 263,
"weight": 49,
"cookies": false,
"type": "location",
"demo": "avatars\/get-credit-card.md",
@@ -4313,7 +4301,7 @@
"x-appwrite": {
"method": "getFavicon",
"group": null,
"weight": 267,
"weight": 53,
"cookies": false,
"type": "location",
"demo": "avatars\/get-favicon.md",
@@ -4373,7 +4361,7 @@
"x-appwrite": {
"method": "getFlag",
"group": null,
"weight": 265,
"weight": 51,
"cookies": false,
"type": "location",
"demo": "avatars\/get-flag.md",
@@ -4863,7 +4851,7 @@
"x-appwrite": {
"method": "getImage",
"group": null,
"weight": 266,
"weight": 52,
"cookies": false,
"type": "location",
"demo": "avatars\/get-image.md",
@@ -4947,7 +4935,7 @@
"x-appwrite": {
"method": "getInitials",
"group": null,
"weight": 269,
"weight": 55,
"cookies": false,
"type": "location",
"demo": "avatars\/get-initials.md",
@@ -5041,7 +5029,7 @@
"x-appwrite": {
"method": "getQR",
"group": null,
"weight": 268,
"weight": 54,
"cookies": false,
"type": "location",
"demo": "avatars\/get-qr.md",
@@ -5135,7 +5123,7 @@
"x-appwrite": {
"method": "getScreenshot",
"group": null,
"weight": 270,
"weight": 56,
"cookies": false,
"type": "location",
"demo": "avatars\/get-screenshot.md",
@@ -5888,7 +5876,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 346,
"weight": 362,
"cookies": false,
"type": "",
"demo": "databases\/list-transactions.md",
@@ -5955,7 +5943,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 342,
"weight": 358,
"cookies": false,
"type": "",
"demo": "databases\/create-transaction.md",
@@ -5992,8 +5980,7 @@
"ttl": {
"type": "integer",
"description": "Seconds before the transaction expires.",
"x-example": 60,
"format": "int32"
"x-example": 60
}
}
}
@@ -6026,7 +6013,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 343,
"weight": 359,
"cookies": false,
"type": "",
"demo": "databases\/get-transaction.md",
@@ -6090,7 +6077,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 344,
"weight": 360,
"cookies": false,
"type": "",
"demo": "databases\/update-transaction.md",
@@ -6168,7 +6155,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 345,
"weight": 361,
"cookies": false,
"type": "",
"demo": "databases\/delete-transaction.md",
@@ -6234,7 +6221,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 347,
"weight": 363,
"cookies": false,
"type": "",
"demo": "databases\/create-operations.md",
@@ -6319,7 +6306,7 @@
"x-appwrite": {
"method": "listDocuments",
"group": "documents",
"weight": 297,
"weight": 321,
"cookies": false,
"type": "",
"demo": "databases\/list-documents.md",
@@ -6431,7 +6418,7 @@
"x-appwrite": {
"method": "createDocument",
"group": "documents",
"weight": 289,
"weight": 313,
"cookies": false,
"type": "",
"demo": "databases\/create-document.md",
@@ -6592,7 +6579,7 @@
"x-appwrite": {
"method": "getDocument",
"group": "documents",
"weight": 290,
"weight": 314,
"cookies": false,
"type": "",
"demo": "databases\/get-document.md",
@@ -6703,7 +6690,7 @@
"x-appwrite": {
"method": "upsertDocument",
"group": "documents",
"weight": 293,
"weight": 317,
"cookies": false,
"type": "",
"demo": "databases\/upsert-document.md",
@@ -6858,7 +6845,7 @@
"x-appwrite": {
"method": "updateDocument",
"group": "documents",
"weight": 291,
"weight": 315,
"cookies": false,
"type": "",
"demo": "databases\/update-document.md",
@@ -6970,7 +6957,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 295,
"weight": 319,
"cookies": false,
"type": "",
"demo": "databases\/delete-document.md",
@@ -7077,7 +7064,7 @@
"x-appwrite": {
"method": "decrementDocumentAttribute",
"group": "documents",
"weight": 300,
"weight": 324,
"cookies": false,
"type": "",
"demo": "databases\/decrement-document-attribute.md",
@@ -7159,14 +7146,12 @@
"value": {
"type": "number",
"description": "Value to increment the attribute by. The value must be a number.",
"x-example": null,
"format": "float"
"x-example": null
},
"min": {
"type": "number",
"description": "Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.",
"x-example": null,
"format": "float",
"x-nullable": true
},
"transactionId": {
@@ -7206,7 +7191,7 @@
"x-appwrite": {
"method": "incrementDocumentAttribute",
"group": "documents",
"weight": 299,
"weight": 323,
"cookies": false,
"type": "",
"demo": "databases\/increment-document-attribute.md",
@@ -7288,14 +7273,12 @@
"value": {
"type": "number",
"description": "Value to increment the attribute by. The value must be a number.",
"x-example": null,
"format": "float"
"x-example": null
},
"max": {
"type": "number",
"description": "Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.",
"x-example": null,
"format": "float",
"x-nullable": true
},
"transactionId": {
@@ -7335,7 +7318,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 447,
"weight": 455,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@@ -7422,7 +7405,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 445,
"weight": 453,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@@ -7540,7 +7523,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 446,
"weight": 454,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@@ -7615,7 +7598,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 190,
"weight": 225,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@@ -7669,7 +7652,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 189,
"weight": 224,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@@ -7723,7 +7706,7 @@
"x-appwrite": {
"method": "get",
"group": null,
"weight": 49,
"weight": 60,
"cookies": false,
"type": "",
"demo": "locale\/get.md",
@@ -7777,7 +7760,7 @@
"x-appwrite": {
"method": "listCodes",
"group": null,
"weight": 50,
"weight": 61,
"cookies": false,
"type": "",
"demo": "locale\/list-codes.md",
@@ -7831,7 +7814,7 @@
"x-appwrite": {
"method": "listContinents",
"group": null,
"weight": 54,
"weight": 65,
"cookies": false,
"type": "",
"demo": "locale\/list-continents.md",
@@ -7885,7 +7868,7 @@
"x-appwrite": {
"method": "listCountries",
"group": null,
"weight": 51,
"weight": 62,
"cookies": false,
"type": "",
"demo": "locale\/list-countries.md",
@@ -7939,7 +7922,7 @@
"x-appwrite": {
"method": "listCountriesEU",
"group": null,
"weight": 52,
"weight": 63,
"cookies": false,
"type": "",
"demo": "locale\/list-countries-eu.md",
@@ -7993,7 +7976,7 @@
"x-appwrite": {
"method": "listCountriesPhones",
"group": null,
"weight": 53,
"weight": 64,
"cookies": false,
"type": "",
"demo": "locale\/list-countries-phones.md",
@@ -8047,7 +8030,7 @@
"x-appwrite": {
"method": "listCurrencies",
"group": null,
"weight": 55,
"weight": 66,
"cookies": false,
"type": "",
"demo": "locale\/list-currencies.md",
@@ -8101,7 +8084,7 @@
"x-appwrite": {
"method": "listLanguages",
"group": null,
"weight": 56,
"weight": 67,
"cookies": false,
"type": "",
"demo": "locale\/list-languages.md",
@@ -8155,7 +8138,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 237,
"weight": 272,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@@ -8239,7 +8222,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 241,
"weight": 276,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@@ -8315,7 +8298,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 543,
"weight": 526,
"cookies": false,
"type": "",
"demo": "storage\/list-files.md",
@@ -8414,7 +8397,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 541,
"weight": 524,
"cookies": false,
"type": "upload",
"demo": "storage\/create-file.md",
@@ -8469,8 +8452,7 @@
"file": {
"type": "string",
"description": "Binary file. Appwrite SDKs provide helpers to handle file input. [Learn about file input](https:\/\/appwrite.io\/docs\/products\/storage\/upload-download#input-file).",
"x-example": null,
"format": "binary"
"x-example": null
},
"permissions": {
"type": "array",
@@ -8516,7 +8498,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 542,
"weight": 525,
"cookies": false,
"type": "",
"demo": "storage\/get-file.md",
@@ -8590,7 +8572,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 544,
"weight": 527,
"cookies": false,
"type": "",
"demo": "storage\/update-file.md",
@@ -8682,7 +8664,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 545,
"weight": 528,
"cookies": false,
"type": "",
"demo": "storage\/delete-file.md",
@@ -8751,7 +8733,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 547,
"weight": 530,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-download.md",
@@ -8831,7 +8813,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 546,
"weight": 529,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-preview.md",
@@ -9061,7 +9043,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 548,
"weight": 531,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-view.md",
@@ -9148,7 +9130,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 419,
"weight": 427,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-transactions.md",
@@ -9218,7 +9200,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 415,
"weight": 423,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-transaction.md",
@@ -9258,8 +9240,7 @@
"ttl": {
"type": "integer",
"description": "Seconds before the transaction expires.",
"x-example": 60,
"format": "int32"
"x-example": 60
}
}
}
@@ -9292,7 +9273,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 416,
"weight": 424,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-transaction.md",
@@ -9359,7 +9340,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 417,
"weight": 425,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-transaction.md",
@@ -9440,7 +9421,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 418,
"weight": 426,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-transaction.md",
@@ -9509,7 +9490,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 420,
"weight": 428,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-operations.md",
@@ -9597,7 +9578,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 411,
"weight": 419,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@@ -9708,7 +9689,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 403,
"weight": 411,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@@ -9864,7 +9845,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 404,
"weight": 412,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@@ -9974,7 +9955,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 407,
"weight": 415,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@@ -10124,7 +10105,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 405,
"weight": 413,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@@ -10235,7 +10216,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 409,
"weight": 417,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@@ -10341,7 +10322,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 414,
"weight": 422,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@@ -10422,14 +10403,12 @@
"value": {
"type": "number",
"description": "Value to increment the column by. The value must be a number.",
"x-example": null,
"format": "float"
"x-example": null
},
"min": {
"type": "number",
"description": "Minimum value for the column. If the current value is lesser than this value, an exception will be thrown.",
"x-example": null,
"format": "float",
"x-nullable": true
},
"transactionId": {
@@ -10469,7 +10448,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 413,
"weight": 421,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",
@@ -10550,14 +10529,12 @@
"value": {
"type": "number",
"description": "Value to increment the column by. The value must be a number.",
"x-example": null,
"format": "float"
"x-example": null
},
"max": {
"type": "number",
"description": "Maximum value for the column. If the current value is greater than this value, an error will be thrown.",
"x-example": null,
"format": "float",
"x-nullable": true
},
"transactionId": {
@@ -10597,7 +10574,7 @@
"x-appwrite": {
"method": "list",
"group": "teams",
"weight": 110,
"weight": 145,
"cookies": false,
"type": "",
"demo": "teams\/list.md",
@@ -10686,7 +10663,7 @@
"x-appwrite": {
"method": "create",
"group": "teams",
"weight": 109,
"weight": 144,
"cookies": false,
"type": "",
"demo": "teams\/create.md",
@@ -10773,7 +10750,7 @@
"x-appwrite": {
"method": "get",
"group": "teams",
"weight": 111,
"weight": 146,
"cookies": false,
"type": "",
"demo": "teams\/get.md",
@@ -10837,7 +10814,7 @@
"x-appwrite": {
"method": "updateName",
"group": "teams",
"weight": 113,
"weight": 148,
"cookies": false,
"type": "",
"demo": "teams\/update-name.md",
@@ -10913,7 +10890,7 @@
"x-appwrite": {
"method": "delete",
"group": "teams",
"weight": 115,
"weight": 150,
"cookies": false,
"type": "",
"demo": "teams\/delete.md",
@@ -10979,7 +10956,7 @@
"x-appwrite": {
"method": "listMemberships",
"group": "memberships",
"weight": 117,
"weight": 152,
"cookies": false,
"type": "",
"demo": "teams\/list-memberships.md",
@@ -11078,7 +11055,7 @@
"x-appwrite": {
"method": "createMembership",
"group": "memberships",
"weight": 116,
"weight": 151,
"cookies": false,
"type": "",
"demo": "teams\/create-membership.md",
@@ -11127,8 +11104,7 @@
"email": {
"type": "string",
"description": "Email of the new team member.",
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"userId": {
"type": "string",
@@ -11138,8 +11114,7 @@
"phone": {
"type": "string",
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
"x-example": "+12065550100",
"format": "phone"
"x-example": "+12065550100"
},
"roles": {
"type": "array",
@@ -11159,8 +11134,7 @@
"url": {
"type": "string",
"description": "URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.",
"x-example": "https:\/\/example.com",
"format": "url"
"x-example": "https:\/\/example.com"
},
"name": {
"type": "string",
@@ -11201,7 +11175,7 @@
"x-appwrite": {
"method": "getMembership",
"group": "memberships",
"weight": 118,
"weight": 153,
"cookies": false,
"type": "",
"demo": "teams\/get-membership.md",
@@ -11275,7 +11249,7 @@
"x-appwrite": {
"method": "updateMembership",
"group": "memberships",
"weight": 119,
"weight": 154,
"cookies": false,
"type": "",
"demo": "teams\/update-membership.md",
@@ -11371,7 +11345,7 @@
"x-appwrite": {
"method": "deleteMembership",
"group": "memberships",
"weight": 121,
"weight": 156,
"cookies": false,
"type": "",
"demo": "teams\/delete-membership.md",
@@ -11447,7 +11421,7 @@
"x-appwrite": {
"method": "updateMembershipStatus",
"group": "memberships",
"weight": 120,
"weight": 155,
"cookies": false,
"type": "",
"demo": "teams\/update-membership-status.md",
@@ -11547,7 +11521,7 @@
"x-appwrite": {
"method": "getPrefs",
"group": "teams",
"weight": 112,
"weight": 147,
"cookies": false,
"type": "",
"demo": "teams\/get-prefs.md",
@@ -11610,7 +11584,7 @@
"x-appwrite": {
"method": "updatePrefs",
"group": "teams",
"weight": 114,
"weight": 149,
"cookies": false,
"type": "",
"demo": "teams\/update-prefs.md",
@@ -13454,16 +13428,6 @@
"description": "Total number of chunks uploaded",
"x-example": 17890,
"format": "int32"
},
"encryption": {
"type": "boolean",
"description": "Whether file contents are encrypted at rest.",
"x-example": true
},
"compression": {
"type": "string",
"description": "Compression algorithm used for the file. Will be one of none, [gzip](https:\/\/en.wikipedia.org\/wiki\/Gzip), or [zstd](https:\/\/en.wikipedia.org\/wiki\/Zstd).",
"x-example": "gzip"
}
},
"required": [
@@ -13477,9 +13441,7 @@
"mimeType",
"sizeOriginal",
"chunksTotal",
"chunksUploaded",
"encryption",
"compression"
"chunksUploaded"
],
"example": {
"$id": "5e5ea5c16897e",
@@ -13494,9 +13456,7 @@
"mimeType": "image\/png",
"sizeOriginal": 17890,
"chunksTotal": 17890,
"chunksUploaded": 17890,
"encryption": true,
"compression": "gzip"
"chunksUploaded": 17890
}
},
"team": {
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+116 -155
View File
@@ -1,7 +1,7 @@
{
"swagger": "2.0",
"info": {
"version": "1.8.1",
"version": "1.8.0",
"title": "Appwrite",
"description": "Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https:\/\/appwrite.io\/docs](https:\/\/appwrite.io\/docs)",
"termsOfService": "https:\/\/appwrite.io\/policy\/terms",
@@ -191,8 +191,7 @@
"type": "string",
"description": "User email.",
"default": null,
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"password": {
"type": "string",
@@ -281,15 +280,13 @@
"type": "string",
"description": "User email.",
"default": null,
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"password": {
"type": "string",
"description": "User password. Must be at least 8 chars.",
"default": null,
"x-example": "password",
"format": "password"
"x-example": "password"
}
},
"required": [
@@ -501,8 +498,7 @@
"type": "integer",
"description": "Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.",
"default": 900,
"x-example": 0,
"format": "int32"
"x-example": 0
}
}
}
@@ -612,7 +608,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 253,
"weight": 288,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@@ -687,7 +683,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 255,
"weight": 290,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@@ -811,7 +807,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 256,
"weight": 291,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@@ -952,7 +948,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 257,
"weight": 292,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@@ -1076,7 +1072,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 261,
"weight": 296,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@@ -1213,7 +1209,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 262,
"weight": 297,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@@ -1353,7 +1349,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 254,
"weight": 289,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@@ -1454,7 +1450,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 260,
"weight": 295,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@@ -1555,7 +1551,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 258,
"weight": 293,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@@ -1656,7 +1652,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 259,
"weight": 294,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@@ -1878,8 +1874,7 @@
"type": "string",
"description": "Current user password. Must be at least 8 chars.",
"default": "",
"x-example": "password",
"format": "password"
"x-example": "password"
}
},
"required": [
@@ -1954,15 +1949,13 @@
"type": "string",
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
"default": null,
"x-example": "+12065550100",
"format": "phone"
"x-example": "+12065550100"
},
"password": {
"type": "string",
"description": "User password. Must be at least 8 chars.",
"default": null,
"x-example": "password",
"format": "password"
"x-example": "password"
}
},
"required": [
@@ -2167,15 +2160,13 @@
"type": "string",
"description": "User email.",
"default": null,
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"url": {
"type": "string",
"description": "URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.",
"default": null,
"x-example": "https:\/\/example.com",
"format": "url"
"x-example": "https:\/\/example.com"
}
},
"required": [
@@ -2494,15 +2485,13 @@
"type": "string",
"description": "User email.",
"default": null,
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"password": {
"type": "string",
"description": "User password. Must be at least 8 chars.",
"default": null,
"x-example": "password",
"format": "password"
"x-example": "password"
}
},
"required": [
@@ -2694,7 +2683,9 @@
"yammer",
"yandex",
"zoho",
"zoom"
"zoom",
"mock",
"mock-unverified"
],
"x-enum-name": "OAuthProvider",
"x-enum-keys": [],
@@ -3438,8 +3429,7 @@
"type": "string",
"description": "User email.",
"default": null,
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"phrase": {
"type": "boolean",
@@ -3530,15 +3520,13 @@
"type": "string",
"description": "User email.",
"default": null,
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"url": {
"type": "string",
"description": "URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.",
"default": "",
"x-example": "https:\/\/example.com",
"format": "url"
"x-example": "https:\/\/example.com"
},
"phrase": {
"type": "boolean",
@@ -3650,7 +3638,9 @@
"yammer",
"yandex",
"zoho",
"zoom"
"zoom",
"mock",
"mock-unverified"
],
"x-enum-name": "OAuthProvider",
"x-enum-keys": [],
@@ -3764,8 +3754,7 @@
"type": "string",
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
"default": null,
"x-example": "+12065550100",
"format": "phone"
"x-example": "+12065550100"
}
},
"required": [
@@ -3893,8 +3882,7 @@
"type": "string",
"description": "URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.",
"default": null,
"x-example": "https:\/\/example.com",
"format": "url"
"x-example": "https:\/\/example.com"
}
},
"required": [
@@ -4203,7 +4191,7 @@
"x-appwrite": {
"method": "getBrowser",
"group": null,
"weight": 264,
"weight": 50,
"cookies": false,
"type": "location",
"demo": "avatars\/get-browser.md",
@@ -4329,7 +4317,7 @@
"x-appwrite": {
"method": "getCreditCard",
"group": null,
"weight": 263,
"weight": 49,
"cookies": false,
"type": "location",
"demo": "avatars\/get-credit-card.md",
@@ -4461,7 +4449,7 @@
"x-appwrite": {
"method": "getFavicon",
"group": null,
"weight": 267,
"weight": 53,
"cookies": false,
"type": "location",
"demo": "avatars\/get-favicon.md",
@@ -4525,7 +4513,7 @@
"x-appwrite": {
"method": "getFlag",
"group": null,
"weight": 265,
"weight": 51,
"cookies": false,
"type": "location",
"demo": "avatars\/get-flag.md",
@@ -5013,7 +5001,7 @@
"x-appwrite": {
"method": "getImage",
"group": null,
"weight": 266,
"weight": 52,
"cookies": false,
"type": "location",
"demo": "avatars\/get-image.md",
@@ -5097,7 +5085,7 @@
"x-appwrite": {
"method": "getInitials",
"group": null,
"weight": 269,
"weight": 55,
"cookies": false,
"type": "location",
"demo": "avatars\/get-initials.md",
@@ -5189,7 +5177,7 @@
"x-appwrite": {
"method": "getQR",
"group": null,
"weight": 268,
"weight": 54,
"cookies": false,
"type": "location",
"demo": "avatars\/get-qr.md",
@@ -5281,7 +5269,7 @@
"x-appwrite": {
"method": "getScreenshot",
"group": null,
"weight": 270,
"weight": 56,
"cookies": false,
"type": "location",
"demo": "avatars\/get-screenshot.md",
@@ -5994,7 +5982,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 346,
"weight": 362,
"cookies": false,
"type": "",
"demo": "databases\/list-transactions.md",
@@ -6061,7 +6049,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 342,
"weight": 358,
"cookies": false,
"type": "",
"demo": "databases\/create-transaction.md",
@@ -6099,8 +6087,7 @@
"type": "integer",
"description": "Seconds before the transaction expires.",
"default": 300,
"x-example": 60,
"format": "int32"
"x-example": 60
}
}
}
@@ -6132,7 +6119,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 343,
"weight": 359,
"cookies": false,
"type": "",
"demo": "databases\/get-transaction.md",
@@ -6195,7 +6182,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 344,
"weight": 360,
"cookies": false,
"type": "",
"demo": "databases\/update-transaction.md",
@@ -6274,7 +6261,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 345,
"weight": 361,
"cookies": false,
"type": "",
"demo": "databases\/delete-transaction.md",
@@ -6339,7 +6326,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 347,
"weight": 363,
"cookies": false,
"type": "",
"demo": "databases\/create-operations.md",
@@ -6420,7 +6407,7 @@
"x-appwrite": {
"method": "listDocuments",
"group": "documents",
"weight": 297,
"weight": 321,
"cookies": false,
"type": "",
"demo": "databases\/list-documents.md",
@@ -6524,7 +6511,7 @@
"x-appwrite": {
"method": "createDocument",
"group": "documents",
"weight": 289,
"weight": 313,
"cookies": false,
"type": "",
"demo": "databases\/create-document.md",
@@ -6683,7 +6670,7 @@
"x-appwrite": {
"method": "getDocument",
"group": "documents",
"weight": 290,
"weight": 314,
"cookies": false,
"type": "",
"demo": "databases\/get-document.md",
@@ -6786,7 +6773,7 @@
"x-appwrite": {
"method": "upsertDocument",
"group": "documents",
"weight": 293,
"weight": 317,
"cookies": false,
"type": "",
"demo": "databases\/upsert-document.md",
@@ -6937,7 +6924,7 @@
"x-appwrite": {
"method": "updateDocument",
"group": "documents",
"weight": 291,
"weight": 315,
"cookies": false,
"type": "",
"demo": "databases\/update-document.md",
@@ -7047,7 +7034,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 295,
"weight": 319,
"cookies": false,
"type": "",
"demo": "databases\/delete-document.md",
@@ -7148,7 +7135,7 @@
"x-appwrite": {
"method": "decrementDocumentAttribute",
"group": "documents",
"weight": 300,
"weight": 324,
"cookies": false,
"type": "",
"demo": "databases\/decrement-document-attribute.md",
@@ -7221,15 +7208,13 @@
"type": "number",
"description": "Value to increment the attribute by. The value must be a number.",
"default": 1,
"x-example": null,
"format": "float"
"x-example": null
},
"min": {
"type": "number",
"description": "Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.",
"default": null,
"x-example": null,
"format": "float",
"x-nullable": true
},
"transactionId": {
@@ -7271,7 +7256,7 @@
"x-appwrite": {
"method": "incrementDocumentAttribute",
"group": "documents",
"weight": 299,
"weight": 323,
"cookies": false,
"type": "",
"demo": "databases\/increment-document-attribute.md",
@@ -7344,15 +7329,13 @@
"type": "number",
"description": "Value to increment the attribute by. The value must be a number.",
"default": 1,
"x-example": null,
"format": "float"
"x-example": null
},
"max": {
"type": "number",
"description": "Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.",
"default": null,
"x-example": null,
"format": "float",
"x-nullable": true
},
"transactionId": {
@@ -7392,7 +7375,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 447,
"weight": 454,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@@ -7475,7 +7458,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 445,
"weight": 452,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@@ -7594,7 +7577,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 446,
"weight": 453,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@@ -7666,7 +7649,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 190,
"weight": 225,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@@ -7741,7 +7724,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 189,
"weight": 224,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@@ -7814,7 +7797,7 @@
"x-appwrite": {
"method": "get",
"group": null,
"weight": 49,
"weight": 60,
"cookies": false,
"type": "",
"demo": "locale\/get.md",
@@ -7867,7 +7850,7 @@
"x-appwrite": {
"method": "listCodes",
"group": null,
"weight": 50,
"weight": 61,
"cookies": false,
"type": "",
"demo": "locale\/list-codes.md",
@@ -7920,7 +7903,7 @@
"x-appwrite": {
"method": "listContinents",
"group": null,
"weight": 54,
"weight": 65,
"cookies": false,
"type": "",
"demo": "locale\/list-continents.md",
@@ -7973,7 +7956,7 @@
"x-appwrite": {
"method": "listCountries",
"group": null,
"weight": 51,
"weight": 62,
"cookies": false,
"type": "",
"demo": "locale\/list-countries.md",
@@ -8026,7 +8009,7 @@
"x-appwrite": {
"method": "listCountriesEU",
"group": null,
"weight": 52,
"weight": 63,
"cookies": false,
"type": "",
"demo": "locale\/list-countries-eu.md",
@@ -8079,7 +8062,7 @@
"x-appwrite": {
"method": "listCountriesPhones",
"group": null,
"weight": 53,
"weight": 64,
"cookies": false,
"type": "",
"demo": "locale\/list-countries-phones.md",
@@ -8132,7 +8115,7 @@
"x-appwrite": {
"method": "listCurrencies",
"group": null,
"weight": 55,
"weight": 66,
"cookies": false,
"type": "",
"demo": "locale\/list-currencies.md",
@@ -8185,7 +8168,7 @@
"x-appwrite": {
"method": "listLanguages",
"group": null,
"weight": 56,
"weight": 67,
"cookies": false,
"type": "",
"demo": "locale\/list-languages.md",
@@ -8240,7 +8223,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 237,
"weight": 272,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@@ -8325,7 +8308,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 241,
"weight": 276,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@@ -8396,7 +8379,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 543,
"weight": 525,
"cookies": false,
"type": "",
"demo": "storage\/list-files.md",
@@ -8489,7 +8472,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 541,
"weight": 523,
"cookies": false,
"type": "upload",
"demo": "storage\/create-file.md",
@@ -8580,7 +8563,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 542,
"weight": 524,
"cookies": false,
"type": "",
"demo": "storage\/get-file.md",
@@ -8651,7 +8634,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 544,
"weight": 526,
"cookies": false,
"type": "",
"demo": "storage\/update-file.md",
@@ -8742,7 +8725,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 545,
"weight": 527,
"cookies": false,
"type": "",
"demo": "storage\/delete-file.md",
@@ -8813,7 +8796,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 547,
"weight": 529,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-download.md",
@@ -8893,7 +8876,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 546,
"weight": 528,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-preview.md",
@@ -9101,7 +9084,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 548,
"weight": 530,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-view.md",
@@ -9181,7 +9164,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 419,
"weight": 427,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-transactions.md",
@@ -9251,7 +9234,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 415,
"weight": 423,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-transaction.md",
@@ -9292,8 +9275,7 @@
"type": "integer",
"description": "Seconds before the transaction expires.",
"default": 300,
"x-example": 60,
"format": "int32"
"x-example": 60
}
}
}
@@ -9325,7 +9307,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 416,
"weight": 424,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-transaction.md",
@@ -9391,7 +9373,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 417,
"weight": 425,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-transaction.md",
@@ -9473,7 +9455,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 418,
"weight": 426,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-transaction.md",
@@ -9541,7 +9523,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 420,
"weight": 428,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-operations.md",
@@ -9625,7 +9607,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 411,
"weight": 419,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@@ -9728,7 +9710,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 403,
"weight": 411,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@@ -9882,7 +9864,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 404,
"weight": 412,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@@ -9984,7 +9966,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 407,
"weight": 415,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@@ -10130,7 +10112,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 405,
"weight": 413,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@@ -10239,7 +10221,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 409,
"weight": 417,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@@ -10339,7 +10321,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 414,
"weight": 422,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@@ -10411,15 +10393,13 @@
"type": "number",
"description": "Value to increment the column by. The value must be a number.",
"default": 1,
"x-example": null,
"format": "float"
"x-example": null
},
"min": {
"type": "number",
"description": "Minimum value for the column. If the current value is lesser than this value, an exception will be thrown.",
"default": null,
"x-example": null,
"format": "float",
"x-nullable": true
},
"transactionId": {
@@ -10461,7 +10441,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 413,
"weight": 421,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",
@@ -10533,15 +10513,13 @@
"type": "number",
"description": "Value to increment the column by. The value must be a number.",
"default": 1,
"x-example": null,
"format": "float"
"x-example": null
},
"max": {
"type": "number",
"description": "Maximum value for the column. If the current value is greater than this value, an error will be thrown.",
"default": null,
"x-example": null,
"format": "float",
"x-nullable": true
},
"transactionId": {
@@ -10581,7 +10559,7 @@
"x-appwrite": {
"method": "list",
"group": "teams",
"weight": 110,
"weight": 145,
"cookies": false,
"type": "",
"demo": "teams\/list.md",
@@ -10666,7 +10644,7 @@
"x-appwrite": {
"method": "create",
"group": "teams",
"weight": 109,
"weight": 144,
"cookies": false,
"type": "",
"demo": "teams\/create.md",
@@ -10757,7 +10735,7 @@
"x-appwrite": {
"method": "get",
"group": "teams",
"weight": 111,
"weight": 146,
"cookies": false,
"type": "",
"demo": "teams\/get.md",
@@ -10820,7 +10798,7 @@
"x-appwrite": {
"method": "updateName",
"group": "teams",
"weight": 113,
"weight": 148,
"cookies": false,
"type": "",
"demo": "teams\/update-name.md",
@@ -10896,7 +10874,7 @@
"x-appwrite": {
"method": "delete",
"group": "teams",
"weight": 115,
"weight": 150,
"cookies": false,
"type": "",
"demo": "teams\/delete.md",
@@ -10959,7 +10937,7 @@
"x-appwrite": {
"method": "listMemberships",
"group": "memberships",
"weight": 117,
"weight": 152,
"cookies": false,
"type": "",
"demo": "teams\/list-memberships.md",
@@ -11052,7 +11030,7 @@
"x-appwrite": {
"method": "createMembership",
"group": "memberships",
"weight": 116,
"weight": 151,
"cookies": false,
"type": "",
"demo": "teams\/create-membership.md",
@@ -11098,8 +11076,7 @@
"type": "string",
"description": "Email of the new team member.",
"default": "",
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"userId": {
"type": "string",
@@ -11111,8 +11088,7 @@
"type": "string",
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
"default": "",
"x-example": "+12065550100",
"format": "phone"
"x-example": "+12065550100"
},
"roles": {
"type": "array",
@@ -11134,8 +11110,7 @@
"type": "string",
"description": "URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.",
"default": "",
"x-example": "https:\/\/example.com",
"format": "url"
"x-example": "https:\/\/example.com"
},
"name": {
"type": "string",
@@ -11176,7 +11151,7 @@
"x-appwrite": {
"method": "getMembership",
"group": "memberships",
"weight": 118,
"weight": 153,
"cookies": false,
"type": "",
"demo": "teams\/get-membership.md",
@@ -11247,7 +11222,7 @@
"x-appwrite": {
"method": "updateMembership",
"group": "memberships",
"weight": 119,
"weight": 154,
"cookies": false,
"type": "",
"demo": "teams\/update-membership.md",
@@ -11341,7 +11316,7 @@
"x-appwrite": {
"method": "deleteMembership",
"group": "memberships",
"weight": 121,
"weight": 156,
"cookies": false,
"type": "",
"demo": "teams\/delete-membership.md",
@@ -11414,7 +11389,7 @@
"x-appwrite": {
"method": "updateMembershipStatus",
"group": "memberships",
"weight": 120,
"weight": 155,
"cookies": false,
"type": "",
"demo": "teams\/update-membership-status.md",
@@ -11510,7 +11485,7 @@
"x-appwrite": {
"method": "getPrefs",
"group": "teams",
"weight": 112,
"weight": 147,
"cookies": false,
"type": "",
"demo": "teams\/get-prefs.md",
@@ -11573,7 +11548,7 @@
"x-appwrite": {
"method": "updatePrefs",
"group": "teams",
"weight": 114,
"weight": 149,
"cookies": false,
"type": "",
"demo": "teams\/update-prefs.md",
@@ -13393,16 +13368,6 @@
"description": "Total number of chunks uploaded",
"x-example": 17890,
"format": "int32"
},
"encryption": {
"type": "boolean",
"description": "Whether file contents are encrypted at rest.",
"x-example": true
},
"compression": {
"type": "string",
"description": "Compression algorithm used for the file. Will be one of none, [gzip](https:\/\/en.wikipedia.org\/wiki\/Gzip), or [zstd](https:\/\/en.wikipedia.org\/wiki\/Zstd).",
"x-example": "gzip"
}
},
"required": [
@@ -13416,9 +13381,7 @@
"mimeType",
"sizeOriginal",
"chunksTotal",
"chunksUploaded",
"encryption",
"compression"
"chunksUploaded"
],
"example": {
"$id": "5e5ea5c16897e",
@@ -13433,9 +13396,7 @@
"mimeType": "image\/png",
"sizeOriginal": 17890,
"chunksTotal": 17890,
"chunksUploaded": 17890,
"encryption": true,
"compression": "gzip"
"chunksUploaded": 17890
}
},
"team": {
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+115 -154
View File
@@ -191,8 +191,7 @@
"type": "string",
"description": "User email.",
"default": null,
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"password": {
"type": "string",
@@ -281,15 +280,13 @@
"type": "string",
"description": "User email.",
"default": null,
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"password": {
"type": "string",
"description": "User password. Must be at least 8 chars.",
"default": null,
"x-example": "password",
"format": "password"
"x-example": "password"
}
},
"required": [
@@ -501,8 +498,7 @@
"type": "integer",
"description": "Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.",
"default": 900,
"x-example": 0,
"format": "int32"
"x-example": 0
}
}
}
@@ -612,7 +608,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 253,
"weight": 288,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@@ -687,7 +683,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 255,
"weight": 290,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@@ -811,7 +807,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 256,
"weight": 291,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@@ -952,7 +948,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 257,
"weight": 292,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@@ -1076,7 +1072,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 261,
"weight": 296,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@@ -1213,7 +1209,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 262,
"weight": 297,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@@ -1353,7 +1349,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 254,
"weight": 289,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@@ -1454,7 +1450,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 260,
"weight": 295,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@@ -1555,7 +1551,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 258,
"weight": 293,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@@ -1656,7 +1652,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 259,
"weight": 294,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@@ -1878,8 +1874,7 @@
"type": "string",
"description": "Current user password. Must be at least 8 chars.",
"default": "",
"x-example": "password",
"format": "password"
"x-example": "password"
}
},
"required": [
@@ -1954,15 +1949,13 @@
"type": "string",
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
"default": null,
"x-example": "+12065550100",
"format": "phone"
"x-example": "+12065550100"
},
"password": {
"type": "string",
"description": "User password. Must be at least 8 chars.",
"default": null,
"x-example": "password",
"format": "password"
"x-example": "password"
}
},
"required": [
@@ -2167,15 +2160,13 @@
"type": "string",
"description": "User email.",
"default": null,
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"url": {
"type": "string",
"description": "URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.",
"default": null,
"x-example": "https:\/\/example.com",
"format": "url"
"x-example": "https:\/\/example.com"
}
},
"required": [
@@ -2494,15 +2485,13 @@
"type": "string",
"description": "User email.",
"default": null,
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"password": {
"type": "string",
"description": "User password. Must be at least 8 chars.",
"default": null,
"x-example": "password",
"format": "password"
"x-example": "password"
}
},
"required": [
@@ -2694,7 +2683,9 @@
"yammer",
"yandex",
"zoho",
"zoom"
"zoom",
"mock",
"mock-unverified"
],
"x-enum-name": "OAuthProvider",
"x-enum-keys": [],
@@ -3438,8 +3429,7 @@
"type": "string",
"description": "User email.",
"default": null,
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"phrase": {
"type": "boolean",
@@ -3530,15 +3520,13 @@
"type": "string",
"description": "User email.",
"default": null,
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"url": {
"type": "string",
"description": "URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.",
"default": "",
"x-example": "https:\/\/example.com",
"format": "url"
"x-example": "https:\/\/example.com"
},
"phrase": {
"type": "boolean",
@@ -3650,7 +3638,9 @@
"yammer",
"yandex",
"zoho",
"zoom"
"zoom",
"mock",
"mock-unverified"
],
"x-enum-name": "OAuthProvider",
"x-enum-keys": [],
@@ -3764,8 +3754,7 @@
"type": "string",
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
"default": null,
"x-example": "+12065550100",
"format": "phone"
"x-example": "+12065550100"
}
},
"required": [
@@ -3893,8 +3882,7 @@
"type": "string",
"description": "URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.",
"default": null,
"x-example": "https:\/\/example.com",
"format": "url"
"x-example": "https:\/\/example.com"
}
},
"required": [
@@ -4203,7 +4191,7 @@
"x-appwrite": {
"method": "getBrowser",
"group": null,
"weight": 264,
"weight": 50,
"cookies": false,
"type": "location",
"demo": "avatars\/get-browser.md",
@@ -4329,7 +4317,7 @@
"x-appwrite": {
"method": "getCreditCard",
"group": null,
"weight": 263,
"weight": 49,
"cookies": false,
"type": "location",
"demo": "avatars\/get-credit-card.md",
@@ -4461,7 +4449,7 @@
"x-appwrite": {
"method": "getFavicon",
"group": null,
"weight": 267,
"weight": 53,
"cookies": false,
"type": "location",
"demo": "avatars\/get-favicon.md",
@@ -4525,7 +4513,7 @@
"x-appwrite": {
"method": "getFlag",
"group": null,
"weight": 265,
"weight": 51,
"cookies": false,
"type": "location",
"demo": "avatars\/get-flag.md",
@@ -5013,7 +5001,7 @@
"x-appwrite": {
"method": "getImage",
"group": null,
"weight": 266,
"weight": 52,
"cookies": false,
"type": "location",
"demo": "avatars\/get-image.md",
@@ -5097,7 +5085,7 @@
"x-appwrite": {
"method": "getInitials",
"group": null,
"weight": 269,
"weight": 55,
"cookies": false,
"type": "location",
"demo": "avatars\/get-initials.md",
@@ -5189,7 +5177,7 @@
"x-appwrite": {
"method": "getQR",
"group": null,
"weight": 268,
"weight": 54,
"cookies": false,
"type": "location",
"demo": "avatars\/get-qr.md",
@@ -5281,7 +5269,7 @@
"x-appwrite": {
"method": "getScreenshot",
"group": null,
"weight": 270,
"weight": 56,
"cookies": false,
"type": "location",
"demo": "avatars\/get-screenshot.md",
@@ -5994,7 +5982,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 346,
"weight": 362,
"cookies": false,
"type": "",
"demo": "databases\/list-transactions.md",
@@ -6061,7 +6049,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 342,
"weight": 358,
"cookies": false,
"type": "",
"demo": "databases\/create-transaction.md",
@@ -6099,8 +6087,7 @@
"type": "integer",
"description": "Seconds before the transaction expires.",
"default": 300,
"x-example": 60,
"format": "int32"
"x-example": 60
}
}
}
@@ -6132,7 +6119,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 343,
"weight": 359,
"cookies": false,
"type": "",
"demo": "databases\/get-transaction.md",
@@ -6195,7 +6182,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 344,
"weight": 360,
"cookies": false,
"type": "",
"demo": "databases\/update-transaction.md",
@@ -6274,7 +6261,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 345,
"weight": 361,
"cookies": false,
"type": "",
"demo": "databases\/delete-transaction.md",
@@ -6339,7 +6326,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 347,
"weight": 363,
"cookies": false,
"type": "",
"demo": "databases\/create-operations.md",
@@ -6420,7 +6407,7 @@
"x-appwrite": {
"method": "listDocuments",
"group": "documents",
"weight": 297,
"weight": 321,
"cookies": false,
"type": "",
"demo": "databases\/list-documents.md",
@@ -6524,7 +6511,7 @@
"x-appwrite": {
"method": "createDocument",
"group": "documents",
"weight": 289,
"weight": 313,
"cookies": false,
"type": "",
"demo": "databases\/create-document.md",
@@ -6683,7 +6670,7 @@
"x-appwrite": {
"method": "getDocument",
"group": "documents",
"weight": 290,
"weight": 314,
"cookies": false,
"type": "",
"demo": "databases\/get-document.md",
@@ -6786,7 +6773,7 @@
"x-appwrite": {
"method": "upsertDocument",
"group": "documents",
"weight": 293,
"weight": 317,
"cookies": false,
"type": "",
"demo": "databases\/upsert-document.md",
@@ -6937,7 +6924,7 @@
"x-appwrite": {
"method": "updateDocument",
"group": "documents",
"weight": 291,
"weight": 315,
"cookies": false,
"type": "",
"demo": "databases\/update-document.md",
@@ -7047,7 +7034,7 @@
"x-appwrite": {
"method": "deleteDocument",
"group": "documents",
"weight": 295,
"weight": 319,
"cookies": false,
"type": "",
"demo": "databases\/delete-document.md",
@@ -7148,7 +7135,7 @@
"x-appwrite": {
"method": "decrementDocumentAttribute",
"group": "documents",
"weight": 300,
"weight": 324,
"cookies": false,
"type": "",
"demo": "databases\/decrement-document-attribute.md",
@@ -7221,15 +7208,13 @@
"type": "number",
"description": "Value to increment the attribute by. The value must be a number.",
"default": 1,
"x-example": null,
"format": "float"
"x-example": null
},
"min": {
"type": "number",
"description": "Minimum value for the attribute. If the current value is lesser than this value, an exception will be thrown.",
"default": null,
"x-example": null,
"format": "float",
"x-nullable": true
},
"transactionId": {
@@ -7271,7 +7256,7 @@
"x-appwrite": {
"method": "incrementDocumentAttribute",
"group": "documents",
"weight": 299,
"weight": 323,
"cookies": false,
"type": "",
"demo": "databases\/increment-document-attribute.md",
@@ -7344,15 +7329,13 @@
"type": "number",
"description": "Value to increment the attribute by. The value must be a number.",
"default": 1,
"x-example": null,
"format": "float"
"x-example": null
},
"max": {
"type": "number",
"description": "Maximum value for the attribute. If the current value is greater than this value, an error will be thrown.",
"default": null,
"x-example": null,
"format": "float",
"x-nullable": true
},
"transactionId": {
@@ -7392,7 +7375,7 @@
"x-appwrite": {
"method": "listExecutions",
"group": "executions",
"weight": 447,
"weight": 455,
"cookies": false,
"type": "",
"demo": "functions\/list-executions.md",
@@ -7475,7 +7458,7 @@
"x-appwrite": {
"method": "createExecution",
"group": "executions",
"weight": 445,
"weight": 453,
"cookies": false,
"type": "",
"demo": "functions\/create-execution.md",
@@ -7594,7 +7577,7 @@
"x-appwrite": {
"method": "getExecution",
"group": "executions",
"weight": 446,
"weight": 454,
"cookies": false,
"type": "",
"demo": "functions\/get-execution.md",
@@ -7666,7 +7649,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 190,
"weight": 225,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@@ -7741,7 +7724,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 189,
"weight": 224,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@@ -7814,7 +7797,7 @@
"x-appwrite": {
"method": "get",
"group": null,
"weight": 49,
"weight": 60,
"cookies": false,
"type": "",
"demo": "locale\/get.md",
@@ -7867,7 +7850,7 @@
"x-appwrite": {
"method": "listCodes",
"group": null,
"weight": 50,
"weight": 61,
"cookies": false,
"type": "",
"demo": "locale\/list-codes.md",
@@ -7920,7 +7903,7 @@
"x-appwrite": {
"method": "listContinents",
"group": null,
"weight": 54,
"weight": 65,
"cookies": false,
"type": "",
"demo": "locale\/list-continents.md",
@@ -7973,7 +7956,7 @@
"x-appwrite": {
"method": "listCountries",
"group": null,
"weight": 51,
"weight": 62,
"cookies": false,
"type": "",
"demo": "locale\/list-countries.md",
@@ -8026,7 +8009,7 @@
"x-appwrite": {
"method": "listCountriesEU",
"group": null,
"weight": 52,
"weight": 63,
"cookies": false,
"type": "",
"demo": "locale\/list-countries-eu.md",
@@ -8079,7 +8062,7 @@
"x-appwrite": {
"method": "listCountriesPhones",
"group": null,
"weight": 53,
"weight": 64,
"cookies": false,
"type": "",
"demo": "locale\/list-countries-phones.md",
@@ -8132,7 +8115,7 @@
"x-appwrite": {
"method": "listCurrencies",
"group": null,
"weight": 55,
"weight": 66,
"cookies": false,
"type": "",
"demo": "locale\/list-currencies.md",
@@ -8185,7 +8168,7 @@
"x-appwrite": {
"method": "listLanguages",
"group": null,
"weight": 56,
"weight": 67,
"cookies": false,
"type": "",
"demo": "locale\/list-languages.md",
@@ -8240,7 +8223,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 237,
"weight": 272,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@@ -8325,7 +8308,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 241,
"weight": 276,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@@ -8396,7 +8379,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 543,
"weight": 526,
"cookies": false,
"type": "",
"demo": "storage\/list-files.md",
@@ -8489,7 +8472,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 541,
"weight": 524,
"cookies": false,
"type": "upload",
"demo": "storage\/create-file.md",
@@ -8580,7 +8563,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 542,
"weight": 525,
"cookies": false,
"type": "",
"demo": "storage\/get-file.md",
@@ -8651,7 +8634,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 544,
"weight": 527,
"cookies": false,
"type": "",
"demo": "storage\/update-file.md",
@@ -8742,7 +8725,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 545,
"weight": 528,
"cookies": false,
"type": "",
"demo": "storage\/delete-file.md",
@@ -8813,7 +8796,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 547,
"weight": 530,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-download.md",
@@ -8893,7 +8876,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 546,
"weight": 529,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-preview.md",
@@ -9101,7 +9084,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 548,
"weight": 531,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-view.md",
@@ -9181,7 +9164,7 @@
"x-appwrite": {
"method": "listTransactions",
"group": "transactions",
"weight": 419,
"weight": 427,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-transactions.md",
@@ -9251,7 +9234,7 @@
"x-appwrite": {
"method": "createTransaction",
"group": "transactions",
"weight": 415,
"weight": 423,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-transaction.md",
@@ -9292,8 +9275,7 @@
"type": "integer",
"description": "Seconds before the transaction expires.",
"default": 300,
"x-example": 60,
"format": "int32"
"x-example": 60
}
}
}
@@ -9325,7 +9307,7 @@
"x-appwrite": {
"method": "getTransaction",
"group": "transactions",
"weight": 416,
"weight": 424,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-transaction.md",
@@ -9391,7 +9373,7 @@
"x-appwrite": {
"method": "updateTransaction",
"group": "transactions",
"weight": 417,
"weight": 425,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-transaction.md",
@@ -9473,7 +9455,7 @@
"x-appwrite": {
"method": "deleteTransaction",
"group": "transactions",
"weight": 418,
"weight": 426,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-transaction.md",
@@ -9541,7 +9523,7 @@
"x-appwrite": {
"method": "createOperations",
"group": "transactions",
"weight": 420,
"weight": 428,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-operations.md",
@@ -9625,7 +9607,7 @@
"x-appwrite": {
"method": "listRows",
"group": "rows",
"weight": 411,
"weight": 419,
"cookies": false,
"type": "",
"demo": "tablesdb\/list-rows.md",
@@ -9728,7 +9710,7 @@
"x-appwrite": {
"method": "createRow",
"group": "rows",
"weight": 403,
"weight": 411,
"cookies": false,
"type": "",
"demo": "tablesdb\/create-row.md",
@@ -9882,7 +9864,7 @@
"x-appwrite": {
"method": "getRow",
"group": "rows",
"weight": 404,
"weight": 412,
"cookies": false,
"type": "",
"demo": "tablesdb\/get-row.md",
@@ -9984,7 +9966,7 @@
"x-appwrite": {
"method": "upsertRow",
"group": "rows",
"weight": 407,
"weight": 415,
"cookies": false,
"type": "",
"demo": "tablesdb\/upsert-row.md",
@@ -10130,7 +10112,7 @@
"x-appwrite": {
"method": "updateRow",
"group": "rows",
"weight": 405,
"weight": 413,
"cookies": false,
"type": "",
"demo": "tablesdb\/update-row.md",
@@ -10239,7 +10221,7 @@
"x-appwrite": {
"method": "deleteRow",
"group": "rows",
"weight": 409,
"weight": 417,
"cookies": false,
"type": "",
"demo": "tablesdb\/delete-row.md",
@@ -10339,7 +10321,7 @@
"x-appwrite": {
"method": "decrementRowColumn",
"group": "rows",
"weight": 414,
"weight": 422,
"cookies": false,
"type": "",
"demo": "tablesdb\/decrement-row-column.md",
@@ -10411,15 +10393,13 @@
"type": "number",
"description": "Value to increment the column by. The value must be a number.",
"default": 1,
"x-example": null,
"format": "float"
"x-example": null
},
"min": {
"type": "number",
"description": "Minimum value for the column. If the current value is lesser than this value, an exception will be thrown.",
"default": null,
"x-example": null,
"format": "float",
"x-nullable": true
},
"transactionId": {
@@ -10461,7 +10441,7 @@
"x-appwrite": {
"method": "incrementRowColumn",
"group": "rows",
"weight": 413,
"weight": 421,
"cookies": false,
"type": "",
"demo": "tablesdb\/increment-row-column.md",
@@ -10533,15 +10513,13 @@
"type": "number",
"description": "Value to increment the column by. The value must be a number.",
"default": 1,
"x-example": null,
"format": "float"
"x-example": null
},
"max": {
"type": "number",
"description": "Maximum value for the column. If the current value is greater than this value, an error will be thrown.",
"default": null,
"x-example": null,
"format": "float",
"x-nullable": true
},
"transactionId": {
@@ -10581,7 +10559,7 @@
"x-appwrite": {
"method": "list",
"group": "teams",
"weight": 110,
"weight": 145,
"cookies": false,
"type": "",
"demo": "teams\/list.md",
@@ -10666,7 +10644,7 @@
"x-appwrite": {
"method": "create",
"group": "teams",
"weight": 109,
"weight": 144,
"cookies": false,
"type": "",
"demo": "teams\/create.md",
@@ -10757,7 +10735,7 @@
"x-appwrite": {
"method": "get",
"group": "teams",
"weight": 111,
"weight": 146,
"cookies": false,
"type": "",
"demo": "teams\/get.md",
@@ -10820,7 +10798,7 @@
"x-appwrite": {
"method": "updateName",
"group": "teams",
"weight": 113,
"weight": 148,
"cookies": false,
"type": "",
"demo": "teams\/update-name.md",
@@ -10896,7 +10874,7 @@
"x-appwrite": {
"method": "delete",
"group": "teams",
"weight": 115,
"weight": 150,
"cookies": false,
"type": "",
"demo": "teams\/delete.md",
@@ -10959,7 +10937,7 @@
"x-appwrite": {
"method": "listMemberships",
"group": "memberships",
"weight": 117,
"weight": 152,
"cookies": false,
"type": "",
"demo": "teams\/list-memberships.md",
@@ -11052,7 +11030,7 @@
"x-appwrite": {
"method": "createMembership",
"group": "memberships",
"weight": 116,
"weight": 151,
"cookies": false,
"type": "",
"demo": "teams\/create-membership.md",
@@ -11098,8 +11076,7 @@
"type": "string",
"description": "Email of the new team member.",
"default": "",
"x-example": "email@example.com",
"format": "email"
"x-example": "email@example.com"
},
"userId": {
"type": "string",
@@ -11111,8 +11088,7 @@
"type": "string",
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
"default": "",
"x-example": "+12065550100",
"format": "phone"
"x-example": "+12065550100"
},
"roles": {
"type": "array",
@@ -11134,8 +11110,7 @@
"type": "string",
"description": "URL to redirect the user back to your app from the invitation email. This parameter is not required when an API key is supplied. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https:\/\/cheatsheetseries.owasp.org\/cheatsheets\/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.",
"default": "",
"x-example": "https:\/\/example.com",
"format": "url"
"x-example": "https:\/\/example.com"
},
"name": {
"type": "string",
@@ -11176,7 +11151,7 @@
"x-appwrite": {
"method": "getMembership",
"group": "memberships",
"weight": 118,
"weight": 153,
"cookies": false,
"type": "",
"demo": "teams\/get-membership.md",
@@ -11247,7 +11222,7 @@
"x-appwrite": {
"method": "updateMembership",
"group": "memberships",
"weight": 119,
"weight": 154,
"cookies": false,
"type": "",
"demo": "teams\/update-membership.md",
@@ -11341,7 +11316,7 @@
"x-appwrite": {
"method": "deleteMembership",
"group": "memberships",
"weight": 121,
"weight": 156,
"cookies": false,
"type": "",
"demo": "teams\/delete-membership.md",
@@ -11414,7 +11389,7 @@
"x-appwrite": {
"method": "updateMembershipStatus",
"group": "memberships",
"weight": 120,
"weight": 155,
"cookies": false,
"type": "",
"demo": "teams\/update-membership-status.md",
@@ -11510,7 +11485,7 @@
"x-appwrite": {
"method": "getPrefs",
"group": "teams",
"weight": 112,
"weight": 147,
"cookies": false,
"type": "",
"demo": "teams\/get-prefs.md",
@@ -11573,7 +11548,7 @@
"x-appwrite": {
"method": "updatePrefs",
"group": "teams",
"weight": 114,
"weight": 149,
"cookies": false,
"type": "",
"demo": "teams\/update-prefs.md",
@@ -13393,16 +13368,6 @@
"description": "Total number of chunks uploaded",
"x-example": 17890,
"format": "int32"
},
"encryption": {
"type": "boolean",
"description": "Whether file contents are encrypted at rest.",
"x-example": true
},
"compression": {
"type": "string",
"description": "Compression algorithm used for the file. Will be one of none, [gzip](https:\/\/en.wikipedia.org\/wiki\/Gzip), or [zstd](https:\/\/en.wikipedia.org\/wiki\/Zstd).",
"x-example": "gzip"
}
},
"required": [
@@ -13416,9 +13381,7 @@
"mimeType",
"sizeOriginal",
"chunksTotal",
"chunksUploaded",
"encryption",
"compression"
"chunksUploaded"
],
"example": {
"$id": "5e5ea5c16897e",
@@ -13433,9 +13396,7 @@
"mimeType": "image\/png",
"sizeOriginal": 17890,
"chunksTotal": 17890,
"chunksUploaded": 17890,
"encryption": true,
"compression": "gzip"
"chunksUploaded": 17890
}
},
"team": {
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -3
View File
@@ -3,6 +3,4 @@
use Utopia\Image\Image;
use Utopia\System\System;
if (\class_exists('Imagick')) {
Image::setResourceLimit('memory', intval(System::getEnv('_APP_IMAGES_RESOURCE_LIMIT_MEMORY', 1024*1024*64)));
}
Image::setResourceLimit('memory', intval(System::getEnv('_APP_IMAGES_RESOURCE_LIMIT_MEMORY', 1024*1024*64)));
+40 -68
View File
@@ -20,34 +20,6 @@ function getRuntimes($runtimes, $commands, $entrypoint, $providerRootDirectory,
}));
}
class FunctionUseCases
{
public const STARTER = 'starter';
public const DATABASES = 'databases';
public const AI = 'ai';
public const MESSAGING = 'messaging';
public const UTILITIES = 'utilities';
public const DEV_TOOLS = 'dev-tools';
public const AUTH = 'auth';
/**
* @var array<string>
*/
public static function getAll(): array
{
return [
self::STARTER,
self::DATABASES,
self::AI,
self::MESSAGING,
self::UTILITIES,
self::DEV_TOOLS,
self::AUTH,
];
}
}
return [
[
'icon' => 'icon-lightning-bolt',
@@ -60,7 +32,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::STARTER],
'useCases' => ['starter'],
'runtimes' => [
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/starter', $allowList),
...getRuntimes(
@@ -101,7 +73,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::DATABASES],
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -147,7 +119,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::DATABASES],
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -192,7 +164,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::DATABASES],
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -246,7 +218,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::DATABASES],
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -285,7 +257,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::DATABASES],
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -354,7 +326,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -420,7 +392,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::MESSAGING],
'useCases' => ['messaging'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -487,7 +459,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -525,7 +497,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -576,7 +548,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::UTILITIES],
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/generate-pdf', $allowList)
],
@@ -599,7 +571,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::DEV_TOOLS],
'useCases' => ['dev-tools'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -644,7 +616,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::UTILITIES],
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -697,7 +669,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::DATABASES],
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -782,7 +754,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::DATABASES],
'useCases' => ['databases'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -881,7 +853,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::MESSAGING],
'useCases' => ['messaging'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -974,7 +946,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::MESSAGING],
'useCases' => ['messaging'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1032,7 +1004,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::UTILITIES],
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1119,7 +1091,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::UTILITIES],
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1162,7 +1134,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::UTILITIES],
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1221,7 +1193,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 30,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1257,7 +1229,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 30,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1293,7 +1265,7 @@ return [
'events' => ['buckets.*.files.*.create'],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1353,7 +1325,7 @@ return [
'events' => ['buckets.*.files.*.create'],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1413,7 +1385,7 @@ return [
'events' => ['buckets.*.files.*.create'],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1476,7 +1448,7 @@ return [
],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1536,7 +1508,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 300,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1573,7 +1545,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 300,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1617,7 +1589,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1660,7 +1632,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 300,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1697,7 +1669,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 30,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1762,7 +1734,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 30,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1827,7 +1799,7 @@ return [
'cron' => '',
'events' => [],
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1884,7 +1856,7 @@ return [
'cron' => '',
'events' => [],
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1927,7 +1899,7 @@ return [
'cron' => '',
'events' => [],
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -1970,7 +1942,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -2014,7 +1986,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 300,
'useCases' => [FunctionUseCases::AI],
'useCases' => ['ai'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -2051,7 +2023,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::UTILITIES],
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -2108,7 +2080,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::UTILITIES],
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes(
$templateRuntimes['NODE'],
@@ -2181,7 +2153,7 @@ return [
'events' => [],
'cron' => '',
'timeout' => 15,
'useCases' => [FunctionUseCases::AUTH],
'useCases' => ['auth'],
'runtimes' => [
...getRuntimes($templateRuntimes['DART'], 'dart pub get', 'lib/main.dart', 'dart/sign_in_with_apple', $allowList)
],
+78 -96
View File
@@ -13,7 +13,7 @@ $hostname = $platform['consoleHostname'] ?? '';
$url = $protocol . '://' . $hostname;
class SiteUseCases
class UseCases
{
public const PORTFOLIO = 'portfolio';
public const STARTER = 'starter';
@@ -21,27 +21,9 @@ class SiteUseCases
public const ECOMMERCE = 'ecommerce';
public const DOCUMENTATION = 'documentation';
public const BLOG = 'blog';
public const AI = 'ai';
public const AI = 'artificial intelligence';
public const FORMS = 'forms';
public const DASHBOARD = 'dashboard';
/**
* @var array<string>
*/
public static function getAll(): array
{
return [
self::PORTFOLIO,
self::STARTER,
self::EVENTS,
self::ECOMMERCE,
self::DOCUMENTATION,
self::BLOG,
self::AI,
self::FORMS,
self::DASHBOARD,
];
}
}
const TEMPLATE_FRAMEWORKS = [
@@ -206,7 +188,7 @@ return [
'name' => 'Documentation template',
'tagline' => 'Modern site to store your knowledge with a clean design, full-text search, dark mode, and more.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::DOCUMENTATION],
'useCases' => [UseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/template-for-documentation-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-documentation-light.png',
'frameworks' => [
@@ -227,7 +209,7 @@ return [
// When we add Lynx with Appwrite SDK, use following tagline for it:
// 'tagline' => 'Sample application built with Lynx, a cross-platform framework focused on performance.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-lynx-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-lynx-light.png',
'frameworks' => [
@@ -238,7 +220,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -246,7 +228,7 @@ return [
'name' => 'Vitepress',
'tagline' => 'Platform for documentation and knowledge sharing powered by Vite.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::DOCUMENTATION],
'useCases' => [UseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/vitepress-dark.png',
'screenshotLight' => $url . '/images/sites/templates/vitepress-light.png',
'frameworks' => [
@@ -261,7 +243,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -269,7 +251,7 @@ return [
'name' => 'Vuepress',
'tagline' => 'Platform for documentation and knowledge sharing powered by Vue.',
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::DOCUMENTATION],
'useCases' => [UseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/vuepress-dark.png',
'screenshotLight' => $url . '/images/sites/templates/vuepress-light.png',
'frameworks' => [
@@ -284,7 +266,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -292,7 +274,7 @@ return [
'name' => 'Docusaurus',
'tagline' => 'Platform for documentation and knowledge sharing powered by React.',
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::DOCUMENTATION],
'useCases' => [UseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/docusaurus-dark.png',
'screenshotLight' => $url . '/images/sites/templates/docusaurus-light.png',
'frameworks' => [
@@ -307,7 +289,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -315,7 +297,7 @@ return [
'name' => 'Nxt Lnk',
'tagline' => 'Personal website for creators to merge all URLs to social profiles.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::PORTFOLIO],
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/nxt-lnk-dark.png',
'screenshotLight' => $url . '/images/sites/templates/nxt-lnk-light.png',
'frameworks' => [
@@ -326,7 +308,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -334,7 +316,7 @@ return [
'name' => 'Magic Portfolio',
'tagline' => 'Complex personal website to showcase your projects, articles, and more.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::PORTFOLIO],
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/magic-portfolio-dark.png',
'screenshotLight' => $url . '/images/sites/templates/magic-portfolio-light.png',
'frameworks' => [
@@ -345,7 +327,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -353,7 +335,7 @@ return [
'name' => 'LittleLink',
'tagline' => 'Personal website for creators to merge all URLs to social profiles.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::PORTFOLIO],
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/littlelink-dark.png',
'screenshotLight' => $url . '/images/sites/templates/littlelink-light.png',
'frameworks' => [
@@ -364,7 +346,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -372,7 +354,7 @@ return [
'name' => 'Logspot',
'tagline' => 'Website to publish changelogs of your application.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::BLOG],
'useCases' => [UseCases::BLOG],
'screenshotDark' => $url . '/images/sites/templates/logspot-dark.png',
'screenshotLight' => $url . '/images/sites/templates/logspot-light.png',
'frameworks' => [
@@ -386,7 +368,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -394,7 +376,7 @@ return [
'name' => 'Astro Nano',
'tagline' => 'Minimal personal website to showcase your projects, articles, and more.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::PORTFOLIO],
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/astro-nano-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-nano-light.png',
'frameworks' => [
@@ -407,7 +389,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -415,7 +397,7 @@ return [
'name' => 'Astro Starlight',
'tagline' => 'Platform for documentation and knowledge sharing powered by Astro.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::DOCUMENTATION],
'useCases' => [UseCases::DOCUMENTATION],
'screenshotDark' => $url . '/images/sites/templates/astro-starlight-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-starlight-light.png',
'frameworks' => [
@@ -428,7 +410,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -436,7 +418,7 @@ return [
'name' => 'Astro Sphere',
'tagline' => 'Modern personal website to showcase your projects, articles, and more.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::PORTFOLIO],
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/astro-sphere-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-sphere-light.png',
'frameworks' => [
@@ -449,7 +431,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -457,7 +439,7 @@ return [
'name' => 'Astro Starlog',
'tagline' => 'Platform for publishing written content and media powered by Astro.',
'score' => 5, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::BLOG],
'useCases' => [UseCases::BLOG],
'screenshotDark' => $url . '/images/sites/templates/astro-starlog-dark.png',
'screenshotLight' => $url . '/images/sites/templates/astro-starlog-light.png',
'frameworks' => [
@@ -470,7 +452,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -478,7 +460,7 @@ return [
'name' => 'Onelink',
'tagline' => 'Personal website for creators to merge all URLs to social profiles.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::PORTFOLIO],
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/onelink-dark.png',
'screenshotLight' => $url . '/images/sites/templates/onelink-light.png',
'frameworks' => [
@@ -492,13 +474,13 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
'key' => 'starter-for-flutter',
'name' => 'Flutter starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Flutter application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-flutter-dark.png',
@@ -543,7 +525,7 @@ return [
[
'key' => 'starter-for-js',
'name' => 'JavaScript starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple JavaScript application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-js-dark.png',
@@ -587,7 +569,7 @@ return [
[
'key' => 'starter-for-angular',
'name' => 'Angular starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Angular application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-angular-dark.png',
@@ -633,7 +615,7 @@ return [
[
'key' => 'starter-for-astro',
'name' => 'Astro starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Astro application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-astro-dark.png',
@@ -678,7 +660,7 @@ return [
[
'key' => 'starter-for-analog',
'name' => 'Analog starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Analog application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-analog-dark.png',
@@ -722,7 +704,7 @@ return [
[
'key' => 'starter-for-remix',
'name' => 'Remix starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Remix application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-remix-dark.png',
@@ -766,7 +748,7 @@ return [
[
'key' => 'starter-for-svelte',
'name' => 'Svelte starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Svelte application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-svelte-dark.png',
@@ -810,7 +792,7 @@ return [
[
'key' => 'starter-for-react',
'name' => 'React starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple React application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-react-dark.png',
@@ -854,7 +836,7 @@ return [
[
'key' => 'starter-for-vue',
'name' => 'Vue starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Vue application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-vue-dark.png',
@@ -898,7 +880,7 @@ return [
[
'key' => 'starter-for-react-native',
'name' => 'React Native starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple React Native application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-react-native-dark.png',
@@ -942,7 +924,7 @@ return [
[
'key' => 'starter-for-nextjs',
'name' => 'Next.js starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Next.js application integrated with Appwrite SDK.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-nextjs-dark.png',
@@ -986,7 +968,7 @@ return [
[
'key' => 'starter-for-tanstack-start',
'name' => 'TanStack Start starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple TanStack Start application integrated with Appwrite SDK.',
'score' => 9, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-tanstack-start-dark.png',
@@ -1030,7 +1012,7 @@ return [
[
'key' => 'starter-for-nuxt',
'name' => 'Nuxt starter',
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'tagline' => 'Simple Nuxt application integrated with Appwrite SDK.',
'score' => 3, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'screenshotDark' => $url . '/images/sites/templates/starter-for-nuxt-dark.png',
@@ -1076,7 +1058,7 @@ return [
'name' => 'Event template',
'tagline' => 'Hackathon landing page with support for project submissions.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::EVENTS],
'useCases' => [UseCases::EVENTS],
'screenshotDark' => $url . '/images/sites/templates/template-for-event-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-event-light.png',
'frameworks' => [
@@ -1114,7 +1096,7 @@ return [
'name' => 'Portfolio template',
'tagline' => 'Simple personal website to showcase your projects, articles, and more.',
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::PORTFOLIO],
'useCases' => [UseCases::PORTFOLIO],
'screenshotDark' => $url . '/images/sites/templates/template-for-portfolio-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-portfolio-light.png',
'frameworks' => [
@@ -1133,7 +1115,7 @@ return [
'name' => 'Store template',
'tagline' => 'E-commerce platform for selling products with Stripe integration.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::ECOMMERCE],
'useCases' => [UseCases::ECOMMERCE],
'screenshotDark' => $url . '/images/sites/templates/template-for-store-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-store-light.png',
'frameworks' => [
@@ -1177,7 +1159,7 @@ return [
'name' => 'Blog template',
'tagline' => 'Platform for publishing written content and media.',
'score' => 7, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::BLOG],
'useCases' => [UseCases::BLOG],
'screenshotDark' => $url . '/images/sites/templates/template-for-blog-dark.png',
'screenshotLight' => $url . '/images/sites/templates/template-for-blog-light.png',
'frameworks' => [
@@ -1196,7 +1178,7 @@ return [
'name' => 'Astro playground',
'tagline' => 'A basic Astro website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-astro-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-astro-light.png',
'frameworks' => [
@@ -1207,7 +1189,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1215,7 +1197,7 @@ return [
'name' => 'Remix playground',
'tagline' => 'A basic Remix website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-remix-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-remix-light.png',
'frameworks' => [
@@ -1226,7 +1208,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1234,7 +1216,7 @@ return [
'name' => 'Next.js playground',
'tagline' => 'A basic Next.js website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-nextjs-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-nextjs-light.png',
'frameworks' => [
@@ -1245,7 +1227,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1253,7 +1235,7 @@ return [
'name' => 'Flutter playground',
'tagline' => 'A basic Flutter website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-flutter-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-flutter-light.png',
'frameworks' => [
@@ -1264,7 +1246,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1272,7 +1254,7 @@ return [
'name' => 'Vite playground',
'tagline' => 'A basic Vite website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-vite-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-vite-light.png',
'frameworks' => [
@@ -1283,7 +1265,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1291,7 +1273,7 @@ return [
'name' => 'Angular playground',
'tagline' => 'A basic Angular website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-angular-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-angular-light.png',
'frameworks' => [
@@ -1303,7 +1285,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1311,7 +1293,7 @@ return [
'name' => 'Analog playground',
'tagline' => 'A basic Analog website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-analog-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-analog-light.png',
'frameworks' => [
@@ -1322,7 +1304,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1330,7 +1312,7 @@ return [
'name' => 'Svelte playground',
'tagline' => 'A basic Svelte website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-svelte-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-svelte-light.png',
'frameworks' => [
@@ -1341,7 +1323,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
@@ -1350,7 +1332,7 @@ return [
'name' => 'React playground',
'tagline' => 'A basic React website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-react-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-react-light.png',
'frameworks' => [
@@ -1362,7 +1344,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
@@ -1371,7 +1353,7 @@ return [
'name' => 'Vue playground',
'tagline' => 'A basic Vue website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-vue-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-vue-light.png',
'frameworks' => [
@@ -1382,7 +1364,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1390,7 +1372,7 @@ return [
'name' => 'Nuxt playground',
'tagline' => 'A basic Nuxt website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-nuxt-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-nuxt-light.png',
'frameworks' => [
@@ -1401,7 +1383,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1409,7 +1391,7 @@ return [
'name' => 'TanStack Start playground',
'tagline' => 'A basic TanStack Start website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-tanstack-start-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-tanstack-start-light.png',
'frameworks' => [
@@ -1420,7 +1402,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.5.*',
'variables' => [],
],
[
@@ -1428,7 +1410,7 @@ return [
'name' => 'React Native playground',
'tagline' => 'A basic React Native website without Appwrite SDK integration.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/playground-for-react-native-dark.png',
'screenshotLight' => $url . '/images/sites/templates/playground-for-react-native-light.png',
'frameworks' => [
@@ -1439,7 +1421,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => [],
],
[
@@ -1447,7 +1429,7 @@ return [
'name' => 'Lynx gallery',
'tagline' => 'A Lynx website showcasing gallery with smooth animations.',
'score' => 1, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::STARTER],
'useCases' => [UseCases::STARTER],
'screenshotDark' => $url . '/images/sites/templates/gallery-for-lynx-dark.png',
'screenshotLight' => $url . '/images/sites/templates/gallery-for-lynx-light.png',
'frameworks' => [
@@ -1458,7 +1440,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.3.*',
'variables' => []
],
[
@@ -1466,7 +1448,7 @@ return [
'name' => 'Text-to-speech with ElevenLabs',
'tagline' => 'Next.js app that transforms text into natural, human-like speech using ElevenLabs',
'score' => 10, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::AI],
'useCases' => [UseCases::AI],
'screenshotDark' => $url . '/images/sites/templates/text-to-speech-dark.png',
'screenshotLight' => $url . '/images/sites/templates/text-to-speech-light.png',
'frameworks' => [
@@ -1477,7 +1459,7 @@ return [
'vcsProvider' => 'github',
'providerRepositoryId' => 'templates-for-sites',
'providerOwner' => 'appwrite',
'providerVersion' => '0.7.*',
'providerVersion' => '0.6.*',
'variables' => [
[
'name' => 'ELEVENLABS_API_KEY',
@@ -1494,7 +1476,7 @@ return [
'name' => 'CRM dashboard with React Admin',
'tagline' => 'A React-based admin dashboard template with CRM features.',
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::DASHBOARD],
'useCases' => [UseCases::DASHBOARD],
'screenshotDark' => $url . '/images/sites/templates/crm-dashboard-react-admin-dark.png',
'screenshotLight' => $url . '/images/sites/templates/crm-dashboard-react-admin-light.png',
'frameworks' => [
@@ -1597,7 +1579,7 @@ return [
'name' => 'Job applications form with Formspree',
'tagline' => 'A simple form submission template using Formspree.',
'score' => 4, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
'useCases' => [SiteUseCases::FORMS],
'useCases' => [UseCases::FORMS],
'screenshotDark' => $url . '/images/sites/templates/job-applications-formspree-dark.png',
'screenshotLight' => $url . '/images/sites/templates/job-applications-formspree-light.png',
'frameworks' => [
-9
View File
@@ -1285,15 +1285,6 @@ return [
'category' => 'GraphQL',
'description' => '',
'variables' => [
[
'name' => '_APP_GRAPHQL_INTROSPECTION',
'description' => 'Enable or disable GraphQL introspection. Set to \'enabled\' to allow schema introspection, or \'disabled\' to block it. The default value is \'enabled\'.',
'introduction' => '',
'default' => 'enabled',
'required' => false,
'question' => '',
'filter' => ''
],
[
'name' => '_APP_GRAPHQL_MAX_BATCH_SIZE',
'description' => 'Maximum number of batched queries per request. The default value is 10.',
+102 -114
View File
@@ -33,9 +33,9 @@ use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Identities;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumberUtil;
use MaxMind\Db\Reader;
use Utopia\Abuse\Abuse;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Auth\Hashes\Sha;
@@ -195,7 +195,7 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, arr
->setPreview($preview)
->setBody($body)
->setBodyTemplate($bodyTemplate)
->appendVariables($emailVariables)
->setVariables($emailVariables)
->setRecipient($email);
// since this is console project, set email sender name!
@@ -207,10 +207,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, Authorization $authorization) {
$createSession = function (string $userId, string $secret, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Store $store, ProofsToken $proofForToken, ProofsCode $proofForCode) {
/** @var Appwrite\Utopia\Database\Documents\User $userFromRequest */
$userFromRequest = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId));
$userFromRequest = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId));
if ($userFromRequest->isEmpty()) {
throw new Exception(Exception::USER_INVALID_TOKEN);
@@ -266,7 +266,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
$detector->getDevice()
));
$authorization->addRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::user($user->getId())->toString());
$session = $dbForProject->createDocument('sessions', $session
->setAttribute('$permissions', [
@@ -275,7 +275,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,9 +376,8 @@ 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, Authorization $authorization, Hooks $hooks) {
->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Hooks $hooks) {
$email = \strtolower($email);
if ('console' === $project->getId()) {
@@ -470,9 +469,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())),
@@ -498,9 +497,9 @@ App::post('/v1/account')
throw new Exception(Exception::USER_ALREADY_EXISTS);
}
$authorization->removeRole(Role::guests()->toString());
$authorization->addRole(Role::user($user->getId())->toString());
$authorization->addRole(Role::users()->toString());
Authorization::unsetRole(Role::guests()->toString());
Authorization::setRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::users()->toString());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -977,8 +976,7 @@ App::post('/v1/account/sessions/email')
->inject('store')
->inject('proofForPassword')
->inject('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) {
->action(function (string $email, string $password, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) {
$email = \strtolower($email);
$protocol = $request->getProtocol();
@@ -1023,7 +1021,7 @@ App::post('/v1/account/sessions/email')
$detector->getDevice()
));
$authorization->addRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::user($user->getId())->toString());
// Re-hash if not using recommended algo
if ($user->getAttribute('hash') !== $proofForPassword->getHash()->getName()) {
@@ -1122,8 +1120,7 @@ App::post('/v1/account/sessions/anonymous')
->inject('store')
->inject('proofForPassword')
->inject('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) {
->action(function (Request $request, Response $response, Locale $locale, User $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) {
$protocol = $request->getProtocol();
if ('console' === $project->getId()) {
@@ -1168,7 +1165,7 @@ App::post('/v1/account/sessions/anonymous')
'accessedAt' => DateTime::now(),
]);
$user->removeAttribute('$sequence');
$user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
// Create session token
$duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
@@ -1194,7 +1191,7 @@ App::post('/v1/account/sessions/anonymous')
$detector->getDevice()
));
$authorization->addRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::user($user->getId())->toString());
$session = $dbForProject->createDocument('sessions', $session->setAttribute('$permissions', [
Permission::read(Role::user($user->getId())),
@@ -1277,7 +1274,6 @@ App::post('/v1/account/sessions/token')
->inject('store')
->inject('proofForToken')
->inject('proofForCode')
->inject('authorization')
->action($createSession);
App::get('/v1/account/sessions/oauth2/:provider')
@@ -1474,8 +1470,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
->inject('store')
->inject('proofForPassword')
->inject('proofForToken')
->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) {
->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) {
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
$port = $request->getPort();
$callbackBase = $protocol . '://' . $request->getHostname();
@@ -1731,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())),
@@ -1749,8 +1744,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
}
}
$authorization->addRole(Role::user($user->getId())->toString());
$authorization->addRole(Role::users()->toString());
Authorization::setRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::users()->toString());
if (false === $user->getAttribute('status')) { // Account is blocked
$failureRedirect(Exception::USER_BLOCKED); // User is in status blocked
@@ -1821,7 +1816,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$dbForProject->updateDocument('users', $user->getId(), $user);
$authorization->addRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::user($user->getId())->toString());
$state['success'] = URLParser::parse($state['success']);
$query = URLParser::parseQuery($state['success']['query']);
@@ -1845,7 +1840,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
'ip' => $request->getIP(),
]);
$authorization->addRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::user($user->getId())->toString());
$token = $dbForProject->createDocument('tokens', $token
->setAttribute('$permissions', [
@@ -2082,8 +2077,7 @@ App::post('/v1/account/tokens/magic-url')
->inject('queueForMails')
->inject('proofForPassword')
->inject('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) {
->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) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
}
@@ -2156,7 +2150,7 @@ App::post('/v1/account/tokens/magic-url')
]);
$user->removeAttribute('$sequence');
$user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
}
$proofForToken = new ProofsToken(TOKEN_LENGTH_MAGIC_URL);
@@ -2176,7 +2170,7 @@ App::post('/v1/account/tokens/magic-url')
'ip' => $request->getIP(),
]);
$authorization->addRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::user($user->getId())->toString());
$token = $dbForProject->createDocument('tokens', $token
->setAttribute('$permissions', [
@@ -2301,7 +2295,7 @@ App::post('/v1/account/tokens/magic-url')
->setSubject($subject)
->setPreview($preview)
->setBody($body)
->appendVariables($emailVariables)
->setVariables($emailVariables)
->setRecipient($email);
if ($project->getId() === 'console') {
@@ -2362,8 +2356,7 @@ App::post('/v1/account/tokens/email')
->inject('queueForMails')
->inject('proofForPassword')
->inject('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) {
->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, User $user, Document $project, array $platform, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword, ProofsCode $proofForCode) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
}
@@ -2432,9 +2425,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())),
@@ -2472,7 +2465,7 @@ App::post('/v1/account/tokens/email')
'ip' => $request->getIP(),
]);
$authorization->addRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::user($user->getId())->toString());
$token = $dbForProject->createDocument('tokens', $token
->setAttribute('$permissions', [
@@ -2602,7 +2595,7 @@ App::post('/v1/account/tokens/email')
->setPreview($preview)
->setBody($body)
->setBodyTemplate($bodyTemplate)
->appendVariables($emailVariables)
->setVariables($emailVariables)
->setRecipient($email);
// since this is console project, set email sender name!
@@ -2669,11 +2662,10 @@ App::put('/v1/account/sessions/magic-url')
->inject('queueForMails')
->inject('store')
->inject('proofForCode')
->inject('authorization')
->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForCode, $authorization) use ($createSession) {
->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForCode) use ($createSession) {
$proofForToken = new ProofsToken(TOKEN_LENGTH_MAGIC_URL);
$proofForToken->setHash(new Sha());
$createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForToken, $proofForCode, $authorization);
$createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForToken, $proofForCode);
});
App::put('/v1/account/sessions/phone')
@@ -2719,7 +2711,6 @@ App::put('/v1/account/sessions/phone')
->inject('store')
->inject('proofForToken')
->inject('proofForCode')
->inject('authorization')
->action($createSession);
App::post('/v1/account/tokens/phone')
@@ -2763,8 +2754,7 @@ App::post('/v1/account/tokens/phone')
->inject('plan')
->inject('store')
->inject('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) {
->action(function (string $userId, string $phone, Request $request, Response $response, User $user, Document $project, array $platform, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Store $store, ProofsCode $proofForCode) {
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
}
@@ -2814,9 +2804,9 @@ App::post('/v1/account/tokens/phone')
]);
$user->removeAttribute('$sequence');
$user = $authorization->skip(fn () => $dbForProject->createDocument('users', $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())),
@@ -2862,7 +2852,7 @@ App::post('/v1/account/tokens/phone')
'ip' => $request->getIP(),
]);
$authorization->addRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::user($user->getId())->toString());
$token = $dbForProject->createDocument('tokens', $token
->setAttribute('$permissions', [
@@ -2908,21 +2898,26 @@ App::post('/v1/account/tokens/phone')
->setRecipients([$phone])
->setProviderType(MESSAGE_TYPE_SMS);
$helper = PhoneNumberUtil::getInstance();
try {
$countryCode = $helper->parse($phone)->getCountryCode();
if (isset($plan['authPhone'])) {
$timelimit = $timelimit('organization:{organizationId}', $plan['authPhone'], 30 * 24 * 60 * 60); // 30 days
$timelimit
->setParam('{organizationId}', $project->getAttribute('teamId'));
if (!empty($countryCode)) {
$queueForStatsUsage
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
$abuse = new Abuse($timelimit);
if ($abuse->check() && System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') {
$helper = PhoneNumberUtil::getInstance();
$countryCode = $helper->parse($phone)->getCountryCode();
if (!empty($countryCode)) {
$queueForStatsUsage
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
}
}
} catch (NumberParseException $e) {
// Ignore invalid phone number for country code stats
$queueForStatsUsage
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
->setProject($project)
->trigger();
}
$queueForStatsUsage
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
->setProject($project)
->trigger();
}
$token->setAttribute('secret', $secret);
@@ -3248,8 +3243,7 @@ App::patch('/v1/account/email')
->inject('project')
->inject('hooks')
->inject('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) {
->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, User $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, ProofsPassword $proofForPassword) {
// passwordUpdate will be empty if the user has never set a password
$passwordUpdate = $user->getAttribute('passwordUpdate');
@@ -3301,7 +3295,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]),
]));
@@ -3317,7 +3311,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) {
@@ -3358,9 +3352,8 @@ App::patch('/v1/account/phone')
->inject('queueForEvents')
->inject('project')
->inject('hooks')
->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) {
->inject('proofForPassword')
->action(function (string $phone, string $password, Response $response, User $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, ProofsPassword $proofForPassword) {
// passwordUpdate will be empty if the user has never set a password
$passwordUpdate = $user->getAttribute('passwordUpdate');
@@ -3375,7 +3368,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]),
]));
@@ -3406,7 +3399,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) {
@@ -3542,9 +3535,7 @@ App::post('/v1/account/recovery')
->inject('queueForMails')
->inject('queueForEvents')
->inject('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) {
->action(function (string $email, string $url, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Mail $queueForMails, Event $queueForEvents, ProofsToken $proofForToken) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
}
@@ -3580,7 +3571,7 @@ App::post('/v1/account/recovery')
'ip' => $request->getIP(),
]);
$authorization->addRole(Role::user($profile->getId())->toString());
Authorization::setRole(Role::user($profile->getId())->toString());
$recovery = $dbForProject->createDocument('tokens', $recovery
->setAttribute('$permissions', [
@@ -3678,7 +3669,7 @@ App::post('/v1/account/recovery')
->setRecipient($profile->getAttribute('email', ''))
->setName($profile->getAttribute('name', ''))
->setBody($body)
->appendVariables($emailVariables)
->setVariables($emailVariables)
->setSubject($subject)
->setPreview($preview);
@@ -3736,8 +3727,7 @@ App::put('/v1/account/recovery')
->inject('hooks')
->inject('proofForPassword')
->inject('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) {
->action(function (string $userId, string $secret, string $password, Response $response, User $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks, ProofsPassword $proofForPassword, ProofsToken $proofForToken) {
/** @var Appwrite\Utopia\Database\Documents\User $profile */
$profile = $dbForProject->getDocument('users', $userId);
@@ -3751,7 +3741,7 @@ App::put('/v1/account/recovery')
throw new Exception(Exception::USER_INVALID_TOKEN);
}
$authorization->addRole(Role::user($profile->getId())->toString());
Authorization::setRole(Role::user($profile->getId())->toString());
$newPassword = $proofForPassword->hash($password);
@@ -3854,8 +3844,7 @@ App::post('/v1/account/verifications/email')
->inject('queueForEvents')
->inject('queueForMails')
->inject('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) {
->action(function (string $url, Request $request, Response $response, Document $project, array $platform, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsToken $proofForToken) {
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
@@ -3884,7 +3873,7 @@ App::post('/v1/account/verifications/email')
'ip' => $request->getIP(),
]);
$authorization->addRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::user($user->getId())->toString());
$verification = $dbForProject->createDocument('tokens', $verification
->setAttribute('$permissions', [
@@ -4009,7 +3998,7 @@ App::post('/v1/account/verifications/email')
->setPreview($preview)
->setBody($body)
->setBodyTemplate($bodyTemplate)
->appendVariables($emailVariables)
->setVariables($emailVariables)
->setRecipient($user->getAttribute('email'))
->setName($user->getAttribute('name') ?? '');
@@ -4083,10 +4072,9 @@ App::put('/v1/account/verifications/email')
->inject('dbForProject')
->inject('queueForEvents')
->inject('proofForToken')
->inject('authorization')
->action(function (string $userId, string $secret, Response $response, User $user, Database $dbForProject, Event $queueForEvents, ProofsToken $proofForToken, Authorization $authorization) {
->action(function (string $userId, string $secret, Response $response, User $user, Database $dbForProject, Event $queueForEvents, ProofsToken $proofForToken) {
/** @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);
@@ -4098,7 +4086,7 @@ App::put('/v1/account/verifications/email')
throw new Exception(Exception::USER_INVALID_TOKEN);
}
$authorization->addRole(Role::user($profile->getId())->toString());
Authorization::setRole(Role::user($profile->getId())->toString());
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true));
@@ -4158,8 +4146,7 @@ App::post('/v1/account/verifications/phone')
->inject('queueForStatsUsage')
->inject('plan')
->inject('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) {
->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) {
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
}
@@ -4198,7 +4185,7 @@ App::post('/v1/account/verifications/phone')
'ip' => $request->getIP(),
]);
$authorization->addRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::user($user->getId())->toString());
$verification = $dbForProject->createDocument('tokens', $verification
->setAttribute('$permissions', [
@@ -4239,21 +4226,26 @@ App::post('/v1/account/verifications/phone')
->setRecipients([$user->getAttribute('phone')])
->setProviderType(MESSAGE_TYPE_SMS);
$helper = PhoneNumberUtil::getInstance();
try {
$countryCode = $helper->parse($phone)->getCountryCode();
if (isset($plan['authPhone'])) {
$timelimit = $timelimit('organization:{organizationId}', $plan['authPhone'], 30 * 24 * 60 * 60); // 30 days
$timelimit
->setParam('{organizationId}', $project->getAttribute('teamId'));
if (!empty($countryCode)) {
$queueForStatsUsage
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
$abuse = new Abuse($timelimit);
if ($abuse->check() && System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') {
$helper = PhoneNumberUtil::getInstance();
$countryCode = $helper->parse($phone)->getCountryCode();
if (!empty($countryCode)) {
$queueForStatsUsage
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
}
}
} catch (NumberParseException $e) {
// Ignore invalid phone number for country code stats
$queueForStatsUsage
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
->setProject($project)
->trigger();
}
$queueForStatsUsage
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
->setProject($project)
->trigger();
}
$verification->setAttribute('secret', $secret);
@@ -4299,10 +4291,9 @@ App::put('/v1/account/verifications/phone')
->inject('dbForProject')
->inject('queueForEvents')
->inject('proofForCode')
->inject('authorization')
->action(function (string $userId, string $secret, Response $response, User $user, Database $dbForProject, Event $queueForEvents, ProofsCode $proofForCode, Authorization $authorization) {
->action(function (string $userId, string $secret, Response $response, User $user, Database $dbForProject, Event $queueForEvents, ProofsCode $proofForCode) {
/** @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);
@@ -4314,7 +4305,7 @@ App::put('/v1/account/verifications/phone')
throw new Exception(Exception::USER_INVALID_TOKEN);
}
$authorization->addRole(Role::user($profile->getId())->toString());
Authorization::setRole(Role::user($profile->getId())->toString());
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('phoneVerification', true));
@@ -4367,13 +4358,12 @@ App::post('/v1/account/targets/push')
->inject('dbForProject')
->inject('store')
->inject('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) {
->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, User $user, Request $request, Response $response, Database $dbForProject, Store $store, ProofsToken $proofForToken) {
$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);
@@ -4448,10 +4438,9 @@ App::put('/v1/account/targets/:targetId/push')
->inject('request')
->inject('response')
->inject('dbForProject')
->inject('authorization')
->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) {
->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject) {
$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);
@@ -4514,9 +4503,8 @@ App::delete('/v1/account/targets/:targetId/push')
->inject('request')
->inject('response')
->inject('dbForProject')
->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));
->action(function (string $targetId, Event $queueForEvents, Delete $queueForDeletes, Document $user, Request $request, Response $response, Database $dbForProject) {
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
if ($target->isEmpty()) {
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
File diff suppressed because it is too large Load Diff
+3 -7
View File
@@ -28,12 +28,11 @@ use Utopia\Validator\Text;
App::init()
->groups(['graphql'])
->inject('project')
->inject('authorization')
->action(function (Document $project, Authorization $authorization) {
->action(function (Document $project) {
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);
}
@@ -224,11 +223,8 @@ function execute(
$flags = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE;
$validations = GraphQL::getStandardValidationRules();
if (System::getEnv('_APP_GRAPHQL_INTROSPECTION', 'enabled') === 'disabled') {
$validations[] = new DisableIntrospection();
}
if (System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') {
$validations[] = new DisableIntrospection();
$validations[] = new QueryComplexity($maxComplexity);
$validations[] = new QueryDepth($maxDepth);
}
File diff suppressed because it is too large Load Diff
+30 -35
View File
@@ -36,7 +36,6 @@ 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;
@@ -1074,9 +1073,8 @@ 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, Authorization $authorization, Response $response) {
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
@@ -1102,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.");
@@ -2483,9 +2481,8 @@ 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, Authorization $authorization, Response $response) {
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
@@ -2511,7 +2508,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.");
@@ -2785,27 +2782,29 @@ 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, Authorization $authorization, Response $response) {
->action(function (string $subscriberId, string $topicId, string $targetId, Event $queueForEvents, Database $dbForProject, 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);
}
if (!$authorization->isValid(new Input('subscribe', $topic->getAttribute('subscribe')))) {
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
$validator = new Authorization('subscribe');
if (!$validator->isValid($topic->getAttribute('subscribe'))) {
throw new Exception(Exception::USER_UNAUTHORIZED, $validator->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,
@@ -2838,7 +2837,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,
@@ -2883,9 +2882,8 @@ 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, Authorization $authorization, Response $response) {
->action(function (string $topicId, array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
@@ -2896,7 +2894,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);
@@ -2919,7 +2917,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.");
@@ -2933,10 +2931,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, $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')));
$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')));
return $subscriber
->setAttribute('target', $target)
@@ -3069,10 +3067,9 @@ 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, Authorization $authorization, Response $response) {
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
->action(function (string $topicId, string $subscriberId, Database $dbForProject, Response $response) {
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
if ($topic->isEmpty()) {
throw new Exception(Exception::TOPIC_NOT_FOUND);
@@ -3084,8 +3081,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)
@@ -3121,10 +3118,9 @@ 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, Authorization $authorization, Response $response) {
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
->action(function (string $topicId, string $subscriberId, Event $queueForEvents, Database $dbForProject, Response $response) {
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
if ($topic->isEmpty()) {
throw new Exception(Exception::TOPIC_NOT_FOUND);
@@ -3147,7 +3143,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,
@@ -3706,9 +3702,8 @@ 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, Authorization $authorization, Response $response) {
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
@@ -3734,7 +3729,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.");
+5 -9
View File
@@ -342,7 +342,6 @@ App::post('/v1/migrations/csv/imports')
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('authorization')
->inject('project')
->inject('platform')
->inject('deviceForFiles')
@@ -357,7 +356,6 @@ App::post('/v1/migrations/csv/imports')
Response $response,
Database $dbForProject,
Database $dbForPlatform,
Authorization $authorization,
Document $project,
array $platform,
Device $deviceForFiles,
@@ -365,7 +363,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');
}
@@ -376,7 +374,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);
}
@@ -493,7 +491,6 @@ App::post('/v1/migrations/csv/exports')
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
->inject('authorization')
->inject('project')
->inject('platform')
->inject('queueForEvents')
@@ -512,7 +509,6 @@ App::post('/v1/migrations/csv/exports')
Response $response,
Database $dbForProject,
Database $dbForPlatform,
Authorization $authorization,
Document $project,
array $platform,
Event $queueForEvents,
@@ -524,7 +520,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);
}
@@ -537,12 +533,12 @@ 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);
}
+4 -5
View File
@@ -45,10 +45,9 @@ 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, Authorization $authorization, callable $getLogsDB, array $smsRates) {
->action(function (string $startDate, string $endDate, string $period, Response $response, Document $project, Database $dbForProject, callable $getLogsDB, array $smsRates) {
$stats = $total = $usage = [];
$format = 'Y-m-d 00:00:00';
$firstDay = (new DateTime($startDate))->format($format);
@@ -103,7 +102,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;
@@ -287,7 +286,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),
@@ -295,7 +294,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),
+6 -4
View File
@@ -227,7 +227,6 @@ App::post('/v1/projects')
if (!$sharedTablesV2) {
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$dbForProject = new Database($adapter, $cache);
$dbForProject->setDatabase(APP_DATABASE);
if ($sharedTables) {
$dbForProject
@@ -1483,7 +1482,7 @@ App::post('/v1/projects/:projectId/keys')
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
->param('expire', null, new Nullable(new DatetimeValidator()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
->inject('response')
->inject('dbForPlatform')
@@ -1502,6 +1501,9 @@ App::post('/v1/projects/:projectId/keys')
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
// TODO: @hmacr Remove `projectInternalId` and `projectId` column writes before deleting the column.
'projectInternalId' => $project->getSequence(),
'projectId' => $project->getId(),
'resourceInternalId' => $project->getSequence(),
'resourceId' => $project->getId(),
'resourceType' => 'projects',
@@ -1625,7 +1627,7 @@ App::put('/v1/projects/:projectId/keys/:keyId')
->param('projectId', '', new UID(), 'Project unique ID.')
->param('keyId', '', new UID(), 'Key unique ID.')
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
->param('expire', null, new Nullable(new DatetimeValidator()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
->inject('response')
->inject('dbForPlatform')
@@ -1726,7 +1728,7 @@ App::post('/v1/projects/:projectId/jwts')
]
))
->param('projectId', '', new UID(), 'Project unique ID.')
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for JWT key. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for JWT key. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true)
->inject('response')
->inject('dbForPlatform')
+48 -58
View File
@@ -23,9 +23,9 @@ use Appwrite\Utopia\Database\Validator\Queries\Memberships;
use Appwrite\Utopia\Database\Validator\Queries\Teams;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use libphonenumber\NumberParseException;
use libphonenumber\PhoneNumberUtil;
use MaxMind\Db\Reader;
use Utopia\Abuse\Abuse;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Auth\Proofs\Password;
@@ -86,24 +86,22 @@ 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, Authorization $authorization, Event $queueForEvents) {
->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, 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)),
Permission::update(Role::team($teamId, 'owner')),
Permission::delete(Role::team($teamId, 'owner')),
],
'labels' => [],
'name' => $name,
'total' => ($isPrivilegedUser || $isAppUser) ? 0 : 1,
'prefs' => new \stdClass(),
@@ -433,22 +431,15 @@ App::delete('/v1/teams/:teamId')
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove team from DB');
}
// Sync delete
$deletes = new Deletes();
$deletes->deleteMemberships($getProjectDB, $team, $project);
// Async delete
if ($project->getId() === 'console') {
$queueForDeletes
->setType(DELETE_TYPE_TEAM_PROJECTS)
->setDocument($team)
->trigger();
->setDocument($team);
}
$queueForDeletes
->setType(DELETE_TYPE_DOCUMENT)
->setDocument($team);
$queueForEvents
->setParam('teamId', $team->getId())
->setPayload($response->output($team, Response::MODEL_TEAM))
@@ -500,7 +491,6 @@ App::post('/v1/teams/:teamId/memberships')
->inject('project')
->inject('user')
->inject('dbForProject')
->inject('authorization')
->inject('locale')
->inject('queueForMails')
->inject('queueForMessaging')
@@ -510,9 +500,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, 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());
->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());
$url = htmlentities($url);
if (empty($url)) {
@@ -629,13 +619,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->hasRole('team:' . $team->getId() . '/owner');
$isOwner = Authorization::isRole('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');
@@ -671,11 +661,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));
@@ -687,7 +677,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);
@@ -779,7 +769,7 @@ App::post('/v1/teams/:teamId/memberships')
->setPreview($preview)
->setRecipient($invitee->getAttribute('email'))
->setName($invitee->getAttribute('name', ''))
->appendVariables($emailVariables)
->setVariables($emailVariables)
->trigger();
} elseif (!empty($phone)) {
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
@@ -809,21 +799,26 @@ App::post('/v1/teams/:teamId/memberships')
->setRecipients([$phone])
->setProviderType('SMS');
$helper = PhoneNumberUtil::getInstance();
try {
$countryCode = $helper->parse($phone)->getCountryCode();
if (isset($plan['authPhone'])) {
$timelimit = $timelimit('organization:{organizationId}', $plan['authPhone'], 30 * 24 * 60 * 60); // 30 days
$timelimit
->setParam('{organizationId}', $project->getAttribute('teamId'));
if (!empty($countryCode)) {
$queueForStatsUsage
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
$abuse = new Abuse($timelimit);
if ($abuse->check() && System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') === 'enabled') {
$helper = PhoneNumberUtil::getInstance();
$countryCode = $helper->parse($phone)->getCountryCode();
if (!empty($countryCode)) {
$queueForStatsUsage
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
}
}
} catch (NumberParseException $e) {
// Ignore invalid phone number for country code stats
$queueForStatsUsage
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
->setProject($project)
->trigger();
}
$queueForStatsUsage
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
->setProject($project)
->trigger();
}
}
@@ -868,8 +863,7 @@ App::get('/v1/teams/:teamId/memberships')
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('authorization')
->action(function (string $teamId, array $queries, string $search, bool $includeTotal, Response $response, Document $project, Database $dbForProject, Authorization $authorization) {
->action(function (string $teamId, array $queries, string $search, bool $includeTotal, Response $response, Document $project, Database $dbForProject) {
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
@@ -939,7 +933,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);
@@ -1010,8 +1004,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('authorization')
->action(function (string $teamId, string $membershipId, Response $response, Document $project, Database $dbForProject, Authorization $authorization) {
->action(function (string $teamId, string $membershipId, Response $response, Document $project, Database $dbForProject) {
$team = $dbForProject->getDocument('teams', $teamId);
@@ -1031,7 +1024,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);
@@ -1110,9 +1103,8 @@ 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, Authorization $authorization, Event $queueForEvents) {
->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents) {
$team = $dbForProject->getDocument('teams', $teamId);
if ($team->isEmpty()) {
@@ -1129,9 +1121,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->hasRole('team:' . $team->getId() . '/owner');
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
$isAppUser = User::isApp(Authorization::getRoles());
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
if ($project->getId() === 'console') {
// Quick check: fetch up to 2 owners to determine if only one exists
@@ -1212,13 +1204,12 @@ 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, Authorization $authorization, $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, Document $project, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
$protocol = $request->getProtocol();
$membership = $dbForProject->getDocument('memberships', $membershipId);
@@ -1227,7 +1218,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);
@@ -1263,11 +1254,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->addRole(Role::user($user->getId())->toString());
Authorization::setRole(Role::user($user->getId())->toString());
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
@@ -1295,7 +1286,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$session = $dbForProject->createDocument('sessions', $session);
$authorization->addRole(Role::user($userId)->toString());
Authorization::setRole(Role::user($userId)->toString());
$encoded = $store
->setProperty('id', $user->getId())
@@ -1333,7 +1324,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())
@@ -1377,9 +1368,8 @@ 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, Authorization $authorization, Event $queueForEvents) {
->action(function (string $teamId, string $membershipId, Document $user, Document $project, Response $response, Database $dbForProject, Event $queueForEvents) {
$membership = $dbForProject->getDocument('memberships', $membershipId);
@@ -1437,7 +1427,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
+3 -3
View File
@@ -2678,8 +2678,8 @@ App::get('/v1/users/usage')
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->inject('response')
->inject('dbForProject')
->inject('authorization')
->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) {
->inject('register')
->action(function (string $range, Response $response, Database $dbForProject) {
$periods = Config::getParam('usage', []);
$stats = $usage = [];
@@ -2689,7 +2689,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]),
+30 -33
View File
@@ -76,7 +76,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, Authorization $authorization, Build $queueForBuilds, callable $getProjectDB, Request $request, 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, Build $queueForBuilds, callable $getProjectDB, array $platform) {
$errors = [];
foreach ($repositories as $repository) {
try {
@@ -87,12 +87,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 +141,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 +180,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 +191,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 +212,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 +251,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 +294,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()),
@@ -306,7 +306,6 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
'resourceType' => $resourceCollection,
'entrypoint' => $resource->getAttribute('entrypoint', ''),
'buildCommands' => \implode(' && ', $commands),
'startCommand' => $resource->getAttribute('startCommand', ''),
'buildOutput' => $resource->getAttribute('outputDirectory', ''),
'adapter' => $resource->getAttribute('adapter', ''),
'fallbackFile' => $resource->getAttribute('fallbackFile', ''),
@@ -335,17 +334,17 @@ $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();
// Deployment preview
$sitesDomain = $platform['sitePreviewDomain'];
$sitesDomain = System::getEnv('_APP_DOMAIN_SITES', '');
$domain = ID::unique() . "." . $sitesDomain;
$ruleId = md5($domain);
$previewRuleId = $ruleId;
$authorization->skip(
Authorization::skip(
fn () => $dbForPlatform->createDocument('rules', new Document([
'$id' => $ruleId,
'projectId' => $project->getId(),
@@ -378,7 +377,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(),
@@ -409,7 +408,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(),
@@ -461,7 +460,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', '')) : '';
@@ -473,7 +472,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));
}
}
}
@@ -1477,12 +1476,11 @@ 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, Authorization $authorization, callable $getProjectDB, Build $queueForBuilds, array $platform) use ($createGitDeployments) {
function (GitHub $github, Request $request, Response $response, Database $dbForPlatform, 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', '');
@@ -1518,14 +1516,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, $authorization, $queueForBuilds, $getProjectDB, $request, $platform);
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthorName, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $queueForBuilds, $getProjectDB, $platform);
}
} elseif ($event == $github::EVENT_INSTALLATION) {
if ($parsedPayload["action"] == "deleted") {
@@ -1538,16 +1536,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) {
@@ -1576,12 +1574,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, $authorization, $queueForBuilds, $getProjectDB, $request, $platform);
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForPlatform, $queueForBuilds, $getProjectDB, $platform);
} elseif ($parsedPayload["action"] == "closed") {
// Allowed external contributions cleanup
@@ -1590,7 +1588,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')
]));
@@ -1601,7 +1599,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));
}
}
}
@@ -1788,18 +1786,17 @@ 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, Request $request, Response $response, Document $project, Database $dbForPlatform, Authorization $authorization, callable $getProjectDB, Build $queueForBuilds, array $platform) use ($createGitDeployments) {
->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Response $response, Document $project, Database $dbForPlatform, 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()])
]));
@@ -1817,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');
@@ -1849,7 +1846,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
$providerCommitMessage = $pullRequestResponse['title'] ?? '';
$providerCommitUrl = $pullRequestResponse['html_url'] ?? '';
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, '', '', '', '', $providerCommitHash, '', '', '', '', $providerPullRequestId, true, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $request, $platform);
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, true, $dbForPlatform, $queueForBuilds, $getProjectDB, $platform);
$response->noContent();
});
+97 -205
View File
@@ -6,7 +6,6 @@ use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Auth\Key;
use Appwrite\Event\Certificate;
use Appwrite\Event\Delete as DeleteEvent;
use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\StatsUsage;
@@ -60,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, Authorization $authorization, ?Key $apiKey, DeleteEvent $queueForDeletes, int $executionsRetentionCount)
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)
{
$host = $request->getHostname() ?? '';
if (!empty($previewHostname)) {
@@ -68,16 +67,16 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
}
// TODO: (@Meldiron) Remove after 1.7.x migration
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();
}
$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();
});
$errorView = __DIR__ . '/../views/general/error.phtml';
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
@@ -112,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)
);
@@ -120,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));
}
/**
@@ -159,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
@@ -173,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') {
@@ -200,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');
@@ -243,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;
}
@@ -256,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');
@@ -803,20 +802,6 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
->setProject($project)
->trigger();
/* cleanup */
if ($executionsRetentionCount > 0 && ENABLE_EXECUTIONS_LIMIT_ON_ROUTE) {
$resourceType = $type === 'function'
? RESOURCE_TYPE_FUNCTIONS
: RESOURCE_TYPE_SITES;
$queueForDeletes
->setProject($project)
->setResourceType($resourceType)
->setResource($resource->getSequence())
->setType(DELETE_TYPE_EXECUTIONS_LIMIT)
->trigger();
}
return true;
} elseif ($type === 'api') {
return false;
@@ -827,6 +812,8 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
} else {
throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Unknown resource type ' . $type, view: $errorView);
}
return false;
}
App::init()
@@ -875,18 +862,15 @@ App::init()
->inject('devKey')
->inject('apiKey')
->inject('cors')
->inject('authorization')
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->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, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->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) {
/*
* 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, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
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)) {
$utopia->getRoute()?->label('router', true);
}
}
@@ -1049,8 +1033,7 @@ App::init()
->inject('dbForPlatform')
->inject('queueForCertificates')
->inject('platform')
->inject('authorization')
->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform, Authorization $authorization) {
->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform) {
$hostname = $request->getHostname();
$cache = Config::getParam('hostnames', []);
$platformHostnames = $platform['hostnames'] ?? [];
@@ -1078,64 +1061,64 @@ App::init()
}
// 4. Check/create rule (requires DB access)
$authorization->skip(function () use ($dbForPlatform, $domain, $console, $queueForCertificates, &$cache) {
try {
// TODO: (@Meldiron) Remove after 1.7.x migration
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
$document = $isMd5
? $dbForPlatform->getDocument('rules', md5($domain->get()))
: $dbForPlatform->findOne('rules', [
Query::equal('domain', [$domain->get()]),
]);
if (!$document->isEmpty()) {
return;
}
// 5. Create new rule
$owner = '';
$fallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
$funcDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
$siteDomain = System::getEnv('_APP_DOMAIN_SITES', '');
if (!empty($fallback) && \str_ends_with($domain->get(), $fallback)) {
$funcDomain = $fallback;
}
if (
(!empty($funcDomain) && \str_ends_with($domain->get(), $funcDomain)) ||
(!empty($siteDomain) && \str_ends_with($domain->get(), $siteDomain))
) {
$owner = 'Appwrite';
}
$ruleId = $isMd5 ? md5($domain->get()) : ID::unique();
$document = new Document([
'$id' => $ruleId,
'domain' => $domain->get(),
'type' => 'api',
'status' => 'verifying',
'projectId' => $console->getId(),
'projectInternalId' => $console->getSequence(),
'search' => implode(' ', [$ruleId, $domain->get()]),
'owner' => $owner,
'region' => $console->getAttribute('region')
Authorization::disable();
try {
// TODO: (@Meldiron) Remove after 1.7.x migration
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
$document = $isMd5
? $dbForPlatform->getDocument('rules', md5($domain->get()))
: $dbForPlatform->findOne('rules', [
Query::equal('domain', [$domain->get()]),
]);
$dbForPlatform->createDocument('rules', $document);
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
$queueForCertificates
->setDomain($document)
->setSkipRenewCheck(true)
->trigger();
} catch (Duplicate $e) {
Console::info('Certificate already exists');
} finally {
$cache[$domain->get()] = true;
Config::setParam('hostnames', $cache);
if (!$document->isEmpty()) {
return;
}
});
// 5. Create new rule
$owner = '';
$fallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', '');
$funcDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', '');
$siteDomain = System::getEnv('_APP_DOMAIN_SITES', '');
if (!empty($fallback) && \str_ends_with($domain->get(), $fallback)) {
$funcDomain = $fallback;
}
if (
(!empty($funcDomain) && \str_ends_with($domain->get(), $funcDomain)) ||
(!empty($siteDomain) && \str_ends_with($domain->get(), $siteDomain))
) {
$owner = 'Appwrite';
}
$ruleId = $isMd5 ? md5($domain->get()) : ID::unique();
$document = new Document([
'$id' => $ruleId,
'domain' => $domain->get(),
'type' => 'api',
'status' => 'verifying',
'projectId' => $console->getId(),
'projectInternalId' => $console->getSequence(),
'search' => implode(' ', [$ruleId, $domain->get()]),
'owner' => $owner,
'region' => $console->getAttribute('region')
]);
$dbForPlatform->createDocument('rules', $document);
Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...');
$queueForCertificates
->setDomain($document)
->setSkipRenewCheck(true)
->trigger();
} catch (Duplicate $e) {
Console::info('Certificate already exists');
} finally {
$cache[$domain->get()] = true;
Config::setParam('hostnames', $cache);
Authorization::reset();
}
});
App::options()
@@ -1158,17 +1141,14 @@ App::options()
->inject('devKey')
->inject('apiKey')
->inject('cors')
->inject('authorization')
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->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, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->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) {
/*
* Appwrite Router
*/
$platformHostnames = $platform['hostnames'] ?? [];
// Only run Router when external domain
if (!in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
$utopia->getRoute()?->label('router', true);
}
}
@@ -1202,8 +1182,7 @@ App::error()
->inject('log')
->inject('queueForStatsUsage')
->inject('devKey')
->inject('authorization')
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, StatsUsage $queueForStatsUsage, Document $devKey, Authorization $authorization) {
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, StatsUsage $queueForStatsUsage) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
$route = $utopia->getRoute();
$class = \get_class($error);
@@ -1228,7 +1207,7 @@ App::error()
}
switch ($class) {
case Utopia\Exception::class:
case 'Utopia\Exception':
$error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error);
switch ($code) {
case 400:
@@ -1239,10 +1218,10 @@ App::error()
break;
}
break;
case Utopia\Database\Exception\Authorization::class:
case 'Utopia\Database\Exception\Authorization':
$error = new AppwriteException(AppwriteException::USER_UNAUTHORIZED);
break;
case Utopia\Database\Exception\Timeout::class:
case 'Utopia\Database\Exception\Timeout':
$error = new AppwriteException(AppwriteException::DATABASE_TIMEOUT, previous: $error);
break;
}
@@ -1285,7 +1264,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)) {
@@ -1347,87 +1326,7 @@ App::error()
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('roles', $authorization->getRoles());
try {
/* add queries to log */
$queries = $request->getParam('queries', []);
if (!empty($queries) && is_array($queries)) {
$parsedQueries = Query::parseQueries($queries);
// format query by removing sensitive values
$formatQuery = function (array $queryArray) use (&$formatQuery): ?array {
$method = $queryArray['method'] ?? '';
$values = $queryArray['values'] ?? [];
$attribute = $queryArray['attribute'] ?? '';
if (!is_string($method) || $method === '') {
return null;
}
// logical queries - recursively format nested queries
if (in_array($method, [Query::TYPE_AND, Query::TYPE_OR], true)) {
$nested = [];
foreach ($values as $nestedArray) {
if (is_array($nestedArray)) {
$formatted = $formatQuery($nestedArray);
if ($formatted !== null) {
$nested[] = $formatted;
}
}
}
return empty($nested) ? null : [$method => $nested];
}
// select - show selected attributes
if ($method === Query::TYPE_SELECT) {
$attributes = array_values(array_filter($values, 'is_string'));
return [$method => $attributes];
}
// pagination
if (in_array($method, [
Query::TYPE_LIMIT,
Query::TYPE_OFFSET,
Query::TYPE_CURSOR_AFTER,
Query::TYPE_CURSOR_BEFORE
], true)) {
return [$method => []];
}
// orders
if (in_array($method, [
Query::TYPE_ORDER_DESC,
Query::TYPE_ORDER_ASC,
Query::TYPE_ORDER_RANDOM
], true)) {
return [$method => !empty($attribute) ? [$attribute] : []];
}
// filter
if (!empty($attribute)) {
return [$method => [$attribute]];
}
// fallback
return [$method => []];
};
$formattedQueries = [];
foreach ($parsedQueries as $query) {
$formatted = $formatQuery($query->toArray());
if ($formatted !== null) {
$formattedQueries[] = $formatted;
}
}
if (!empty($formattedQueries)) {
$log->addExtra('queries', $formattedQueries);
}
}
} catch (Throwable $_) {
// don't fail the error handler
}
$log->addExtra('roles', Authorization::getRoles());
$action = 'UNKNOWN_NAMESPACE.UNKNOWN.METHOD';
if (!empty($sdk)) {
@@ -1503,7 +1402,7 @@ App::error()
$template = $error->getView() ?? (($route) ? $route->getLabel('error', null) : null);
// TODO: Ideally use group 'api' here, but all wildcard routes seem to have 'api' at the moment
if (empty($route) || !\str_starts_with($route->getPath(), '/v1')) {
if (!\str_starts_with($route->getPath(), '/v1')) {
$template = __DIR__ . '/../views/general/error.phtml';
}
@@ -1551,16 +1450,13 @@ App::get('/robots.txt')
->inject('platform')
->inject('previewHostname')
->inject('apiKey')
->inject('authorization')
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->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, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->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) {
$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, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
$utopia->getRoute()?->label('router', true);
}
}
@@ -1586,16 +1482,13 @@ App::get('/humans.txt')
->inject('platform')
->inject('previewHostname')
->inject('apiKey')
->inject('authorization')
->inject('queueForDeletes')
->inject('executionsRetentionCount')
->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, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
->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) {
$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, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
$utopia->getRoute()?->label('router', true);
}
}
@@ -1679,8 +1572,7 @@ App::get('/v1/ping')
->inject('project')
->inject('dbForPlatform')
->inject('queueForEvents')
->inject('authorization')
->action(function (Response $response, Document $project, Database $dbForPlatform, Event $queueForEvents, Authorization $authorization) {
->action(function (Response $response, Document $project, Database $dbForPlatform, Event $queueForEvents) {
if ($project->isEmpty() || $project->getId() === 'console') {
throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND);
}
@@ -1692,7 +1584,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);
});
+4 -1
View File
@@ -191,7 +191,7 @@ App::post('/v1/mock/api-key-unprefixed')
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$scopes = array_keys(Config::getParam('projectScopes'));
$scopes = array_keys(Config::getParam('scopes'));
$key = new Document([
'$id' => ID::unique(),
@@ -200,6 +200,9 @@ App::post('/v1/mock/api-key-unprefixed')
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
// TODO: @hmacr Remove `projectInternalId` and `projectId` column writes before deleting the column.
'projectInternalId' => $project->getSequence(),
'projectId' => $project->getId(),
'resourceInternalId' => $project->getSequence(),
'resourceId' => $project->getId(),
'resourceType' => 'projects',
+228 -99
View File
@@ -10,12 +10,12 @@ use Appwrite\Event\Event;
use Appwrite\Event\Func;
use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\Migration;
use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Functions\EventProcessor;
use Appwrite\SDK\Method;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Request;
@@ -30,7 +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;
use Utopia\Validator\WhiteList;
@@ -74,6 +74,151 @@ $parseLabel = function (string $label, array $responsePayload, array $requestPar
return $label;
};
/**
* This isolated event handling for `users.*.create` which is based on a `Database::EVENT_DOCUMENT_CREATE` listener may look odd, but it is **intentional**.
*
* Accounts can be created in many ways beyond `createAccount`
* (anonymous, OAuth, phone, etc.), and those flows are probably not covered in event tests; so we handle this here.
*/
$eventDatabaseListener = function (Document $project, Document $document, Response $response, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime) {
// Only trigger events for user creation with the database listener.
if ($document->getCollection() !== 'users') {
return;
}
$queueForEvents
->setEvent('users.[userId].create')
->setParam('userId', $document->getId())
->setPayload($response->output($document, Response::MODEL_USER));
// Trigger functions, webhooks, and realtime events
$queueForFunctions
->from($queueForEvents)
->trigger();
/** Trigger webhooks events only if a project has them enabled */
if (!empty($project->getAttribute('webhooks'))) {
$queueForWebhooks
->from($queueForEvents)
->trigger();
}
/** Trigger realtime events only for non console events */
if ($queueForEvents->getProject()->getId() !== 'console') {
$queueForRealtime
->from($queueForEvents)
->trigger();
}
};
$usageDatabaseListener = function (string $event, Document $document, StatsUsage $queueForStatsUsage) {
$value = 1;
switch ($event) {
case Database::EVENT_DOCUMENT_DELETE:
$value = -1;
break;
case Database::EVENT_DOCUMENTS_DELETE:
$value = -1 * $document->getAttribute('modified', 0);
break;
case Database::EVENT_DOCUMENTS_CREATE:
$value = $document->getAttribute('modified', 0);
break;
case Database::EVENT_DOCUMENTS_UPSERT:
$value = $document->getAttribute('created', 0);
break;
}
switch (true) {
case $document->getCollection() === 'teams':
$queueForStatsUsage->addMetric(METRIC_TEAMS, $value); // per project
break;
case $document->getCollection() === 'users':
$queueForStatsUsage->addMetric(METRIC_USERS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage->addReduce($document);
}
break;
case $document->getCollection() === 'sessions': // sessions
$queueForStatsUsage->addMetric(METRIC_SESSIONS, $value); //per project
break;
case $document->getCollection() === 'databases': // databases
$queueForStatsUsage->addMetric(METRIC_DATABASES, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'database_') && !str_contains($document->getCollection(), 'collection'): //collections
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$queueForStatsUsage
->addMetric(METRIC_COLLECTIONS, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_COLLECTIONS), $value);
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'database_') && str_contains($document->getCollection(), '_collection_'): //documents
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$collectionInternalId = $parts[3] ?? 0;
$queueForStatsUsage
->addMetric(METRIC_DOCUMENTS, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_DOCUMENTS), $value) // per database
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS), $value); // per collection
break;
case $document->getCollection() === 'buckets': //buckets
$queueForStatsUsage
->addMetric(METRIC_BUCKETS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage
->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'bucket_'): // files
$parts = explode('_', $document->getCollection());
$bucketInternalId = $parts[1];
$queueForStatsUsage
->addMetric(METRIC_FILES, $value) // per project
->addMetric(METRIC_FILES_STORAGE, $document->getAttribute('sizeOriginal') * $value) // per project
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES), $value) // per bucket
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES_STORAGE), $document->getAttribute('sizeOriginal') * $value); // per bucket
break;
case $document->getCollection() === 'functions':
$queueForStatsUsage
->addMetric(METRIC_FUNCTIONS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage
->addReduce($document);
}
break;
case $document->getCollection() === 'sites':
$queueForStatsUsage
->addMetric(METRIC_SITES, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage
->addReduce($document);
}
break;
case $document->getCollection() === 'deployments':
$queueForStatsUsage
->addMetric(METRIC_DEPLOYMENTS, $value) // per project
->addMetric(METRIC_DEPLOYMENTS_STORAGE, $document->getAttribute('size') * $value) // per project
->addMetric(str_replace(['{resourceType}'], [$document->getAttribute('resourceType')], METRIC_RESOURCE_TYPE_DEPLOYMENTS), $value) // per function
->addMetric(str_replace(['{resourceType}'], [$document->getAttribute('resourceType')], METRIC_RESOURCE_TYPE_DEPLOYMENTS_STORAGE), $document->getAttribute('size') * $value)
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS), $value) // per function
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS_STORAGE), $document->getAttribute('size') * $value);
break;
default:
break;
}
};
App::init()
->groups(['api'])
->inject('utopia')
@@ -88,8 +233,7 @@ App::init()
->inject('mode')
->inject('team')
->inject('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) {
->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) {
$route = $utopia->getRoute();
/**
@@ -157,6 +301,10 @@ App::init()
// Step 5: API Key Authentication
if (!empty($apiKey)) {
// Verify no user session exists simultaneously
if (!$user->isEmpty()) {
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
}
// Check if key is expired
if ($apiKey->isExpired()) {
throw new Exception(Exception::PROJECT_KEY_EXPIRED);
@@ -170,7 +318,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' => '',
@@ -185,38 +333,23 @@ App::init()
}
// For standard keys, update last accessed time
if (\in_array($apiKey->getType(), [API_KEY_STANDARD, API_KEY_ORGANIZATION, API_KEY_ACCOUNT])) {
$dbKey = null;
if (!empty($apiKey->getProjectId())) {
$dbKey = $project->find(
key: 'secret',
find: $request->getHeader('x-appwrite-key', ''),
subject: 'keys'
);
} elseif (!empty($apiKey->getUserId())) {
$dbKey = $user->find(
key: 'secret',
find: $request->getHeader('x-appwrite-key', ''),
subject: 'keys'
);
} elseif (!empty($apiKey->getTeamId())) {
$dbKey = $team->find(
key: 'secret',
find: $request->getHeader('x-appwrite-key', ''),
subject: 'keys'
);
}
if ($apiKey->getType() === API_KEY_STANDARD) {
$dbKey = $project->find(
key: 'secret',
find: $request->getHeader('x-appwrite-key', ''),
subject: 'keys'
);
if (!$dbKey) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$updates = new Document();
$accessedAt = $dbKey->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
$updates->setAttribute('accessedAt', DateTime::now());
$dbKey->setAttribute('accessedAt', DateTime::now());
$dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey);
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
}
$sdkValidator = new WhiteList($servers, true);
@@ -227,21 +360,12 @@ App::init()
if (!in_array($sdk, $sdks)) {
$sdks[] = $sdk;
$dbKey->setAttribute('sdks', $sdks);
$updates->setAttribute('sdks', $sdks);
$updates->setAttribute('accessedAt', Datetime::now());
}
}
if (!$updates->isEmpty()) {
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->updateDocument('keys', $dbKey->getId(), $updates));
if (!empty($apiKey->getProjectId())) {
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('projects', $project->getId()));
} elseif (!empty($apiKey->getUserId())) {
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('users', $user->getId()));
} elseif (!empty($apiKey->getTeamId())) {
$dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->purgeCachedDocument('teams', $team->getId()));
/** Update access time as well */
$dbKey->setAttribute('accessedAt', Datetime::now());
$dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey);
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
}
}
@@ -268,14 +392,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->addRole($role);
foreach ($user->getRoles($authorization) as $authRole) {
$authorization->addRole($authRole);
Authorization::setRole($role);
foreach ($user->getRoles() as $authRole) {
Authorization::setRole($authRole);
}
// Step 6: Update project and user last activity
@@ -283,7 +407,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));
}
}
@@ -318,7 +442,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);
}
@@ -363,6 +487,9 @@ App::init()
->inject('response')
->inject('project')
->inject('user')
->inject('publisher')
->inject('publisherFunctions')
->inject('publisherWebhooks')
->inject('queueForEvents')
->inject('queueForMessaging')
->inject('queueForAudits')
@@ -372,6 +499,7 @@ App::init()
->inject('queueForStatsUsage')
->inject('queueForFunctions')
->inject('queueForMails')
->inject('queueForMigrations')
->inject('dbForProject')
->inject('timelimit')
->inject('resourceToken')
@@ -381,15 +509,14 @@ App::init()
->inject('devKey')
->inject('telemetry')
->inject('platform')
->inject('authorization')
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Mail $queueForMails, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform, Authorization $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) 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);
}
@@ -419,7 +546,7 @@ App::init()
$closestLimit = null;
$roles = $authorization->getRoles();
$roles = Authorization::getRoles();
$isPrivilegedUser = User::isPrivileged($roles);
$isAppUser = User::isApp($roles);
@@ -502,16 +629,38 @@ App::init()
$queueForBuilds->setPlatform($platform);
$queueForMails->setPlatform($platform);
// Clone the queues, to prevent events triggered by the database listener
// from overwriting the events that are supposed to be triggered in the shutdown hook.
$queueForEventsClone = new Event($publisher);
$queueForFunctions = new Func($publisherFunctions);
$queueForWebhooks = new Webhook($publisherWebhooks);
$queueForRealtime = new Realtime();
$dbForProject
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENTS_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENTS_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENTS_UPSERT, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener(
$project,
$document,
$response,
$queueForEventsClone->from($queueForEvents),
$queueForFunctions->from($queueForEvents),
$queueForWebhooks->from($queueForEvents),
$queueForRealtime->from($queueForEvents)
));
$useCache = $route->getLabel('cache', false);
$storageCacheOperationsCounter = $telemetry->createCounter('storage.cache.operations.load');
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())
);
@@ -528,10 +677,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);
@@ -542,7 +691,8 @@ App::init()
}
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
$valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
$validator = new Authorization(Database::PERMISSION_READ);
$valid = $validator->isValid($bucket->getRead());
if (!$fileSecurity && !$valid && !$isToken) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
@@ -553,7 +703,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()) {
@@ -564,11 +714,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));
}
}
}
@@ -664,10 +814,8 @@ App::shutdown()
->inject('queueForWebhooks')
->inject('queueForRealtime')
->inject('dbForProject')
->inject('authorization')
->inject('timelimit')
->inject('eventProcessor')
->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, Authorization $authorization, callable $timelimit, EventProcessor $eventProcessor) use ($parseLabel) {
->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, callable $timelimit) use ($parseLabel) {
$responsePayload = $response->getPayload();
@@ -676,15 +824,9 @@ App::shutdown()
$queueForEvents->setPayload($responsePayload);
}
// Get project and function/webhook events (cached)
$functionsEvents = $eventProcessor->getFunctionsEvents($project, $dbForProject);
$webhooksEvents = $eventProcessor->getWebhooksEvents($project);
// Generate events for this operation
$generatedEvents = Event::generateEvents(
$queueForEvents->getEvent(),
$queueForEvents->getParams()
);
$queueForFunctions
->from($queueForEvents)
->trigger();
if ($project->getId() !== 'console') {
$queueForRealtime
@@ -692,28 +834,15 @@ App::shutdown()
->trigger();
}
// Only trigger functions if there are matching function events
if (!empty($functionsEvents)) {
foreach ($generatedEvents as $event) {
if (isset($functionsEvents[$event])) {
$queueForFunctions
->from($queueForEvents)
->trigger();
break;
}
}
}
// Only trigger webhooks if there are matching webhook events
if (!empty($webhooksEvents)) {
foreach ($generatedEvents as $event) {
if (isset($webhooksEvents[$event])) {
$queueForWebhooks
->from($queueForEvents)
->trigger();
break;
}
}
/** Trigger webhooks events only if a project has them enabled
* A future optimisation is to only trigger webhooks if the webhook is "enabled"
* But it might have performance implications on the API due to the number of webhooks etc.
* Some profiling is needed to see if this is a problem.
*/
if (!empty($project->getAttribute('webhooks'))) {
$queueForWebhooks
->from($queueForEvents)
->trigger();
}
}
@@ -847,11 +976,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,
@@ -861,7 +990,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']);
}
@@ -873,7 +1002,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)) {
+3 -4
View File
@@ -36,8 +36,7 @@ App::init()
->inject('request')
->inject('project')
->inject('geodb')
->inject('authorization')
->action(function (App $utopia, Request $request, Document $project, Reader $geodb, Authorization $authorization) {
->action(function (App $utopia, Request $request, Document $project, Reader $geodb) {
$denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', '');
if (!empty($denylist && $project->getId() === 'console')) {
$countries = explode(',', $denylist);
@@ -50,8 +49,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;
+11 -17
View File
@@ -27,6 +27,7 @@ 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;
@@ -260,9 +261,7 @@ $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, $app) {
$authorization = $app->getResource('authorization');
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);
@@ -322,9 +321,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',
@@ -339,7 +338,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'] ?? [];
@@ -367,7 +366,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));
}
});
@@ -459,12 +458,8 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
App::setResource('pools', fn () => $pools);
try {
$authorization = $app->getResource('authorization');
$request->setAuthorization($authorization);
$response->setAuthorization($authorization);
$authorization->cleanRoles();
$authorization->addRole(Role::any()->toString());
Authorization::cleanRoles();
Authorization::setRole(Role::any()->toString());
$app->run($request, $response);
} catch (\Throwable $th) {
@@ -506,7 +501,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', isset($authorization) ? $authorization->getRoles() : []);
$log->addExtra('roles', Authorization::getRoles());
$sdk = $route->getLabel("sdk", false);
@@ -565,7 +560,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, $app) {
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $domains, &$lastSyncUpdate) {
try {
$time = DateTime::now();
$limit = 1000;
@@ -582,8 +577,7 @@ $http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
}
$results = [];
try {
$authorization = $app->getResource('authorization');
$results = $authorization->skip(fn () => $dbForPlatform->find('rules', $queries));
$results = Authorization::skip(fn () => $dbForPlatform->find('rules', $queries));
} catch (Throwable $th) {
Console::error($th->getMessage());
}
+1 -3
View File
@@ -22,9 +22,7 @@ Config::load('collections', __DIR__ . '/../config/collections.php', $configAdapt
Config::load('frameworks', __DIR__ . '/../config/frameworks.php', $configAdapter);
Config::load('usage', __DIR__ . '/../config/usage.php', $configAdapter);
Config::load('roles', __DIR__ . '/../config/roles.php', $configAdapter); // User roles and scopes
Config::load('projectScopes', __DIR__ . '/../config/scopes/project.php', $configAdapter);
Config::load('organizationScopes', __DIR__ . '/../config/scopes/organization.php', $configAdapter);
Config::load('accountScopes', __DIR__ . '/../config/scopes/account.php', $configAdapter);
Config::load('scopes', __DIR__ . '/../config/scopes.php', $configAdapter); // User roles and scopes
Config::load('services', __DIR__ . '/../config/services.php', $configAdapter); // List of services
Config::load('variables', __DIR__ . '/../config/variables.php', $configAdapter); // List of env variables
Config::load('regions', __DIR__ . '/../config/regions.php', $configAdapter); // List of available regions
+2 -10
View File
@@ -161,9 +161,6 @@ const ACTIVITY_TYPE_GUEST = 'guest';
const MFA_RECENT_DURATION = 1800; // 30 mins
// Database name
const APP_DATABASE = 'appwrite';
// Database Reconnect
const DATABASE_RECONNECT_SLEEP = 2;
const DATABASE_RECONNECT_MAX_ATTEMPTS = 10;
@@ -181,10 +178,8 @@ const BUILD_TYPE_DEPLOYMENT = 'deployment';
const BUILD_TYPE_RETRY = 'retry';
// Deletion Types
const ENABLE_EXECUTIONS_LIMIT_ON_ROUTE = false;
const DELETE_TYPE_DATABASES = 'databases';
const DELETE_TYPE_DOCUMENT = 'document';
const DELETE_TYPE_COLLECTIONS = 'collections';
const DELETE_TYPE_TRANSACTION = 'transaction';
@@ -194,10 +189,8 @@ const DELETE_TYPE_SITES = 'sites';
const DELETE_TYPE_FUNCTIONS = 'functions';
const DELETE_TYPE_DEPLOYMENTS = 'deployments';
const DELETE_TYPE_USERS = 'users';
const DELETE_TYPE_TEAMS = 'teams';
const DELETE_TYPE_TEAM_PROJECTS = 'teams_projects';
const DELETE_TYPE_EXECUTIONS = 'executions';
const DELETE_TYPE_EXECUTIONS_LIMIT = 'executionsLimit';
const DELETE_TYPE_AUDIT = 'audit';
const DELETE_TYPE_ABUSE = 'abuse';
const DELETE_TYPE_USAGE = 'usage';
@@ -250,8 +243,6 @@ const MESSAGE_TYPE_PUSH = 'push';
// API key types
const API_KEY_STANDARD = 'standard';
const API_KEY_DYNAMIC = 'dynamic';
const API_KEY_ORGANIZATION = 'organization';
const API_KEY_ACCOUNT = 'account';
// Usage metrics
const METRIC_TEAMS = 'teams';
const METRIC_USERS = 'users';
@@ -369,6 +360,7 @@ const RESOURCE_TYPE_TOPICS = 'topics';
const RESOURCE_TYPE_SUBSCRIBERS = 'subscribers';
const RESOURCE_TYPE_MESSAGES = 'messages';
const RESOURCE_TYPE_EXECUTIONS = 'executions';
const RESOURCE_TYPE_PAYMENTS = 'payments';
// Resource types for Tokens
const TOKENS_RESOURCE_TYPE_FILES = 'files';
+24 -54
View File
@@ -4,6 +4,7 @@ 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(
@@ -69,11 +70,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
$attributes = $database->getAuthorization()->skip(fn () => $database->find('attributes', [
$attributes = $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');
@@ -104,12 +105,12 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return $database
->find('indexes', [
Query::equal('collectionInternalId', [$document->getSequence()]),
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
Query::limit($database->getLimitForIndexes()),
]));
]);
}
);
@@ -119,11 +120,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return $database
->find('platforms', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
]);
}
);
@@ -133,12 +134,12 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return $database
->find('keys', [
Query::equal('resourceType', ['projects']),
Query::equal('resourceInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
]);
}
);
@@ -148,11 +149,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return $database
->find('devKeys', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
]);
}
);
@@ -162,11 +163,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return $database
->find('webhooks', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
]);
}
);
@@ -176,7 +177,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database->find('sessions', [
return Authorization::skip(fn () => $database->find('sessions', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
@@ -189,7 +190,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return Authorization::skip(fn () => $database
->find('tokens', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
@@ -203,7 +204,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return Authorization::skip(fn () => $database
->find('challenges', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
@@ -217,7 +218,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return Authorization::skip(fn () => $database
->find('authenticators', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
@@ -231,7 +232,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return Authorization::skip(fn () => $database
->find('memberships', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
@@ -251,14 +252,14 @@ Database::addFilter(
default => ['function', 'site']
};
return $database->getAuthorization()->skip(fn () => $database
return $database
->find('variables', [
Query::equal('resourceInternalId', [$document->getSequence()]),
Query::equal('resourceType', $resourceType),
Query::orderAsc('resourceType'),
Query::orderAsc(),
Query::limit(APP_LIMIT_SUBQUERY),
]));
]);
}
);
@@ -294,11 +295,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return $database
->find('variables', [
Query::equal('resourceType', ['project']),
Query::limit(APP_LIMIT_SUBQUERY)
]));
]);
}
);
@@ -331,7 +332,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
return Authorization::skip(fn () => $database
->find('targets', [
Query::equal('userInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY)
@@ -345,7 +346,7 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
$targetIds = $database->getAuthorization()->skip(fn () => \array_map(
$targetIds = Authorization::skip(fn () => \array_map(
fn ($document) => $document->getAttribute('targetInternalId'),
$database->find('subscribers', [
Query::equal('topicInternalId', [$document->getSequence()]),
@@ -433,34 +434,3 @@ Database::addFilter(
return $value;
}
);
Database::addFilter(
'subQueryOrganizationKeys',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
->find('keys', [
Query::equal('resourceType', ['teams']),
Query::equal('resourceInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
);
Database::addFilter(
'subQueryAccountKeys',
function (mixed $value) {
return;
},
function (mixed $value, Document $document, Database $database) {
return $database->getAuthorization()->skip(fn () => $database
->find('keys', [
Query::equal('resourceType', ['users']),
Query::equal('resourceInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]));
}
);
+23 -17
View File
@@ -20,15 +20,11 @@ use Appwrite\Utopia\Response\Model\AttributeInteger;
use Appwrite\Utopia\Response\Model\AttributeIP;
use Appwrite\Utopia\Response\Model\AttributeLine;
use Appwrite\Utopia\Response\Model\AttributeList;
use Appwrite\Utopia\Response\Model\AttributeLongtext;
use Appwrite\Utopia\Response\Model\AttributeMediumtext;
use Appwrite\Utopia\Response\Model\AttributePoint;
use Appwrite\Utopia\Response\Model\AttributePolygon;
use Appwrite\Utopia\Response\Model\AttributeRelationship;
use Appwrite\Utopia\Response\Model\AttributeString;
use Appwrite\Utopia\Response\Model\AttributeText;
use Appwrite\Utopia\Response\Model\AttributeURL;
use Appwrite\Utopia\Response\Model\AttributeVarchar;
use Appwrite\Utopia\Response\Model\AuthProvider;
use Appwrite\Utopia\Response\Model\BaseList;
use Appwrite\Utopia\Response\Model\Branch;
@@ -45,15 +41,11 @@ use Appwrite\Utopia\Response\Model\ColumnInteger;
use Appwrite\Utopia\Response\Model\ColumnIP;
use Appwrite\Utopia\Response\Model\ColumnLine;
use Appwrite\Utopia\Response\Model\ColumnList;
use Appwrite\Utopia\Response\Model\ColumnLongtext;
use Appwrite\Utopia\Response\Model\ColumnMediumtext;
use Appwrite\Utopia\Response\Model\ColumnPoint;
use Appwrite\Utopia\Response\Model\ColumnPolygon;
use Appwrite\Utopia\Response\Model\ColumnRelationship;
use Appwrite\Utopia\Response\Model\ColumnString;
use Appwrite\Utopia\Response\Model\ColumnText;
use Appwrite\Utopia\Response\Model\ColumnURL;
use Appwrite\Utopia\Response\Model\ColumnVarchar;
use Appwrite\Utopia\Response\Model\ConsoleVariables;
use Appwrite\Utopia\Response\Model\Continent;
use Appwrite\Utopia\Response\Model\Country;
@@ -102,6 +94,13 @@ use Appwrite\Utopia\Response\Model\MigrationReport;
use Appwrite\Utopia\Response\Model\Mock;
use Appwrite\Utopia\Response\Model\MockNumber;
use Appwrite\Utopia\Response\Model\None;
use Appwrite\Utopia\Response\Model\PaymentFeature;
use Appwrite\Utopia\Response\Model\PaymentInvoice;
use Appwrite\Utopia\Response\Model\PaymentPlan;
use Appwrite\Utopia\Response\Model\PaymentPlanFeature;
use Appwrite\Utopia\Response\Model\PaymentProviderConfig;
use Appwrite\Utopia\Response\Model\PaymentSubscription;
use Appwrite\Utopia\Response\Model\PaymentUsageEvent;
use Appwrite\Utopia\Response\Model\Phone;
use Appwrite\Utopia\Response\Model\Platform;
use Appwrite\Utopia\Response\Model\Preferences;
@@ -185,7 +184,7 @@ Response::setModel(new BaseList('Deployments List', Response::MODEL_DEPLOYMENT_L
Response::setModel(new BaseList('Executions List', Response::MODEL_EXECUTION_LIST, 'executions', Response::MODEL_EXECUTION));
Response::setModel(new BaseList('Projects List', Response::MODEL_PROJECT_LIST, 'projects', Response::MODEL_PROJECT, true, false));
Response::setModel(new BaseList('Webhooks List', Response::MODEL_WEBHOOK_LIST, 'webhooks', Response::MODEL_WEBHOOK, true, false));
Response::setModel(new BaseList('API Keys List', Response::MODEL_KEY_LIST, 'keys', Response::MODEL_KEY, true, true));
Response::setModel(new BaseList('API Keys List', Response::MODEL_KEY_LIST, 'keys', Response::MODEL_KEY, true, false));
Response::setModel(new BaseList('Dev Keys List', Response::MODEL_DEV_KEY_LIST, 'devKeys', Response::MODEL_DEV_KEY, true, false));
Response::setModel(new BaseList('Auth Providers List', Response::MODEL_AUTH_PROVIDER_LIST, 'platforms', Response::MODEL_AUTH_PROVIDER, true, false));
Response::setModel(new BaseList('Platforms List', Response::MODEL_PLATFORM_LIST, 'platforms', Response::MODEL_PLATFORM, true, false));
@@ -230,10 +229,6 @@ Response::setModel(new AttributeRelationship());
Response::setModel(new AttributePoint());
Response::setModel(new AttributeLine());
Response::setModel(new AttributePolygon());
Response::setModel(new AttributeVarchar());
Response::setModel(new AttributeText());
Response::setModel(new AttributeMediumtext());
Response::setModel(new AttributeLongtext());
// Table API Models
Response::setModel(new Table());
@@ -252,10 +247,6 @@ Response::setModel(new ColumnRelationship());
Response::setModel(new ColumnPoint());
Response::setModel(new ColumnLine());
Response::setModel(new ColumnPolygon());
Response::setModel(new ColumnVarchar());
Response::setModel(new ColumnText());
Response::setModel(new ColumnMediumtext());
Response::setModel(new ColumnLongtext());
Response::setModel(new Index());
Response::setModel(new ColumnIndex());
Response::setModel(new Row());
@@ -356,5 +347,20 @@ Response::setModel(new Migration());
Response::setModel(new MigrationReport());
Response::setModel(new MigrationFirebaseProject());
// Payments
Response::setModel(new PaymentPlan());
Response::setModel(new PaymentFeature());
Response::setModel(new PaymentSubscription());
Response::setModel(new PaymentProviderConfig());
Response::setModel(new PaymentPlanFeature());
Response::setModel(new PaymentInvoice());
Response::setModel(new PaymentUsageEvent());
Response::setModel(new BaseList('Payment Subscription List', Response::MODEL_PAYMENT_SUBSCRIPTION_LIST, 'subscriptions', Response::MODEL_PAYMENT_SUBSCRIPTION, true, true));
Response::setModel(new BaseList('Payment Plan List', Response::MODEL_PAYMENT_PLAN_LIST, 'plans', Response::MODEL_PAYMENT_PLAN, true, true));
Response::setModel(new BaseList('Payment Feature List', Response::MODEL_PAYMENT_FEATURE_LIST, 'features', Response::MODEL_PAYMENT_FEATURE, true, true));
Response::setModel(new BaseList('Payment Plan Feature List', Response::MODEL_PAYMENT_PLAN_FEATURE_LIST, 'features', Response::MODEL_PAYMENT_PLAN_FEATURE, true, true));
Response::setModel(new BaseList('Payment Invoice List', Response::MODEL_PAYMENT_INVOICE_LIST, 'invoices', Response::MODEL_PAYMENT_INVOICE, true, true));
Response::setModel(new BaseList('Payment Usage Event List', Response::MODEL_PAYMENT_USAGE_EVENT_LIST, 'events', Response::MODEL_PAYMENT_USAGE_EVENT, true, true));
// Tests (keep last)
Response::setModel(new Mock());
+6 -11
View File
@@ -23,8 +23,6 @@ use Utopia\Logger\Adapter\LogOwl;
use Utopia\Logger\Adapter\Raygun;
use Utopia\Logger\Adapter\Sentry;
use Utopia\Logger\Logger;
use Utopia\Pools\Adapter\Stack as StackPool;
use Utopia\Pools\Adapter\Swoole as SwoolePool;
use Utopia\Pools\Group;
use Utopia\Pools\Pool;
use Utopia\Queue;
@@ -287,9 +285,7 @@ $register->set('pools', function () {
default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Invalid scheme'),
};
$poolAdapter = System::getEnv('_APP_POOL_ADAPTER', default: 'stack') === 'swoole' ? new SwoolePool() : new StackPool();
$pool = new Pool($poolAdapter, $name, $poolSize, function () use ($type, $resource, $dsn) {
$pool = new Pool($name, $poolSize, function () use ($type, $resource, $dsn) {
// Get Adapter
switch ($type) {
case 'database':
@@ -328,12 +324,6 @@ $register->set('pools', function () {
Config::setParam('pools-' . $key, $config);
}
$reconnectAttempts = (int) System::getEnv('_APP_CONNECTIONS_RECONNECT_ATTEMPTS', 5);
$reconnectSleep = (int) System::getEnv('_APP_CONNECTIONS_RECONNECT_SLEEP', 2);
$group->setReconnectAttempts($reconnectAttempts);
$group->setReconnectSleep($reconnectSleep);
return $group;
});
@@ -398,3 +388,8 @@ $register->set('promiseAdapter', function () {
$register->set('hooks', function () {
return new Hooks();
});
$register->set('registryPayments', function () {
$registry = new \Appwrite\Payments\Provider\Registry();
$registry->register('stripe', \Appwrite\Payments\Provider\StripeAdapter::class);
return $registry;
});
+55 -341
View File
@@ -15,12 +15,10 @@ use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\Migration;
use Appwrite\Event\Realtime;
use Appwrite\Event\Screenshot;
use Appwrite\Event\StatsResources;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception;
use Appwrite\Functions\EventProcessor;
use Appwrite\GraphQL\Schema;
use Appwrite\Network\Cors;
use Appwrite\Network\Platform;
@@ -84,6 +82,9 @@ App::setResource('hooks', function ($register) {
}, ['register']);
App::setResource('register', fn () => $register);
App::setResource('registryPayments', function ($register) {
return $register->get('registryPayments');
}, ['register']);
App::setResource('locale', function () {
$locale = new Locale(System::getEnv('_APP_LOCALE', 'en'));
$locale->setFallback(System::getEnv('_APP_LOCALE', 'en'));
@@ -131,9 +132,6 @@ App::setResource('queueForMails', function (Publisher $publisher) {
App::setResource('queueForBuilds', function (Publisher $publisher) {
return new Build($publisher);
}, ['publisher']);
App::setResource('queueForScreenshots', function (Publisher $publisher) {
return new Screenshot($publisher);
}, ['publisher']);
App::setResource('queueForDatabase', function (Publisher $publisher) {
return new EventDatabase($publisher);
}, ['publisher']);
@@ -158,9 +156,6 @@ App::setResource('queueForAudits', function (Publisher $publisher) {
App::setResource('queueForFunctions', function (Publisher $publisher) {
return new Func($publisher);
}, ['publisher']);
App::setResource('eventProcessor', function () {
return new EventProcessor();
}, []);
App::setResource('queueForCertificates', function (Publisher $publisher) {
return new Certificate($publisher);
}, ['publisher']);
@@ -234,7 +229,7 @@ App::setResource('allowedSchemes', function (Document $project) {
/**
* Rule associated with a request origin.
*/
App::setResource('rule', function (Request $request, Database $dbForPlatform, Document $project, Authorization $authorization) {
App::setResource('rule', function (Request $request, Database $dbForPlatform, Document $project) {
$domain = \parse_url($request->getOrigin(), PHP_URL_HOST);
if (empty($domain)) {
return new Document();
@@ -242,7 +237,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));
}
@@ -257,7 +252,7 @@ App::setResource('rule', function (Request $request, Database $dbForPlatform, Do
}
return $rule;
}, ['request', 'dbForPlatform', 'project', 'authorization']);
}, ['request', 'dbForPlatform', 'project']);
/**
* CORS service
@@ -325,7 +320,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, $authorization) {
App::setResource('user', function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken) {
/**
* Handles user authentication and session validation.
*
@@ -343,10 +338,9 @@ App::setResource('user', function (string $mode, Document $project, Document $co
* 5. Regardless of the results from steps 1-4, attempts to fetch the JWT token.
* 6. If the JWT user has a valid session ID, updates the user variable with the user from `projectDB`,
* overwriting the previous value.
* 7. If account API key is passed, use user of the account API key as long as user ID header matches too
*/
$authorization->setDefaultStatus(true);
Authorization::setDefaultStatus(true);
$store->setKey('a_session_' . $project->getId());
@@ -413,24 +407,19 @@ 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([]);
// }
// }
$authJWT = $request->getHeader('x-appwrite-jwt', '');
if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication
if (!$user->isEmpty()) {
throw new Exception(Exception::USER_JWT_AND_COOKIE_SET);
}
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
try {
$payload = $jwt->decode($authJWT);
} catch (JWTException $error) {
throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage());
}
$jwtUserId = $payload['userId'] ?? '';
if (!empty($jwtUserId)) {
if ($mode === APP_MODE_ADMIN) {
@@ -446,41 +435,13 @@ App::setResource('user', function (string $mode, Document $project, Document $co
}
}
}
// Account based on account API key
$accountKey = $request->getHeader('x-appwrite-key', '');
$accountKeyUserId = $request->getHeader('x-appwrite-user', '');
if (!empty($accountKeyUserId) && !empty($accountKey)) {
if (!$user->isEmpty()) {
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
}
$accountKeyUser = $dbForPlatform->getAuthorization()->skip(fn () => $dbForPlatform->getDocument('users', $accountKeyUserId));
if (!$accountKeyUser->isEmpty()) {
$key = $accountKeyUser->find(
key: 'secret',
find: $accountKey,
subject: 'keys'
);
if (!empty($key)) {
$expire = $key->getAttribute('expire');
if (!empty($expire) && $expire < DatabaseDateTime::formatTz(DatabaseDateTime::now())) {
throw new Exception(Exception::ACCOUNT_KEY_EXPIRED);
}
$user = $accountKeyUser;
}
}
}
$dbForProject->setMetadata('user', $user->getId());
$dbForPlatform->setMetadata('user', $user->getId());
return $user;
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken', 'authorization']);
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken']);
App::setResource('project', function ($dbForPlatform, $request, $console, $authorization) {
App::setResource('project', function ($dbForPlatform, $request, $console) {
/** @var Appwrite\Utopia\Request $request */
/** @var Utopia\Database\Database $dbForPlatform */
/** @var Utopia\Database\Document $console */
@@ -491,10 +452,10 @@ App::setResource('project', function ($dbForPlatform, $request, $console, $autho
return $console;
}
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
return $project;
}, ['dbForPlatform', 'request', 'console', 'authorization']);
}, ['dbForPlatform', 'request', 'console']);
App::setResource('session', function (User $user, Store $store, Token $proofForToken) {
if ($user->isEmpty()) {
@@ -517,6 +478,10 @@ 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();
});
@@ -547,15 +512,7 @@ App::setResource('proofForCode', function (): Code {
return $code;
});
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, Response $response, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime, StatsUsage $queueForStatsUsage, Authorization $authorization) {
App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
@@ -571,8 +528,6 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform
$database = new Database($adapter, $cache);
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId())
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
@@ -593,218 +548,14 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform
->setNamespace('_' . $project->getSequence());
}
/**
* This isolated event handling for `users.*.create` which is based on a `Database::EVENT_DOCUMENT_CREATE` listener may look odd, but it is **intentional**.
*
* Accounts can be created in many ways beyond `createAccount`
* (anonymous, OAuth, phone, etc.), and those flows are probably not covered in event tests; so we handle this here.
*/
$eventDatabaseListener = function (Document $project, Document $document, Response $response, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime) {
// Only trigger events for user creation with the database listener.
if ($document->getCollection() !== 'users') {
return;
}
$queueForEvents
->setEvent('users.[userId].create')
->setParam('userId', $document->getId())
->setPayload($response->output($document, Response::MODEL_USER));
// Trigger functions, webhooks, and realtime events
$queueForFunctions
->from($queueForEvents)
->trigger();
/** Trigger webhooks events only if a project has them enabled */
if (!empty($project->getAttribute('webhooks'))) {
$queueForWebhooks
->from($queueForEvents)
->trigger();
}
/** Trigger realtime events only for non console events */
if ($queueForEvents->getProject()->getId() !== 'console') {
$queueForRealtime
->from($queueForEvents)
->trigger();
}
};
/**
* Purge function events cache when functions are created, updated or deleted.
*/
$functionsEventsCacheListener = function (string $event, Document $document, Document $project, Database $dbForProject) {
if ($document->getCollection() !== 'functions') {
return;
}
if ($project->isEmpty() || $project->getId() === 'console') {
return;
}
$hostname = $dbForProject->getAdapter()->getHostname();
$cacheKey = \sprintf(
'%s-cache-%s:%s:%s:project:%s:functions:events',
$dbForProject->getCacheName(),
$hostname ?? '',
$dbForProject->getNamespace(),
$dbForProject->getTenant(),
$project->getId()
);
$dbForProject->getCache()->purge($cacheKey);
};
$usageDatabaseListener = function (string $event, Document $document, StatsUsage $queueForStatsUsage) {
$value = 1;
switch ($event) {
case Database::EVENT_DOCUMENT_DELETE:
$value = -1;
break;
case Database::EVENT_DOCUMENTS_DELETE:
$value = -1 * $document->getAttribute('modified', 0);
break;
case Database::EVENT_DOCUMENTS_CREATE:
$value = $document->getAttribute('modified', 0);
break;
case Database::EVENT_DOCUMENTS_UPSERT:
$value = $document->getAttribute('created', 0);
break;
}
switch (true) {
case $document->getCollection() === 'teams':
$queueForStatsUsage->addMetric(METRIC_TEAMS, $value); // per project
break;
case $document->getCollection() === 'users':
$queueForStatsUsage->addMetric(METRIC_USERS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage->addReduce($document);
}
break;
case $document->getCollection() === 'sessions': // sessions
$queueForStatsUsage->addMetric(METRIC_SESSIONS, $value); //per project
break;
case $document->getCollection() === 'databases': // databases
$queueForStatsUsage->addMetric(METRIC_DATABASES, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'database_') && !str_contains($document->getCollection(), 'collection'): //collections
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$queueForStatsUsage
->addMetric(METRIC_COLLECTIONS, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_COLLECTIONS), $value);
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'database_') && str_contains($document->getCollection(), '_collection_'): //documents
$parts = explode('_', $document->getCollection());
$databaseInternalId = $parts[1] ?? 0;
$collectionInternalId = $parts[3] ?? 0;
$queueForStatsUsage
->addMetric(METRIC_DOCUMENTS, $value) // per project
->addMetric(str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_DOCUMENTS), $value) // per database
->addMetric(str_replace(['{databaseInternalId}', '{collectionInternalId}'], [$databaseInternalId, $collectionInternalId], METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS), $value); // per collection
break;
case $document->getCollection() === 'buckets': //buckets
$queueForStatsUsage
->addMetric(METRIC_BUCKETS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage
->addReduce($document);
}
break;
case str_starts_with($document->getCollection(), 'bucket_'): // files
$parts = explode('_', $document->getCollection());
$bucketInternalId = $parts[1];
$queueForStatsUsage
->addMetric(METRIC_FILES, $value) // per project
->addMetric(METRIC_FILES_STORAGE, $document->getAttribute('sizeOriginal') * $value) // per project
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES), $value) // per bucket
->addMetric(str_replace('{bucketInternalId}', $bucketInternalId, METRIC_BUCKET_ID_FILES_STORAGE), $document->getAttribute('sizeOriginal') * $value); // per bucket
break;
case $document->getCollection() === 'functions':
$queueForStatsUsage
->addMetric(METRIC_FUNCTIONS, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage
->addReduce($document);
}
break;
case $document->getCollection() === 'sites':
$queueForStatsUsage
->addMetric(METRIC_SITES, $value); // per project
if ($event === Database::EVENT_DOCUMENT_DELETE) {
$queueForStatsUsage
->addReduce($document);
}
break;
case $document->getCollection() === 'deployments':
$queueForStatsUsage
->addMetric(METRIC_DEPLOYMENTS, $value) // per project
->addMetric(METRIC_DEPLOYMENTS_STORAGE, $document->getAttribute('size') * $value) // per project
->addMetric(str_replace(['{resourceType}'], [$document->getAttribute('resourceType')], METRIC_RESOURCE_TYPE_DEPLOYMENTS), $value) // per function
->addMetric(str_replace(['{resourceType}'], [$document->getAttribute('resourceType')], METRIC_RESOURCE_TYPE_DEPLOYMENTS_STORAGE), $document->getAttribute('size') * $value)
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS), $value) // per function
->addMetric(str_replace(['{resourceType}', '{resourceInternalId}'], [$document->getAttribute('resourceType'), $document->getAttribute('resourceInternalId')], METRIC_RESOURCE_TYPE_ID_DEPLOYMENTS_STORAGE), $document->getAttribute('size') * $value);
break;
default:
break;
}
};
// Clone the queues, to prevent events triggered by the database listener
// from overwriting the events that are supposed to be triggered in the shutdown hook.
$queueForEventsClone = new Event($publisher);
$queueForFunctions = new Func($publisherFunctions);
$queueForWebhooks = new Webhook($publisherWebhooks);
$queueForRealtime = new Realtime();
$database
->on(Database::EVENT_DOCUMENT_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENT_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENTS_CREATE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENTS_DELETE, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENTS_UPSERT, 'calculate-usage', fn ($event, $document) => $usageDatabaseListener($event, $document, $queueForStatsUsage))
->on(Database::EVENT_DOCUMENT_CREATE, 'create-trigger-events', fn ($event, $document) => $eventDatabaseListener(
$project,
$document,
$response,
$queueForEventsClone->from($queueForEvents),
$queueForFunctions->from($queueForEvents),
$queueForWebhooks->from($queueForEvents),
$queueForRealtime->from($queueForEvents)
))
->on(Database::EVENT_DOCUMENT_CREATE, 'purge-function-events-cache', fn ($event, $document) => $functionsEventsCacheListener($event, $document, $project, $database))
->on(Database::EVENT_DOCUMENT_UPDATE, 'purge-function-events-cache', fn ($event, $document) => $functionsEventsCacheListener($event, $document, $project, $database))
->on(Database::EVENT_DOCUMENT_DELETE, 'purge-function-events-cache', fn ($event, $document) => $functionsEventsCacheListener($event, $document, $project, $database))
;
return $database;
}, ['pools', 'dbForPlatform', 'cache', 'project', 'response', 'publisher', 'publisherFunctions', 'publisherWebhooks', 'queueForEvents', 'queueForFunctions', 'queueForWebhooks', 'queueForRealtime', 'queueForStatsUsage', 'authorization']);
App::setResource('dbForPlatform', function (Group $pools, Cache $cache, Authorization $authorization) {
}, ['pools', 'dbForPlatform', 'cache', 'project']);
App::setResource('dbForPlatform', function (Group $pools, Cache $cache) {
$adapter = new DatabasePool($pools->get('console'));
$database = new Database($adapter, $cache);
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setNamespace('_console')
->setMetadata('host', \gethostname())
->setMetadata('project', 'console')
@@ -814,12 +565,12 @@ App::setResource('dbForPlatform', function (Group $pools, Cache $cache, Authoriz
$database->setDocumentType('users', User::class);
return $database;
}, ['pools', 'cache', 'authorization']);
}, ['pools', 'cache']);
App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, Authorization $authorization) {
App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
$databases = [];
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases) {
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
@@ -831,16 +582,13 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
}
$configure = (function (Database $database) use ($project, $dsn, $authorization) {
$configure = (function (Database $database) use ($project, $dsn) {
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId())
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES)
->setDocumentType('users', User::class)
;
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
$database->setDocumentType('users', User::class);
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
@@ -870,12 +618,12 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
return $database;
};
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
}, ['pools', 'dbForPlatform', 'cache']);
App::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $authorization, &$database) {
return function (?Document $project = null) use ($pools, $cache, &$database) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int) $project->getSequence());
return $database;
@@ -885,8 +633,6 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorizatio
$database = new Database($adapter, $cache);
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setSharedTables(true)
->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
@@ -899,7 +645,7 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorizatio
return $database;
};
}, ['pools', 'cache', 'authorization']);
}, ['pools', 'cache']);
App::setResource('audit', function ($dbForProject) {
$adapter = new AdapterDatabase($dbForProject);
@@ -1098,7 +844,7 @@ App::setResource('promiseAdapter', function ($register) {
return $register->get('promiseAdapter');
}, ['register']);
App::setResource('schema', function ($utopia, $dbForProject, $authorization) {
App::setResource('schema', function ($utopia, $dbForProject) {
$complexity = function (int $complexity, array $args) {
$queries = Query::parseQueries($args['queries'] ?? []);
@@ -1108,8 +854,8 @@ App::setResource('schema', function ($utopia, $dbForProject, $authorization) {
return $complexity * $limit;
};
$attributes = function (int $limit, int $offset) use ($dbForProject, $authorization) {
$attrs = $authorization->skip(fn () => $dbForProject->find('attributes', [
$attributes = function (int $limit, int $offset) use ($dbForProject) {
$attrs = Authorization::skip(fn () => $dbForProject->find('attributes', [
Query::limit($limit),
Query::offset($offset),
]));
@@ -1183,7 +929,7 @@ App::setResource('schema', function ($utopia, $dbForProject, $authorization) {
$urls,
$params,
);
}, ['utopia', 'dbForProject', 'authorization']);
}, ['utopia', 'dbForProject']);
App::setResource('gitHub', function (Cache $cache) {
return new VcsGitHub($cache);
@@ -1211,7 +957,7 @@ App::setResource('smsRates', function () {
return [];
});
App::setResource('devKey', function (Request $request, Document $project, array $servers, Database $dbForPlatform, Authorization $authorization) {
App::setResource('devKey', function (Request $request, Document $project, array $servers, Database $dbForPlatform) {
$devKey = $request->getHeader('x-appwrite-dev-key', $request->getParam('devKey', ''));
// Check if given key match project's development keys
@@ -1230,7 +976,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());
}
@@ -1247,25 +993,25 @@ 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', 'authorization']);
}, ['request', 'project', 'servers', 'dbForPlatform']);
App::setResource('team', function (Document $project, Database $dbForPlatform, App $utopia, Request $request, Authorization $authorization) {
App::setResource('team', function (Document $project, Database $dbForPlatform, App $utopia, Request $request) {
$teamInternalId = '';
if ($project->getId() !== 'console') {
$teamInternalId = $project->getAttribute('teamInternalId', '');
} else {
$route = $utopia->match($request);
$path = !empty($route) ? $route->getPath() : $request->getURI();
$path = $route->getPath();
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', '');
@@ -1274,7 +1020,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;
}
}
@@ -1283,14 +1029,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', 'authorization']);
}, ['project', 'dbForPlatform', 'utopia', 'request']);
App::setResource(
'isResourceBlocked',
@@ -1316,43 +1062,19 @@ App::setResource('previewHostname', function (Request $request, ?Key $apiKey) {
return '';
}, ['request', 'apiKey']);
App::setResource('apiKey', function (Request $request, Document $project, Document $team, Document $user): ?Key {
App::setResource('apiKey', function (Request $request, Document $project): ?Key {
$key = $request->getHeader('x-appwrite-key');
if (empty($key)) {
return null;
}
$key = Key::decode($project, $team, $user, $key);
$userHeader = $request->getHeader('x-appwrite-user');
$organizationHeader = $request->getHeader('x-appwrite-organization');
$projectHeader = $request->getHeader('x-appwrite-project');
if (!empty($key->getProjectId())) {
if (empty($projectHeader) || $projectHeader !== $key->getProjectId()) {
throw new Exception(Exception::PROJECT_ID_MISSING);
}
}
if (!empty($key->getUserId())) {
if (empty($userHeader) || $userHeader !== $key->getUserId()) {
throw new Exception(Exception::USER_ID_MISSING);
}
}
if (!empty($key->getTeamId())) {
if (empty($organizationHeader) || $organizationHeader !== $key->getTeamId()) {
throw new Exception(Exception::ORGANIZATION_ID_MISSING);
}
}
return $key;
}, ['request', 'project', 'team', 'user']);
return Key::decode($project, $key);
}, ['request', 'project']);
App::setResource('executor', fn () => new Executor());
App::setResource('resourceToken', function ($project, $dbForProject, $request, Authorization $authorization) {
App::setResource('resourceToken', function ($project, $dbForProject, $request) {
$tokenJWT = $request->getParam('token');
if (!empty($tokenJWT) && !$project->isEmpty()) { // JWT authentication
@@ -1370,7 +1092,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request, A
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([]);
@@ -1388,7 +1110,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request, A
}
return match ($token->getAttribute('resourceType')) {
TOKENS_RESOURCE_TYPE_FILES => (function () use ($token, $dbForProject, $authorization) {
TOKENS_RESOURCE_TYPE_FILES => (function () use ($token, $dbForProject) {
$sequences = explode(':', $token->getAttribute('resourceInternalId'));
$ids = explode(':', $token->getAttribute('resourceId'));
@@ -1399,7 +1121,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request, A
$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([
@@ -1414,16 +1136,8 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request, A
};
}
return new Document([]);
}, ['project', 'dbForProject', 'request', 'authorization']);
}, ['project', 'dbForProject', 'request']);
App::setResource('transactionState', function (Database $dbForProject, Authorization $authorization) {
return new TransactionState($dbForProject, $authorization);
}, ['dbForProject', 'authorization']);
App::setResource('executionsRetentionCount', function (Document $project, array $plan) {
if ($project->getId() === 'console' || empty($plan)) {
return 0;
}
return (int) ($plan['executionsRetentionCount'] ?? 100);
}, ['project', 'plan']);
App::setResource('transactionState', function (Database $dbForProject) {
return new TransactionState($dbForProject);
}, ['dbForProject']);
+14 -44
View File
@@ -29,10 +29,10 @@ use Utopia\Database\Adapter\Pool as DatabasePool;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Query as QueryException;
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;
@@ -67,7 +67,6 @@ if (!function_exists('getConsoleDB')) {
$adapter = new DatabasePool($pools->get('console'));
$database = new Database($adapter, getCache());
$database
->setDatabase(APP_DATABASE)
->setNamespace('_console')
->setMetadata('host', \gethostname())
->setMetadata('project', '_console');
@@ -124,7 +123,6 @@ if (!function_exists('getProjectDB')) {
}
$database
->setDatabase(APP_DATABASE)
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId());
@@ -311,7 +309,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
'value' => '{}'
]);
$statsDocument = $database->getAuthorization()->skip(fn () => $database->createDocument('realtime', $document));
$statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document));
break;
} catch (Throwable) {
Console::warning("Collection not ready. Retrying connection ({$attempts})...");
@@ -341,7 +339,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
->setAttribute('timestamp', DateTime::now())
->setAttribute('value', json_encode($payload));
$database->getAuthorization()->skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
} catch (Throwable $th) {
$logError($th, "updateWorkerDocument");
}
@@ -372,7 +370,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
$payload = [];
$list = $database->getAuthorization()->skip(fn () => $database->find('realtime', [
$list = Authorization::skip(fn () => $database->find('realtime', [
Query::greaterThan('timestamp', DateTime::addSeconds(new \DateTime(), -15)),
]));
@@ -466,18 +464,17 @@ $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 = $consoleDatabase->getAuthorization()->skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
$project = Authorization::skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
$database = getProjectDB($project);
/** @var Appwrite\Utopia\Database\Documents\User $user */
$user = $database->getDocument('users', $userId);
$roles = $user->getRoles($database->getAuthorization());
$roles = $user->getRoles();
$channels = $realtime->connections[$connection]['channels'];
$queries = $realtime->connections[$connection]['queries'] ?? [];
$realtime->unsubscribe($connection);
$realtime->subscribe($projectId, $connection, $roles, $channels, $queries);
$realtime->subscribe($projectId, $connection, $roles, $channels);
}
}
@@ -529,7 +526,6 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
try {
/** @var Document $project */
$project = $app->getResource('project');
$authorization = $app->getResource('authorization');
/*
* Project Check
@@ -541,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);
}
@@ -577,14 +573,9 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
}
$roles = $user->getRoles($authorization);
$roles = $user->getRoles();
$channels = Realtime::convertChannels($request->getQuery('channels', []), $user->getId());
try {
$queries = Realtime::convertQueries($request->getQuery('queries', []));
} catch (QueryException $e) {
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $e->getMessage());
}
/**
* Channels Check
@@ -593,9 +584,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, 'Missing channels');
}
$realtime->subscribe($project->getId(), $connection, $roles, $channels, $queries);
$realtime->connections[$connection]['authorization'] = $authorization;
$realtime->subscribe($project->getId(), $connection, $roles, $channels);
$user = empty($user->getId()) ? null : $response->output($user, Response::MODEL_ACCOUNT);
@@ -603,7 +592,6 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
'type' => 'connected',
'data' => [
'channels' => array_keys($channels),
'queries' => $queries,
'user' => $user
]
]));
@@ -626,7 +614,6 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$code = 500;
}
$message = $th->getMessage();
// sanitize 0 && 5xx errors
@@ -656,19 +643,12 @@ $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'] ?? null;
// Get authorization from connection (stored during onOpen)
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
$projectId = $realtime->connections[$connection]['projectId'];
$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;
}
@@ -732,19 +712,9 @@ $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($database->getAuthorization());
$roles = $user->getRoles();
$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;
$queries = $realtime->connections[$connection]['queries'];
$realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels, $queries);
// Restore authorization after subscribe
if ($authorization !== null) {
$realtime->connections[$connection]['authorization'] = $authorization;
}
$realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels);
$user = $response->output($user, Response::MODEL_ACCOUNT);
$server->send([$connection], json_encode([
-1
View File
@@ -165,7 +165,6 @@ $enableAssistant = $this->getParam('enableAssistant', false);
- _APP_MAINTENANCE_RETENTION_SCHEDULES
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
- _APP_GRAPHQL_INTROSPECTION
- _APP_GRAPHQL_MAX_BATCH_SIZE
- _APP_GRAPHQL_MAX_COMPLEXITY
- _APP_GRAPHQL_MAX_DEPTH
+27 -52
View File
@@ -14,7 +14,6 @@ use Appwrite\Event\Mail;
use Appwrite\Event\Messaging;
use Appwrite\Event\Migration;
use Appwrite\Event\Realtime;
use Appwrite\Event\Screenshot;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\Webhook;
use Appwrite\Platform\Appwrite;
@@ -49,31 +48,19 @@ 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('authorization', function () {
$authorization = new Authorization();
$authorization->disable();
return $authorization;
}, []);
Server::setResource('dbForPlatform', function (Cache $cache, Registry $register, Authorization $authorization) {
Server::setResource('dbForPlatform', function (Cache $cache, Registry $register) {
$pools = $register->get('pools');
$adapter = new DatabasePool($pools->get('console'));
$dbForPlatform = new Database($adapter, $cache);
$dbForPlatform
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setNamespace('_console')
->setDocumentType('users', User::class)
;
$dbForPlatform->setNamespace('_console');
$dbForPlatform->setDocumentType('users', User::class);
return $dbForPlatform;
}, ['cache', 'register', 'authorization']);
}, ['cache', 'register']);
Server::setResource('project', function (Message $message, Database $dbForPlatform) {
$payload = $message->getPayload() ?? [];
@@ -86,7 +73,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, Authorization $authorization) {
Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForPlatform) {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
@@ -118,18 +105,15 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
->setNamespace('_' . $project->getSequence());
}
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database;
}, ['cache', 'register', 'message', 'project', 'dbForPlatform', 'authorization']);
}, ['cache', 'register', 'message', 'project', 'dbForPlatform']);
Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, Authorization $authorization) {
Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases): Database {
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases): Database {
if ($project->isEmpty() || $project->getId() === 'console') {
return $dbForPlatform;
}
@@ -143,7 +127,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)) {
@@ -180,18 +164,15 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
->setNamespace('_' . $project->getSequence());
}
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
return $database;
};
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
}, ['pools', 'dbForPlatform', 'cache']);
Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
$database = null;
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
return function (?Document $project = null) use ($pools, $cache, $database) {
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
$database->setTenant((int)$project->getSequence());
return $database;
@@ -201,8 +182,6 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authoriza
$database = new Database($adapter, $cache);
$database
->setDatabase(APP_DATABASE)
->setAuthorization($authorization)
->setSharedTables(true)
->setNamespace('logsV1')
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER)
@@ -215,7 +194,7 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authoriza
return $database;
};
}, ['pools', 'cache', 'authorization']);
}, ['pools', 'cache']);
Server::setResource('abuseRetention', function () {
return time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400); // 1 day
@@ -328,10 +307,6 @@ Server::setResource('queueForBuilds', function (Publisher $publisher) {
return new Build($publisher);
}, ['publisher']);
Server::setResource('queueForScreenshots', function (Publisher $publisher) {
return new Screenshot($publisher);
}, ['publisher']);
Server::setResource('queueForDeletes', function (Publisher $publisher) {
return new Delete($publisher);
}, ['publisher']);
@@ -407,6 +382,10 @@ Server::setResource('plan', function (array $plan = []) {
return [];
});
Server::setResource('registryPayments', function (Registry $register) {
return $register->get('registryPayments');
}, ['register']);
Server::setResource('certificates', function () {
$email = System::getEnv('_APP_EMAIL_CERTIFICATES', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'));
if (empty($email)) {
@@ -490,14 +469,6 @@ Server::setResource('getAudit', function (Database $dbForPlatform, callable $get
};
}, ['dbForPlatform', 'getProjectDB']);
Server::setResource('executionsRetentionCount', function (Document $project, array $plan) {
if ($project->getId() === 'console' || empty($plan)) {
return 0;
}
return (int) ($plan['executionsRetentionCount'] ?? 100);
}, ['project', 'plan']);
$pools = $register->get('pools');
$platform = new Appwrite();
$args = $platform->getEnv('argv');
@@ -542,8 +513,7 @@ $worker
->inject('log')
->inject('pools')
->inject('project')
->inject('authorization')
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project, Authorization $authorization) use ($worker, $queueName) {
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project) use ($worker, $queueName) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
if ($logger) {
@@ -559,7 +529,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);
@@ -578,4 +548,9 @@ $worker
Console::error('[Error] Line: ' . $error->getLine());
});
$worker->workerStart()
->action(function () use ($workerName) {
Console::info("Worker $workerName started");
});
$worker->start();
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php schedule-payments-usage $@
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/worker.php payments-usage-sync $@
-3
View File
@@ -1,3 +0,0 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php screenshots "$@"
+13 -13
View File
@@ -38,25 +38,25 @@
"ext-yaml": "*",
"ext-dom": "*",
"ext-redis": "*",
"ext-swoole": "6.*",
"ext-swoole": "*",
"ext-pdo": "*",
"ext-openssl": "*",
"ext-zlib": "*",
"ext-sockets": "*",
"appwrite/php-runtimes": "0.19.*",
"appwrite/php-clamav": "2.0.*",
"utopia-php/abuse": "1.*",
"utopia-php/abuse": "1.*.*",
"utopia-php/analytics": "0.10.*",
"utopia-php/audit": "2.*",
"utopia-php/audit": "2.0.2-rc3",
"utopia-php/auth": "0.5.*",
"utopia-php/cache": "0.13.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "1.*",
"utopia-php/database": "4.*",
"utopia-php/config": "1.*.*",
"utopia-php/database": "3.*.*",
"utopia-php/detector": "0.2.*",
"utopia-php/domains": "0.11.*",
"utopia-php/domains": "0.9.*",
"utopia-php/emails": "0.6.*",
"utopia-php/dns": "1.5.*",
"utopia-php/dns": "1.4.*",
"utopia-php/dsn": "0.2.1",
"utopia-php/framework": "0.33.*",
"utopia-php/fetch": "0.5.*",
@@ -64,15 +64,15 @@
"utopia-php/locale": "0.8.*",
"utopia-php/logger": "0.6.*",
"utopia-php/messaging": "0.20.*",
"utopia-php/migration": "1.*",
"utopia-php/migration": "1.*.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.7.*",
"utopia-php/pools": "1.*",
"utopia-php/pools": "0.8.*",
"utopia-php/preloader": "0.2.*",
"utopia-php/queue": "0.15.*",
"utopia-php/queue": "0.11.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.18.*",
"utopia-php/swoole": "1.*",
"utopia-php/swoole": "0.8.*",
"utopia-php/system": "0.9.*",
"utopia-php/telemetry": "0.1.*",
"utopia-php/vcs": "0.13.*",
@@ -91,7 +91,7 @@
"ext-fileinfo": "*",
"appwrite/sdk-generator": "*",
"phpunit/phpunit": "9.*",
"swoole/ide-helper": "6.*",
"swoole/ide-helper": "5.1.2",
"phpstan/phpstan": "1.8.*",
"textalk/websocket": "1.5.*",
"laravel/pint": "1.*",
@@ -109,4 +109,4 @@
"tbachert/spi": true
}
}
}
}
Generated
+8149 -9066
View File
File diff suppressed because it is too large Load Diff
+64 -96
View File
@@ -98,7 +98,6 @@ services:
- ./public:/usr/src/code/public
- ./src:/usr/src/code/src
- ./dev:/usr/src/code/dev
# - ./vendor/utopia-php/framework:/usr/src/code/vendor/utopia-php/framework
depends_on:
- mariadb
- redis
@@ -112,9 +111,7 @@ services:
- _APP_ENV
- _APP_EDITION
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_LOCALE
- _APP_COMPRESSION_ENABLED
- _APP_COMPRESSION_MIN_SIZE_BYTES
- _APP_CONSOLE_WHITELIST_ROOT
- _APP_CONSOLE_WHITELIST_EMAILS
@@ -203,7 +200,6 @@ services:
- _APP_MAINTENANCE_RETENTION_SCHEDULES
- _APP_SMS_PROVIDER
- _APP_SMS_FROM
- _APP_GRAPHQL_INTROSPECTION
- _APP_GRAPHQL_MAX_BATCH_SIZE
- _APP_GRAPHQL_MAX_COMPLEXITY
- _APP_GRAPHQL_MAX_DEPTH
@@ -302,7 +298,6 @@ services:
- _APP_LOGGING_CONFIG
- _APP_LOGGING_CONFIG_REALTIME
- _APP_DATABASE_SHARED_TABLES
- _APP_POOL_ADAPTER=swoole
appwrite-worker-audits:
entrypoint: worker-audits
@@ -320,7 +315,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -352,7 +346,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_EMAIL_SECURITY
- _APP_DB_HOST
@@ -390,7 +383,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -448,7 +440,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -475,15 +466,16 @@ services:
- appwrite-functions:/storage/functions:rw
- appwrite-sites:/storage/sites:rw
- appwrite-builds:/storage/builds:rw
- appwrite-uploads:/storage/uploads:rw
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
environment:
- _APP_BROWSER_HOST
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_EXECUTOR_SECRET
- _APP_EXECUTOR_HOST
@@ -537,66 +529,6 @@ services:
extra_hosts:
- "host.docker.internal:host-gateway"
appwrite-worker-screenshots:
entrypoint: worker-screenshots
<<: *x-logging
container_name: appwrite-worker-screenshots
image: appwrite-dev
networks:
- appwrite
volumes:
- appwrite-uploads:/storage/uploads:rw
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
environment:
# Specific
- _APP_BROWSER_HOST
# Basic
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_LOGGING_CONFIG
# Database
- _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
# Storage
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
- _APP_STORAGE_S3_REGION
- _APP_STORAGE_S3_BUCKET
- _APP_STORAGE_S3_ENDPOINT
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
- _APP_STORAGE_DO_SPACES_SECRET
- _APP_STORAGE_DO_SPACES_REGION
- _APP_STORAGE_DO_SPACES_BUCKET
- _APP_STORAGE_BACKBLAZE_ACCESS_KEY
- _APP_STORAGE_BACKBLAZE_SECRET
- _APP_STORAGE_BACKBLAZE_REGION
- _APP_STORAGE_BACKBLAZE_BUCKET
- _APP_STORAGE_LINODE_ACCESS_KEY
- _APP_STORAGE_LINODE_SECRET
- _APP_STORAGE_LINODE_REGION
- _APP_STORAGE_LINODE_BUCKET
- _APP_STORAGE_WASABI_ACCESS_KEY
- _APP_STORAGE_WASABI_SECRET
- _APP_STORAGE_WASABI_REGION
- _APP_STORAGE_WASABI_BUCKET
extra_hosts:
- "host.docker.internal:host-gateway"
appwrite-worker-certificates:
entrypoint: worker-certificates
<<: *x-logging
@@ -615,7 +547,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
@@ -649,16 +580,12 @@ services:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
redis:
condition: service_started
mariadb:
condition: service_started
openruntimes-executor:
condition: service_healthy
- redis
- mariadb
- openruntimes-executor
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_OPTIONS_FORCE_HTTPS
@@ -702,7 +629,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
@@ -737,7 +663,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -795,7 +720,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
@@ -835,7 +759,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
@@ -880,7 +803,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
@@ -901,7 +823,6 @@ services:
- _APP_DB_PASS
- _APP_DATABASE_SHARED_TABLES
- _APP_INTERVAL_DOMAIN_VERIFICATION
- _APP_INTERVAL_CLEANUP_STALE_EXECUTIONS
appwrite-task-stats-resources:
container_name: appwrite-task-stats-resources
@@ -919,7 +840,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
@@ -951,7 +871,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
@@ -983,7 +902,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
@@ -999,6 +917,65 @@ services:
- _APP_USAGE_AGGREGATION_INTERVAL
- _APP_DATABASE_SHARED_TABLES
appwrite-worker-payments-usage:
entrypoint: worker-payments-usage
<<: *x-logging
container_name: appwrite-worker-payments-usage
image: appwrite-dev
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_LOGGING_CONFIG
- _APP_DATABASE_SHARED_TABLES
appwrite-task-scheduler-payments-usage:
entrypoint: schedule-payments-usage
<<: *x-logging
container_name: appwrite-task-scheduler-payments-usage
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_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_LOGGING_CONFIG
- _APP_DATABASE_SHARED_TABLES
- _APP_PAYMENTS_USAGE_SYNC_INTERVAL
appwrite-task-scheduler-functions:
entrypoint: schedule-functions
<<: *x-logging
@@ -1015,7 +992,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -1044,7 +1020,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -1072,7 +1047,6 @@ services:
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_REDIS_HOST
- _APP_REDIS_PORT
@@ -1151,12 +1125,6 @@ services:
- OPR_EXECUTOR_STORAGE_WASABI_SECRET=$_APP_STORAGE_WASABI_SECRET
- OPR_EXECUTOR_STORAGE_WASABI_REGION=$_APP_STORAGE_WASABI_REGION
- OPR_EXECUTOR_STORAGE_WASABI_BUCKET=$_APP_STORAGE_WASABI_BUCKET
healthcheck:
test: ["CMD-SHELL", "curl -fsS -H \"Authorization: Bearer $$OPR_EXECUTOR_SECRET\" http://localhost/v1/health >/dev/null"]
interval: 5s
timeout: 3s
retries: 20
start_period: 5s
mariadb:
image: mariadb:10.11 # fix issues when upgrading using: mysql_upgrade -u root -p
@@ -8,15 +8,11 @@ Client client = new Client(context)
Account account = new Account(client);
account.createJWT(
0, // duration (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
})
);
account.createJWT(new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
return;
}
Log.d("Appwrite", result.toString());
}));
@@ -3,8 +3,7 @@ import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Avatars;
import io.appwrite.enums.Theme;
import io.appwrite.enums.Timezone;
import io.appwrite.enums.BrowserPermission;
import io.appwrite.enums.ImageFormat;
import io.appwrite.enums.Output;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@@ -30,12 +29,12 @@ avatars.getScreenshot(
-122.4194, // longitude (optional)
100, // accuracy (optional)
true, // touch (optional)
BrowserPermission.GEOLOCATION, // permissions (optional)
List.of("geolocation", "notifications"), // permissions (optional)
3, // sleep (optional)
800, // width (optional)
600, // height (optional)
85, // quality (optional)
ImageFormat.JPG, // output (optional)
Output.JPG, // output (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -1,7 +1,6 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Teams;
import io.appwrite.enums.Roles;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@@ -11,7 +10,7 @@ Teams teams = new Teams(client);
teams.createMembership(
"<TEAM_ID>", // teamId
Roles.ADMIN, // roles
List.of(), // roles
"email@example.com", // email (optional)
"<USER_ID>", // userId (optional)
"+12065550100", // phone (optional)
@@ -1,7 +1,6 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Teams;
import io.appwrite.enums.Roles;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@@ -12,7 +11,7 @@ Teams teams = new Teams(client);
teams.updateMembership(
"<TEAM_ID>", // teamId
"<MEMBERSHIP_ID>", // membershipId
Roles.ADMIN, // roles
List.of(), // roles
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -8,6 +8,4 @@ val client = Client(context)
val account = Account(client)
val result = account.createJWT(
duration = 0, // (optional)
)
val result = account.createJWT()
@@ -3,8 +3,7 @@ import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Avatars
import io.appwrite.enums.Theme
import io.appwrite.enums.Timezone
import io.appwrite.enums.BrowserPermission
import io.appwrite.enums.ImageFormat
import io.appwrite.enums.Output
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@@ -30,10 +29,10 @@ val result = avatars.getScreenshot(
longitude = -122.4194, // (optional)
accuracy = 100, // (optional)
touch = true, // (optional)
permissions = BrowserPermission.GEOLOCATION, // (optional)
permissions = listOf("geolocation", "notifications"), // (optional)
sleep = 3, // (optional)
width = 800, // (optional)
height = 600, // (optional)
quality = 85, // (optional)
output = ImageFormat.JPG, // (optional)
output = output.JPG, // (optional)
)
@@ -1,7 +1,6 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Teams
import io.appwrite.enums.Roles
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@@ -11,7 +10,7 @@ val teams = Teams(client)
val result = teams.createMembership(
teamId = "<TEAM_ID>",
roles = roles.ADMIN,
roles = listOf(),
email = "email@example.com", // (optional)
userId = "<USER_ID>", // (optional)
phone = "+12065550100", // (optional)
@@ -1,7 +1,6 @@
import io.appwrite.Client
import io.appwrite.coroutines.CoroutineCallback
import io.appwrite.services.Teams
import io.appwrite.enums.Roles
val client = Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@@ -12,5 +11,5 @@ val teams = Teams(client)
val result = teams.updateMembership(
teamId = "<TEAM_ID>",
membershipId = "<MEMBERSHIP_ID>",
roles = roles.ADMIN,
roles = listOf(),
)
@@ -6,7 +6,5 @@ let client = Client()
let account = Account(client)
let jwt = try await account.createJWT(
duration: 0 // optional
)
let jwt = try await account.createJWT()
@@ -16,20 +16,20 @@ let bytes = try await avatars.getScreenshot(
viewportWidth: 1920, // optional
viewportHeight: 1080, // optional
scale: 2, // optional
theme: .dark, // optional
theme: .light, // optional
userAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15", // optional
fullpage: true, // optional
locale: "en-US", // optional
timezone: .americaNewYork, // optional
timezone: .africaAbidjan, // optional
latitude: 37.7749, // optional
longitude: -122.4194, // optional
accuracy: 100, // optional
touch: true, // optional
permissions: [.geolocation, .notifications], // optional
permissions: ["geolocation","notifications"], // optional
sleep: 3, // optional
width: 800, // optional
height: 600, // optional
quality: 85, // optional
output: .jpeg // optional
output: .jpg // optional
)
@@ -1,5 +1,4 @@
import Appwrite
import AppwriteEnums
let client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@@ -9,7 +8,7 @@ let teams = Teams(client)
let membership = try await teams.createMembership(
teamId: "<TEAM_ID>",
roles: [.admin],
roles: [],
email: "email@example.com", // optional
userId: "<USER_ID>", // optional
phone: "+12065550100", // optional
@@ -1,5 +1,4 @@
import Appwrite
import AppwriteEnums
let client = Client()
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@@ -10,6 +9,6 @@ let teams = Teams(client)
let membership = try await teams.updateMembership(
teamId: "<TEAM_ID>",
membershipId: "<MEMBERSHIP_ID>",
roles: [.admin]
roles: []
)
@@ -6,6 +6,4 @@ Client client = Client()
Account account = Account(client);
Jwt result = await account.createJWT(
duration: 0, // optional
);
Jwt result = await account.createJWT();
@@ -7,5 +7,5 @@ Client client = Client()
Account account = Account(client);
MfaType result = await account.createMFAAuthenticator(
type: enums.AuthenticatorType.totp,
type: AuthenticatorType.totp,
);
@@ -7,5 +7,5 @@ Client client = Client()
Account account = Account(client);
MfaChallenge result = await account.createMFAChallenge(
factor: enums.AuthenticationFactor.email,
factor: AuthenticationFactor.email,
);
@@ -7,7 +7,7 @@ Client client = Client()
Account account = Account(client);
await account.createOAuth2Session(
provider: enums.OAuthProvider.amazon,
provider: OAuthProvider.amazon,
success: 'https://example.com', // optional
failure: 'https://example.com', // optional
scopes: [], // optional
@@ -7,7 +7,7 @@ Client client = Client()
Account account = Account(client);
await account.createOAuth2Token(
provider: enums.OAuthProvider.amazon,
provider: OAuthProvider.amazon,
success: 'https://example.com', // optional
failure: 'https://example.com', // optional
scopes: [], // optional
@@ -7,5 +7,5 @@ Client client = Client()
Account account = Account(client);
await account.deleteMFAAuthenticator(
type: enums.AuthenticatorType.totp,
type: AuthenticatorType.totp,
);
@@ -7,6 +7,6 @@ Client client = Client()
Account account = Account(client);
User result = await account.updateMFAAuthenticator(
type: enums.AuthenticatorType.totp,
type: AuthenticatorType.totp,
otp: '<OTP>',
);
@@ -8,7 +8,7 @@ Avatars avatars = Avatars(client);
// Downloading file
Uint8List bytes = await avatars.getBrowser(
code: enums.Browser.avantBrowser,
code: Browser.avantBrowser,
width: 0, // optional
height: 0, // optional
quality: -1, // optional
@@ -20,7 +20,7 @@ file.writeAsBytesSync(bytes);
// Displaying image preview
FutureBuilder(
future: avatars.getBrowser(
code: enums.Browser.avantBrowser,
code: Browser.avantBrowser,
width:0 , // optional
height:0 , // optional
quality:-1 , // optional
@@ -8,7 +8,7 @@ Avatars avatars = Avatars(client);
// Downloading file
Uint8List bytes = await avatars.getCreditCard(
code: enums.CreditCard.americanExpress,
code: CreditCard.americanExpress,
width: 0, // optional
height: 0, // optional
quality: -1, // optional
@@ -20,7 +20,7 @@ file.writeAsBytesSync(bytes);
// Displaying image preview
FutureBuilder(
future: avatars.getCreditCard(
code: enums.CreditCard.americanExpress,
code: CreditCard.americanExpress,
width:0 , // optional
height:0 , // optional
quality:-1 , // optional
@@ -8,7 +8,7 @@ Avatars avatars = Avatars(client);
// Downloading file
Uint8List bytes = await avatars.getFlag(
code: enums.Flag.afghanistan,
code: Flag.afghanistan,
width: 0, // optional
height: 0, // optional
quality: -1, // optional
@@ -20,7 +20,7 @@ file.writeAsBytesSync(bytes);
// Displaying image preview
FutureBuilder(
future: avatars.getFlag(
code: enums.Flag.afghanistan,
code: Flag.afghanistan,
width:0 , // optional
height:0 , // optional
quality:-1 , // optional
@@ -16,21 +16,21 @@ Uint8List bytes = await avatars.getScreenshot(
viewportWidth: 1920, // optional
viewportHeight: 1080, // optional
scale: 2, // optional
theme: enums.Theme.dark, // optional
theme: Theme.light, // optional
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15', // optional
fullpage: true, // optional
locale: 'en-US', // optional
timezone: enums.Timezone.americaNewYork, // optional
timezone: Timezone.africaAbidjan, // optional
latitude: 37.7749, // optional
longitude: -122.4194, // optional
accuracy: 100, // optional
touch: true, // optional
permissions: [enums.BrowserPermission.geolocation, enums.BrowserPermission.notifications], // optional
permissions: ["geolocation","notifications"], // optional
sleep: 3, // optional
width: 800, // optional
height: 600, // optional
quality: 85, // optional
output: enums.ImageFormat.jpeg, // optional
output: Output.jpg, // optional
)
final file = File('path_to_file/filename.ext');
@@ -47,21 +47,21 @@ FutureBuilder(
viewportWidth:1920 , // optional
viewportHeight:1080 , // optional
scale:2 , // optional
theme: enums.Theme.dark, // optional
theme: Theme.light, // optional
userAgent:'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15' , // optional
fullpage:true , // optional
locale:'en-US' , // optional
timezone: enums.Timezone.americaNewYork, // optional
timezone: Timezone.africaAbidjan, // optional
latitude:37.7749 , // optional
longitude:-122.4194 , // optional
accuracy:100 , // optional
touch:true , // optional
permissions: [enums.BrowserPermission.geolocation, enums.BrowserPermission.notifications], // optional
permissions:["geolocation","notifications"] , // optional
sleep:3 , // optional
width:800 , // optional
height:600 , // optional
quality:85 , // optional
output: enums.ImageFormat.jpeg, // optional
output: Output.jpg, // optional
), // Works for both public file and private file, for private files you need to be logged in
builder: (context, snapshot) {
return snapshot.hasData && snapshot.data != null
@@ -11,7 +11,7 @@ Execution result = await functions.createExecution(
body: '<BODY>', // optional
xasync: false, // optional
path: '<PATH>', // optional
method: enums.ExecutionMethod.gET, // optional
method: ExecutionMethod.gET, // optional
headers: {}, // optional
scheduledAt: '<SCHEDULED_AT>', // optional
);
@@ -12,7 +12,7 @@ Uint8List bytes = await storage.getFilePreview(
fileId: '<FILE_ID>',
width: 0, // optional
height: 0, // optional
gravity: enums.ImageGravity.center, // optional
gravity: ImageGravity.center, // optional
quality: -1, // optional
borderWidth: 0, // optional
borderColor: '', // optional
@@ -20,7 +20,7 @@ Uint8List bytes = await storage.getFilePreview(
opacity: 0, // optional
rotation: -360, // optional
background: '', // optional
output: enums.ImageFormat.jpg, // optional
output: ImageFormat.jpg, // optional
token: '<TOKEN>', // optional
)
@@ -34,7 +34,7 @@ FutureBuilder(
fileId:'<FILE_ID>' ,
width:0 , // optional
height:0 , // optional
gravity: enums.ImageGravity.center, // optional
gravity: ImageGravity.center, // optional
quality:-1 , // optional
borderWidth:0 , // optional
borderColor:'' , // optional
@@ -42,7 +42,7 @@ FutureBuilder(
opacity:0 , // optional
rotation:-360 , // optional
background:'' , // optional
output: enums.ImageFormat.jpg, // optional
output: ImageFormat.jpg, // optional
token:'<TOKEN>' , // optional
), // Works for both public file and private file, for private files you need to be logged in
builder: (context, snapshot) {
@@ -8,7 +8,7 @@ Teams teams = Teams(client);
Membership result = await teams.createMembership(
teamId: '<TEAM_ID>',
roles: [enums.Roles.admin],
roles: [],
email: 'email@example.com', // optional
userId: '<USER_ID>', // optional
phone: '+12065550100', // optional
@@ -9,5 +9,5 @@ Teams teams = Teams(client);
Membership result = await teams.updateMembership(
teamId: '<TEAM_ID>',
membershipId: '<MEMBERSHIP_ID>',
roles: [enums.Roles.admin],
roles: [],
);
@@ -1,7 +1,5 @@
mutation {
accountCreateJWT(
duration: 0
) {
accountCreateJWT {
jwt
}
}
@@ -16,7 +16,5 @@ mutation {
sizeOriginal
chunksTotal
chunksUploaded
encryption
compression
}
}
@@ -6,8 +6,6 @@ const client = new Client()
const account = new Account(client);
const result = await account.createJWT({
duration: 0 // optional
});
const result = await account.createJWT();
console.log(result);
@@ -1,4 +1,4 @@
import { Client, Avatars, Theme, Timezone, BrowserPermission, ImageFormat } from "react-native-appwrite";
import { Client, Avatars, Theme, Timezone, Output } from "react-native-appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
@@ -15,21 +15,21 @@ const result = avatars.getScreenshot({
viewportWidth: 1920, // optional
viewportHeight: 1080, // optional
scale: 2, // optional
theme: Theme.Dark, // optional
theme: Theme.Light, // optional
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15', // optional
fullpage: true, // optional
locale: 'en-US', // optional
timezone: Timezone.AmericaNewYork, // optional
timezone: Timezone.AfricaAbidjan, // optional
latitude: 37.7749, // optional
longitude: -122.4194, // optional
accuracy: 100, // optional
touch: true, // optional
permissions: [BrowserPermission.Geolocation, BrowserPermission.Notifications], // optional
permissions: ["geolocation","notifications"], // optional
sleep: 3, // optional
width: 800, // optional
height: 600, // optional
quality: 85, // optional
output: ImageFormat.Jpeg // optional
output: Output.Jpg // optional
});
console.log(result);
@@ -1,4 +1,4 @@
import { Client, Teams, Roles } from "react-native-appwrite";
import { Client, Teams } from "react-native-appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
@@ -8,7 +8,7 @@ const teams = new Teams(client);
const result = await teams.createMembership({
teamId: '<TEAM_ID>',
roles: [Roles.Admin],
roles: [],
email: 'email@example.com', // optional
userId: '<USER_ID>', // optional
phone: '+12065550100', // optional
@@ -1,4 +1,4 @@
import { Client, Teams, Roles } from "react-native-appwrite";
import { Client, Teams } from "react-native-appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
@@ -9,7 +9,7 @@ const teams = new Teams(client);
const result = await teams.updateMembership({
teamId: '<TEAM_ID>',
membershipId: '<MEMBERSHIP_ID>',
roles: [Roles.Admin]
roles: []
});
console.log(result);
@@ -6,6 +6,3 @@ X-Appwrite-Project: <YOUR_PROJECT_ID>
X-Appwrite-Session:
X-Appwrite-JWT: <YOUR_JWT>
{
"duration": 0
}
@@ -6,8 +6,6 @@ const client = new Client()
const account = new Account(client);
const result = await account.createJWT({
duration: 0 // optional
});
const result = await account.createJWT();
console.log(result);
@@ -1,4 +1,4 @@
import { Client, Avatars, Theme, Timezone, BrowserPermission, ImageFormat } from "appwrite";
import { Client, Avatars, Theme, Timezone, Output } from "appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
@@ -15,21 +15,21 @@ const result = avatars.getScreenshot({
viewportWidth: 1920, // optional
viewportHeight: 1080, // optional
scale: 2, // optional
theme: Theme.Dark, // optional
theme: Theme.Light, // optional
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15', // optional
fullpage: true, // optional
locale: 'en-US', // optional
timezone: Timezone.AmericaNewYork, // optional
timezone: Timezone.AfricaAbidjan, // optional
latitude: 37.7749, // optional
longitude: -122.4194, // optional
accuracy: 100, // optional
touch: true, // optional
permissions: [BrowserPermission.Geolocation, BrowserPermission.Notifications], // optional
permissions: ["geolocation","notifications"], // optional
sleep: 3, // optional
width: 800, // optional
height: 600, // optional
quality: 85, // optional
output: ImageFormat.Jpeg // optional
output: Output.Jpg // optional
});
console.log(result);
@@ -1,4 +1,4 @@
import { Client, Teams, Roles } from "appwrite";
import { Client, Teams } from "appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
@@ -8,7 +8,7 @@ const teams = new Teams(client);
const result = await teams.createMembership({
teamId: '<TEAM_ID>',
roles: [Roles.Admin],
roles: [],
email: 'email@example.com', // optional
userId: '<USER_ID>', // optional
phone: '+12065550100', // optional
@@ -1,4 +1,4 @@
import { Client, Teams, Roles } from "appwrite";
import { Client, Teams } from "appwrite";
const client = new Client()
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
@@ -9,7 +9,7 @@ const teams = new Teams(client);
const result = await teams.updateMembership({
teamId: '<TEAM_ID>',
membershipId: '<MEMBERSHIP_ID>',
roles: [Roles.Admin]
roles: []
});
console.log(result);

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