mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
304
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4a589c535 | ||
|
|
99dc31062d | ||
|
|
6514a992a1 | ||
|
|
1167654c6a | ||
|
|
28c5df074d | ||
|
|
785efa3355 | ||
|
|
329b7e1856 | ||
|
|
0dd2f29a7e | ||
|
|
67e2104e98 | ||
|
|
a2ff833efe | ||
|
|
e894eca201 | ||
|
|
7573e0fee9 | ||
|
|
46be2d0e49 | ||
|
|
700ed9f882 | ||
|
|
440be52405 | ||
|
|
7643196d97 | ||
|
|
7e994f8db5 | ||
|
|
b317f85fb6 | ||
|
|
8e98c08a23 | ||
|
|
6ebc36d0f3 | ||
|
|
e5ed7c835a | ||
|
|
cbb5963b29 | ||
|
|
f376db7b26 | ||
|
|
bfd34b3b15 | ||
|
|
a66551863a | ||
|
|
33abd7f0ed | ||
|
|
633e290bb4 | ||
|
|
9b14a8f08d | ||
|
|
ddbddafbf9 | ||
|
|
86a4bfa74e | ||
|
|
8124b07860 | ||
|
|
a7898b3d5b | ||
|
|
5d24b51421 | ||
|
|
0203323b4a | ||
|
|
94e29cff53 | ||
|
|
72def3b2fb | ||
|
|
d015a75e81 | ||
|
|
1306c85eb5 | ||
|
|
c3ea66b37a | ||
|
|
72ce068714 | ||
|
|
ea76213dd2 | ||
|
|
12b79363d1 | ||
|
|
0723101397 | ||
|
|
d56a3c1534 | ||
|
|
b86d7ac3fa | ||
|
|
b1fab79dc4 | ||
|
|
2f066a6ba8 | ||
|
|
e8ca0610ee | ||
|
|
15caa27977 | ||
|
|
5f22022527 | ||
|
|
b9c7c172ad | ||
|
|
c2bf1e8040 | ||
|
|
cda03f63ab | ||
|
|
f5a61fb4d6 | ||
|
|
429f73eb85 | ||
|
|
da871635d9 | ||
|
|
ccaea5d010 | ||
|
|
b5e9c1786a | ||
|
|
79e150b7b2 | ||
|
|
beee5e721e | ||
|
|
0ef4bf21cc | ||
|
|
b2486fcb6c | ||
|
|
c67b77bca0 | ||
|
|
5d5a14bd77 | ||
|
|
1308706023 | ||
|
|
ce91b1a03d | ||
|
|
fbf390c710 | ||
|
|
991f5ff9fd | ||
|
|
e8d8373922 | ||
|
|
0fee1f0ffd | ||
|
|
eb07e99225 | ||
|
|
325968c52a | ||
|
|
39e66711c8 | ||
|
|
42a1c8da38 | ||
|
|
206bd63620 | ||
|
|
75696a21bf | ||
|
|
48866acdb7 | ||
|
|
b1171c661e | ||
|
|
a0e4e89621 | ||
|
|
728ed57df0 | ||
|
|
629479d275 | ||
|
|
5c922f89fe | ||
|
|
6e292349b9 | ||
|
|
7b940e3a17 | ||
|
|
7ab3debb10 | ||
|
|
c083e1ce74 | ||
|
|
61e886eed4 | ||
|
|
71ac9c7264 | ||
|
|
f78d5523ce | ||
|
|
955e1bcbe9 | ||
|
|
09a337aa1b | ||
|
|
2cfb5ecfd9 | ||
|
|
38687bc24e | ||
|
|
77b2a57968 | ||
|
|
223bb7f08a | ||
|
|
b73abf69d0 | ||
|
|
e76ed98590 | ||
|
|
7b10fe1371 | ||
|
|
e4abc0e0da | ||
|
|
6478499f1c | ||
|
|
3bbfbdb063 | ||
|
|
bc6ecbd22c | ||
|
|
4d2f631393 | ||
|
|
479a583ff5 | ||
|
|
08cd610823 | ||
|
|
6cd19bf330 | ||
|
|
d6d8729983 | ||
|
|
6c866be9f3 | ||
|
|
0ec515779e | ||
|
|
91240e0ca7 | ||
|
|
e3ae7daab4 | ||
|
|
f482e6de68 | ||
|
|
c12cad80bb | ||
|
|
2a17429226 | ||
|
|
e2627d784b | ||
|
|
5c915ef92f | ||
|
|
5697d983d0 | ||
|
|
a5fb10c61d | ||
|
|
5469b78367 | ||
|
|
e887859cc0 | ||
|
|
942cfc4591 | ||
|
|
321fc8ee70 | ||
|
|
568ac54d3d | ||
|
|
432e86ec07 | ||
|
|
fef1b5b5e5 | ||
|
|
bfac0f92af | ||
|
|
ae6df78020 | ||
|
|
23d84cf29f | ||
|
|
c79019f960 | ||
|
|
dd78b6dbce | ||
|
|
81e64afe7a | ||
|
|
5ef675bcdd | ||
|
|
a4734a5de7 | ||
|
|
03812cf7bf | ||
|
|
515a34c180 | ||
|
|
8e688bad4d | ||
|
|
4905f7cc48 | ||
|
|
3d27b76051 | ||
|
|
2d6348bf5d | ||
|
|
2131a0cea1 | ||
|
|
9379e4d6c8 | ||
|
|
15922fb88c | ||
|
|
ba9526cce0 | ||
|
|
6dbed494e3 | ||
|
|
4eb1e7ae45 | ||
|
|
bdd53212a0 | ||
|
|
1a69d9d236 | ||
|
|
a860c33969 | ||
|
|
ffd73dc70f | ||
|
|
362a7bef0e | ||
|
|
d08a6f572c | ||
|
|
893c1aa669 | ||
|
|
c34873a8b0 | ||
|
|
17f60b611b | ||
|
|
cd0d609229 | ||
|
|
497e5f8d00 | ||
|
|
60e1efb8cb | ||
|
|
3d4b29e0f1 | ||
|
|
dad21a912e | ||
|
|
445d7495a3 | ||
|
|
aebbe91c34 | ||
|
|
2a9f9f6851 | ||
|
|
eec023aab3 | ||
|
|
71f389e1c0 | ||
|
|
d71b289025 | ||
|
|
f4da9b54e7 | ||
|
|
df84b94df0 | ||
|
|
42bf515c8a | ||
|
|
a7d39b5ef7 | ||
|
|
4c1417944d | ||
|
|
27aee1fb88 | ||
|
|
4319c16584 | ||
|
|
d68fc9a22e | ||
|
|
e3d6fc123f | ||
|
|
e6505b9cf9 | ||
|
|
c1ab9da740 | ||
|
|
b567cd5341 | ||
|
|
f2ac01bccf | ||
|
|
eecfba2a72 | ||
|
|
2cfaa2223e | ||
|
|
1a87bde88e | ||
|
|
4b92e3781f | ||
|
|
65bb8946f6 | ||
|
|
db4dcd164e | ||
|
|
384c47f436 | ||
|
|
dcc926402d | ||
|
|
6969e446d5 | ||
|
|
c8bb939357 | ||
|
|
02488c853d | ||
|
|
92f26c1b5a | ||
|
|
ded06d7435 | ||
|
|
6c59cef8f4 | ||
|
|
46072bb95e | ||
|
|
904b7312d6 | ||
|
|
68fc6b0600 | ||
|
|
47ea8699d1 | ||
|
|
9ba3f6dfe8 | ||
|
|
3483daef0d | ||
|
|
9943305640 | ||
|
|
8bb6f01e22 | ||
|
|
2cc7bbc0a4 | ||
|
|
e9366ac2e6 | ||
|
|
22f8a3eab9 | ||
|
|
896e5a517a | ||
|
|
281dcfc64a | ||
|
|
457cd53475 | ||
|
|
93e7dc772d | ||
|
|
228d095ee5 | ||
|
|
43189f8940 | ||
|
|
b615e4b901 | ||
|
|
ebd6457361 | ||
|
|
0d3bcc9b3a | ||
|
|
61e98a501a | ||
|
|
502012ddf7 | ||
|
|
a6eb479c93 | ||
|
|
e0d7b418dc | ||
|
|
ea97072479 | ||
|
|
7573ee75a2 | ||
|
|
0dc00eb2c5 | ||
|
|
b8ccc3b846 | ||
|
|
6f1cac7d76 | ||
|
|
5f3384b821 | ||
|
|
71cb462f49 | ||
|
|
27e859030d | ||
|
|
e7a82e4d31 | ||
|
|
6dc836307e | ||
|
|
0582cdf394 | ||
|
|
573d8423a3 | ||
|
|
3fa3318346 | ||
|
|
23dfb23a3b | ||
|
|
5642983f91 | ||
|
|
b208e5c066 | ||
|
|
6756ee31b6 | ||
|
|
89c988d73c | ||
|
|
c0f8dee4d4 | ||
|
|
eb10996517 | ||
|
|
31803f0eb9 | ||
|
|
ac9214f3c4 | ||
|
|
14347d86a8 | ||
|
|
1b855d2d41 | ||
|
|
59b41c4b52 | ||
|
|
b6e8b55994 | ||
|
|
3a4fb5dd14 | ||
|
|
2a679ead32 | ||
|
|
7da9d480d0 | ||
|
|
001d38caf5 | ||
|
|
cd651dbdb8 | ||
|
|
2dfb386b12 | ||
|
|
e9dac6710f | ||
|
|
a5d4f69c6c | ||
|
|
f788fc8f8a | ||
|
|
67ef2ab552 | ||
|
|
297fae8f81 | ||
|
|
3e403194e4 | ||
|
|
0305790e69 | ||
|
|
25435aaa11 | ||
|
|
19895e54e3 | ||
|
|
4b3fe0b6fe | ||
|
|
4ef906b836 | ||
|
|
28aa4e8a8d | ||
|
|
9c6a6c265a | ||
|
|
dc0eb5f7a7 | ||
|
|
03ccca2c35 | ||
|
|
00b5236dea | ||
|
|
df57b59786 | ||
|
|
eda189dbf1 | ||
|
|
ee911e3df6 | ||
|
|
eb2c616089 | ||
|
|
5f5d9b4fcb | ||
|
|
7c56a76feb | ||
|
|
6c1f967509 | ||
|
|
04f660e44b | ||
|
|
b4c1b96d43 | ||
|
|
44d8c66e95 | ||
|
|
6774de4eef | ||
|
|
ee0f15eed6 | ||
|
|
c54d1d29a5 | ||
|
|
3b4196735a | ||
|
|
7e315f79cc | ||
|
|
336bd44826 | ||
|
|
881d96a653 | ||
|
|
874e5f61f0 | ||
|
|
39cf207df9 | ||
|
|
c08acedf6a | ||
|
|
9477a5d980 | ||
|
|
c0c1d693c2 | ||
|
|
6e47fb6c70 | ||
|
|
cca49f8f6a | ||
|
|
b0bd9e5b78 | ||
|
|
6f7cca7d68 | ||
|
|
bce3ce85d5 | ||
|
|
b7e2606b9f | ||
|
|
09d71e73af | ||
|
|
465912822f | ||
|
|
ca43281fa9 | ||
|
|
c69382f29f | ||
|
|
d45d27b780 | ||
|
|
859d146e85 | ||
|
|
69d5ce0f55 | ||
|
|
0a3877a900 | ||
|
|
31b9ff4f05 | ||
|
|
1c3f778da9 | ||
|
|
6f16b56f31 | ||
|
|
59f178d634 |
@@ -2,6 +2,7 @@ _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=
|
||||
@@ -102,9 +103,11 @@ _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
|
||||
|
||||
+2
-3
@@ -77,6 +77,7 @@ 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 && \
|
||||
@@ -87,9 +88,7 @@ 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-payments-usage && \
|
||||
chmod +x /usr/local/bin/schedule-payments-usage
|
||||
chmod +x /usr/local/bin/worker-stats-resources
|
||||
|
||||
RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
|
||||
|
||||
|
||||
+21
-14
@@ -5,7 +5,6 @@ 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;
|
||||
@@ -42,8 +41,6 @@ Config::setParam('runtimes', (new Runtimes('v5'))->getAll(supported: false));
|
||||
// require controllers after overwriting runtimes
|
||||
require_once __DIR__ . '/controllers/general.php';
|
||||
|
||||
Authorization::disable();
|
||||
|
||||
CLI::setResource('register', fn () => $register);
|
||||
|
||||
CLI::setResource('cache', function ($pools) {
|
||||
@@ -61,7 +58,13 @@ CLI::setResource('pools', function (Registry $register) {
|
||||
return $register->get('pools');
|
||||
}, ['register']);
|
||||
|
||||
CLI::setResource('dbForPlatform', function ($pools, $cache) {
|
||||
CLI::setResource('authorization', function () {
|
||||
$authorization = new Authorization();
|
||||
$authorization->disable();
|
||||
return $authorization;
|
||||
}, []);
|
||||
|
||||
CLI::setResource('dbForPlatform', function ($pools, $cache, $authorization) {
|
||||
$sleep = 3;
|
||||
$maxAttempts = 5;
|
||||
$attempts = 0;
|
||||
@@ -75,6 +78,8 @@ CLI::setResource('dbForPlatform', function ($pools, $cache) {
|
||||
$dbForPlatform = new Database($adapter, $cache);
|
||||
|
||||
$dbForPlatform
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', 'console');
|
||||
@@ -100,7 +105,7 @@ CLI::setResource('dbForPlatform', function ($pools, $cache) {
|
||||
}
|
||||
|
||||
return $dbForPlatform;
|
||||
}, ['pools', 'cache']);
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
CLI::setResource('console', function () {
|
||||
return new Document(Config::getParam('console'));
|
||||
@@ -111,10 +116,10 @@ CLI::setResource(
|
||||
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
|
||||
);
|
||||
|
||||
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
|
||||
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, $authorization) {
|
||||
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
|
||||
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) {
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForPlatform;
|
||||
}
|
||||
@@ -147,6 +152,7 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
|
||||
|
||||
$adapter = new DatabasePool($pools->get($dsn->getHost()));
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
$databases[$dsn->getHost()] = $database;
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
@@ -163,17 +169,19 @@ 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']);
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
|
||||
|
||||
CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
CLI::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$database = null;
|
||||
|
||||
return function (?Document $project = null) use ($pools, $cache, $database) {
|
||||
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
|
||||
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$database->setTenant((int)$project->getSequence());
|
||||
return $database;
|
||||
@@ -183,6 +191,8 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setSharedTables(true)
|
||||
->setNamespace('logsV1')
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_TASK)
|
||||
@@ -195,7 +205,7 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'cache']);
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
CLI::setResource('publisher', function (Group $pools) {
|
||||
return new BrokerPool(publisher: $pools->get('publisher'));
|
||||
}, ['pools']);
|
||||
@@ -229,9 +239,6 @@ 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()));
|
||||
|
||||
@@ -26,8 +26,8 @@ unset($common['files']);
|
||||
$collections = [
|
||||
'buckets' => $buckets,
|
||||
'databases' => $databases,
|
||||
'projects' => array_merge($projects, $common),
|
||||
'console' => array_merge($platform, $common),
|
||||
'projects' => array_merge_recursive($projects, $common),
|
||||
'console' => array_merge_recursive($platform, $common),
|
||||
'logs' => $logs,
|
||||
];
|
||||
|
||||
|
||||
@@ -1288,6 +1288,17 @@ 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' => [
|
||||
[
|
||||
|
||||
@@ -232,17 +232,6 @@ $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,
|
||||
@@ -643,17 +632,19 @@ $platformCollections = [
|
||||
'$id' => ID::custom('keys'),
|
||||
'name' => 'keys',
|
||||
'attributes' => [
|
||||
// Delete eventuelly, when removing dual-write too
|
||||
[
|
||||
'$id' => ID::custom('projectInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
// Delete eventuelly, when removing dual-write too
|
||||
[
|
||||
'$id' => ID::custom('projectId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
@@ -666,7 +657,7 @@ $platformCollections = [
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'resourceType',
|
||||
'$id' => ID::custom('resourceType'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
@@ -677,7 +668,7 @@ $platformCollections = [
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'resourceId',
|
||||
'$id' => ID::custom('resourceId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
@@ -688,7 +679,7 @@ $platformCollections = [
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => 'resourceInternalId',
|
||||
'$id' => ID::custom('resourceInternalId'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
@@ -767,21 +758,14 @@ $platformCollections = [
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
'$id' => ID::custom('_key_project'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['projectInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_resource',
|
||||
'$id' => ID::custom('_key_resource'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['resourceType', 'resourceInternalId'],
|
||||
'lengths' => [Database::LENGTH_KEY],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => '_key_accessedAt',
|
||||
'$id' => ID::custom('_key_accessedAt'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['accessedAt'],
|
||||
'lengths' => [],
|
||||
|
||||
+106
-121
@@ -567,6 +567,17 @@ 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,
|
||||
@@ -765,6 +776,17 @@ 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'),
|
||||
@@ -776,6 +798,28 @@ 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,
|
||||
@@ -1035,6 +1079,17 @@ 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,
|
||||
@@ -1046,6 +1101,17 @@ 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,
|
||||
@@ -1200,6 +1266,28 @@ 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,
|
||||
@@ -1357,6 +1445,17 @@ 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'),
|
||||
@@ -2098,6 +2197,13 @@ return [
|
||||
'lengths' => [],
|
||||
'orders' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('_key_resourceType'),
|
||||
'type' => Database::INDEX_KEY,
|
||||
'attributes' => ['resourceType'],
|
||||
'lengths' => [],
|
||||
'orders' => [Database::ORDER_ASC],
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -2645,125 +2751,4 @@ 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] ],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
+22
-40
@@ -357,6 +357,11 @@ 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.',
|
||||
@@ -1074,6 +1079,11 @@ 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.',
|
||||
@@ -1103,7 +1113,6 @@ 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,
|
||||
@@ -1329,46 +1338,19 @@ return [
|
||||
'description' => 'Target has an invalid provider type.',
|
||||
'code' => 400,
|
||||
],
|
||||
|
||||
/** 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::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,
|
||||
],
|
||||
Exception::PAYMENT_PLAN_ALREADY_EXISTS => [
|
||||
'name' => Exception::PAYMENT_PLAN_ALREADY_EXISTS,
|
||||
'description' => 'Payment plan with the requested ID already exists.',
|
||||
'code' => 409,
|
||||
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_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,
|
||||
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,
|
||||
],
|
||||
];
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?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.',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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",
|
||||
],
|
||||
];
|
||||
@@ -166,13 +166,4 @@ 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)'
|
||||
],
|
||||
];
|
||||
+23
-3
@@ -60,7 +60,7 @@ return [
|
||||
[
|
||||
'key' => 'flutter',
|
||||
'name' => 'Flutter',
|
||||
'version' => '20.3.2',
|
||||
'version' => '20.3.3',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-flutter',
|
||||
'package' => 'https://pub.dev/packages/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -227,7 +227,7 @@ return [
|
||||
[
|
||||
'key' => 'cli',
|
||||
'name' => 'Command Line',
|
||||
'version' => '12.0.1',
|
||||
'version' => '13.1.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-cli',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite-cli',
|
||||
'enabled' => true,
|
||||
@@ -250,6 +250,26 @@ return [
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'key' => 'markdown',
|
||||
'name' => 'Markdown',
|
||||
'version' => '0.1.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'),
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -377,7 +397,7 @@ return [
|
||||
[
|
||||
'key' => 'dart',
|
||||
'name' => 'Dart',
|
||||
'version' => '20.1.0',
|
||||
'version' => '20.1.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dart',
|
||||
'package' => 'https://pub.dev/packages/dart_appwrite',
|
||||
'enabled' => true,
|
||||
|
||||
+3
-16
@@ -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' => 'api/avatars.php',
|
||||
'controller' => '', // Uses modules
|
||||
'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' => 'api/health.php',
|
||||
'controller' => '', // Uses modules
|
||||
'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' => '',
|
||||
'controller' => '', // Uses modules
|
||||
'sdk' => true,
|
||||
'docs' => true,
|
||||
'docsUrl' => 'https://appwrite.io/docs/client/storage',
|
||||
@@ -225,19 +225,6 @@ 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',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"version": "1.8.0",
|
||||
"version": "1.8.1",
|
||||
"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,7 +140,8 @@
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
@@ -224,12 +225,14 @@
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"description": "User password. Must be at least 8 chars.",
|
||||
"x-example": "password"
|
||||
"x-example": "password",
|
||||
"format": "password"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -439,7 +442,8 @@
|
||||
"duration": {
|
||||
"type": "integer",
|
||||
"description": "Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.",
|
||||
"x-example": 0
|
||||
"x-example": 0,
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -551,7 +555,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMFA",
|
||||
"group": "mfa",
|
||||
"weight": 288,
|
||||
"weight": 253,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa.md",
|
||||
@@ -623,7 +627,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 290,
|
||||
"weight": 255,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-authenticator.md",
|
||||
@@ -747,7 +751,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 291,
|
||||
"weight": 256,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-authenticator.md",
|
||||
@@ -887,7 +891,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 292,
|
||||
"weight": 257,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/delete-mfa-authenticator.md",
|
||||
@@ -1011,7 +1015,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaChallenge",
|
||||
"group": "mfa",
|
||||
"weight": 296,
|
||||
"weight": 261,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-challenge.md",
|
||||
@@ -1145,7 +1149,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaChallenge",
|
||||
"group": "mfa",
|
||||
"weight": 297,
|
||||
"weight": 262,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-challenge.md",
|
||||
@@ -1283,7 +1287,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listMfaFactors",
|
||||
"group": "mfa",
|
||||
"weight": 289,
|
||||
"weight": 254,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/list-mfa-factors.md",
|
||||
@@ -1384,7 +1388,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 295,
|
||||
"weight": 260,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/get-mfa-recovery-codes.md",
|
||||
@@ -1483,7 +1487,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 293,
|
||||
"weight": 258,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-recovery-codes.md",
|
||||
@@ -1582,7 +1586,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 294,
|
||||
"weight": 259,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-recovery-codes.md",
|
||||
@@ -1796,7 +1800,8 @@
|
||||
"oldPassword": {
|
||||
"type": "string",
|
||||
"description": "Current user password. Must be at least 8 chars.",
|
||||
"x-example": "password"
|
||||
"x-example": "password",
|
||||
"format": "password"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1868,12 +1873,14 @@
|
||||
"phone": {
|
||||
"type": "string",
|
||||
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
|
||||
"x-example": "+12065550100"
|
||||
"x-example": "+12065550100",
|
||||
"format": "phone"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"description": "User password. Must be at least 8 chars.",
|
||||
"x-example": "password"
|
||||
"x-example": "password",
|
||||
"format": "password"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -2072,12 +2079,14 @@
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"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"
|
||||
"x-example": "https:\/\/example.com",
|
||||
"format": "url"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -2382,12 +2391,14 @@
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"description": "User password. Must be at least 8 chars.",
|
||||
"x-example": "password"
|
||||
"x-example": "password",
|
||||
"format": "password"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -2573,9 +2584,7 @@
|
||||
"yammer",
|
||||
"yandex",
|
||||
"zoho",
|
||||
"zoom",
|
||||
"mock",
|
||||
"mock-unverified"
|
||||
"zoom"
|
||||
],
|
||||
"x-enum-name": "OAuthProvider",
|
||||
"x-enum-keys": []
|
||||
@@ -3304,7 +3313,8 @@
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"phrase": {
|
||||
"type": "boolean",
|
||||
@@ -3390,12 +3400,14 @@
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"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"
|
||||
"x-example": "https:\/\/example.com",
|
||||
"format": "url"
|
||||
},
|
||||
"phrase": {
|
||||
"type": "boolean",
|
||||
@@ -3504,9 +3516,7 @@
|
||||
"yammer",
|
||||
"yandex",
|
||||
"zoho",
|
||||
"zoom",
|
||||
"mock",
|
||||
"mock-unverified"
|
||||
"zoom"
|
||||
],
|
||||
"x-enum-name": "OAuthProvider",
|
||||
"x-enum-keys": []
|
||||
@@ -3621,7 +3631,8 @@
|
||||
"phone": {
|
||||
"type": "string",
|
||||
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
|
||||
"x-example": "+12065550100"
|
||||
"x-example": "+12065550100",
|
||||
"format": "phone"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -3746,7 +3757,8 @@
|
||||
"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"
|
||||
"x-example": "https:\/\/example.com",
|
||||
"format": "url"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -4039,7 +4051,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getBrowser",
|
||||
"group": null,
|
||||
"weight": 50,
|
||||
"weight": 264,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-browser.md",
|
||||
@@ -4167,7 +4179,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getCreditCard",
|
||||
"group": null,
|
||||
"weight": 49,
|
||||
"weight": 263,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-credit-card.md",
|
||||
@@ -4301,7 +4313,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFavicon",
|
||||
"group": null,
|
||||
"weight": 53,
|
||||
"weight": 267,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-favicon.md",
|
||||
@@ -4361,7 +4373,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFlag",
|
||||
"group": null,
|
||||
"weight": 51,
|
||||
"weight": 265,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-flag.md",
|
||||
@@ -4851,7 +4863,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getImage",
|
||||
"group": null,
|
||||
"weight": 52,
|
||||
"weight": 266,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-image.md",
|
||||
@@ -4935,7 +4947,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getInitials",
|
||||
"group": null,
|
||||
"weight": 55,
|
||||
"weight": 269,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-initials.md",
|
||||
@@ -5029,7 +5041,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getQR",
|
||||
"group": null,
|
||||
"weight": 54,
|
||||
"weight": 268,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-qr.md",
|
||||
@@ -5123,7 +5135,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getScreenshot",
|
||||
"group": null,
|
||||
"weight": 56,
|
||||
"weight": 270,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-screenshot.md",
|
||||
@@ -5876,7 +5888,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 362,
|
||||
"weight": 338,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-transactions.md",
|
||||
@@ -5943,7 +5955,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 358,
|
||||
"weight": 334,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-transaction.md",
|
||||
@@ -5980,7 +5992,8 @@
|
||||
"ttl": {
|
||||
"type": "integer",
|
||||
"description": "Seconds before the transaction expires.",
|
||||
"x-example": 60
|
||||
"x-example": 60,
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6013,7 +6026,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 359,
|
||||
"weight": 335,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-transaction.md",
|
||||
@@ -6077,7 +6090,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 360,
|
||||
"weight": 336,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-transaction.md",
|
||||
@@ -6155,7 +6168,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 361,
|
||||
"weight": 337,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-transaction.md",
|
||||
@@ -6221,7 +6234,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 363,
|
||||
"weight": 339,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-operations.md",
|
||||
@@ -6306,7 +6319,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listDocuments",
|
||||
"group": "documents",
|
||||
"weight": 321,
|
||||
"weight": 297,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-documents.md",
|
||||
@@ -6418,7 +6431,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createDocument",
|
||||
"group": "documents",
|
||||
"weight": 313,
|
||||
"weight": 289,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-document.md",
|
||||
@@ -6579,7 +6592,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getDocument",
|
||||
"group": "documents",
|
||||
"weight": 314,
|
||||
"weight": 290,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-document.md",
|
||||
@@ -6690,7 +6703,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "upsertDocument",
|
||||
"group": "documents",
|
||||
"weight": 317,
|
||||
"weight": 293,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/upsert-document.md",
|
||||
@@ -6845,7 +6858,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateDocument",
|
||||
"group": "documents",
|
||||
"weight": 315,
|
||||
"weight": 291,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-document.md",
|
||||
@@ -6957,7 +6970,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteDocument",
|
||||
"group": "documents",
|
||||
"weight": 319,
|
||||
"weight": 295,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-document.md",
|
||||
@@ -7064,7 +7077,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "decrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 324,
|
||||
"weight": 300,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/decrement-document-attribute.md",
|
||||
@@ -7146,12 +7159,14 @@
|
||||
"value": {
|
||||
"type": "number",
|
||||
"description": "Value to increment the attribute by. The value must be a number.",
|
||||
"x-example": null
|
||||
"x-example": null,
|
||||
"format": "float"
|
||||
},
|
||||
"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": {
|
||||
@@ -7191,7 +7206,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "incrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 323,
|
||||
"weight": 299,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/increment-document-attribute.md",
|
||||
@@ -7273,12 +7288,14 @@
|
||||
"value": {
|
||||
"type": "number",
|
||||
"description": "Value to increment the attribute by. The value must be a number.",
|
||||
"x-example": null
|
||||
"x-example": null,
|
||||
"format": "float"
|
||||
},
|
||||
"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": {
|
||||
@@ -7318,7 +7335,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listExecutions",
|
||||
"group": "executions",
|
||||
"weight": 454,
|
||||
"weight": 431,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/list-executions.md",
|
||||
@@ -7405,7 +7422,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createExecution",
|
||||
"group": "executions",
|
||||
"weight": 452,
|
||||
"weight": 429,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/create-execution.md",
|
||||
@@ -7523,7 +7540,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getExecution",
|
||||
"group": "executions",
|
||||
"weight": 453,
|
||||
"weight": 430,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/get-execution.md",
|
||||
@@ -7598,7 +7615,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "query",
|
||||
"group": "graphql",
|
||||
"weight": 225,
|
||||
"weight": 190,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"demo": "graphql\/query.md",
|
||||
@@ -7652,7 +7669,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "mutation",
|
||||
"group": "graphql",
|
||||
"weight": 224,
|
||||
"weight": 189,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"demo": "graphql\/mutation.md",
|
||||
@@ -7706,7 +7723,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"group": null,
|
||||
"weight": 60,
|
||||
"weight": 49,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/get.md",
|
||||
@@ -7760,7 +7777,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCodes",
|
||||
"group": null,
|
||||
"weight": 61,
|
||||
"weight": 50,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-codes.md",
|
||||
@@ -7814,7 +7831,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listContinents",
|
||||
"group": null,
|
||||
"weight": 65,
|
||||
"weight": 54,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-continents.md",
|
||||
@@ -7868,7 +7885,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCountries",
|
||||
"group": null,
|
||||
"weight": 62,
|
||||
"weight": 51,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-countries.md",
|
||||
@@ -7922,7 +7939,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCountriesEU",
|
||||
"group": null,
|
||||
"weight": 63,
|
||||
"weight": 52,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-countries-eu.md",
|
||||
@@ -7976,7 +7993,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCountriesPhones",
|
||||
"group": null,
|
||||
"weight": 64,
|
||||
"weight": 53,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-countries-phones.md",
|
||||
@@ -8030,7 +8047,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCurrencies",
|
||||
"group": null,
|
||||
"weight": 66,
|
||||
"weight": 55,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-currencies.md",
|
||||
@@ -8084,7 +8101,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listLanguages",
|
||||
"group": null,
|
||||
"weight": 67,
|
||||
"weight": 56,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-languages.md",
|
||||
@@ -8138,7 +8155,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 272,
|
||||
"weight": 237,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/create-subscriber.md",
|
||||
@@ -8222,7 +8239,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 276,
|
||||
"weight": 241,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/delete-subscriber.md",
|
||||
@@ -8298,7 +8315,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listFiles",
|
||||
"group": "files",
|
||||
"weight": 525,
|
||||
"weight": 527,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "storage\/list-files.md",
|
||||
@@ -8397,7 +8414,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createFile",
|
||||
"group": "files",
|
||||
"weight": 523,
|
||||
"weight": 525,
|
||||
"cookies": false,
|
||||
"type": "upload",
|
||||
"demo": "storage\/create-file.md",
|
||||
@@ -8452,7 +8469,8 @@
|
||||
"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
|
||||
"x-example": null,
|
||||
"format": "binary"
|
||||
},
|
||||
"permissions": {
|
||||
"type": "array",
|
||||
@@ -8498,7 +8516,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFile",
|
||||
"group": "files",
|
||||
"weight": 524,
|
||||
"weight": 526,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "storage\/get-file.md",
|
||||
@@ -8572,7 +8590,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateFile",
|
||||
"group": "files",
|
||||
"weight": 526,
|
||||
"weight": 528,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "storage\/update-file.md",
|
||||
@@ -8664,7 +8682,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteFile",
|
||||
"group": "files",
|
||||
"weight": 527,
|
||||
"weight": 529,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "storage\/delete-file.md",
|
||||
@@ -8733,7 +8751,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFileDownload",
|
||||
"group": "files",
|
||||
"weight": 529,
|
||||
"weight": 531,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "storage\/get-file-download.md",
|
||||
@@ -8813,7 +8831,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFilePreview",
|
||||
"group": "files",
|
||||
"weight": 528,
|
||||
"weight": 530,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "storage\/get-file-preview.md",
|
||||
@@ -9043,7 +9061,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFileView",
|
||||
"group": "files",
|
||||
"weight": 530,
|
||||
"weight": 532,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "storage\/get-file-view.md",
|
||||
@@ -9130,7 +9148,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 427,
|
||||
"weight": 403,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-transactions.md",
|
||||
@@ -9200,7 +9218,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 423,
|
||||
"weight": 399,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-transaction.md",
|
||||
@@ -9240,7 +9258,8 @@
|
||||
"ttl": {
|
||||
"type": "integer",
|
||||
"description": "Seconds before the transaction expires.",
|
||||
"x-example": 60
|
||||
"x-example": 60,
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9273,7 +9292,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 424,
|
||||
"weight": 400,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-transaction.md",
|
||||
@@ -9340,7 +9359,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 425,
|
||||
"weight": 401,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-transaction.md",
|
||||
@@ -9421,7 +9440,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 426,
|
||||
"weight": 402,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-transaction.md",
|
||||
@@ -9490,7 +9509,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 428,
|
||||
"weight": 404,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-operations.md",
|
||||
@@ -9578,7 +9597,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listRows",
|
||||
"group": "rows",
|
||||
"weight": 419,
|
||||
"weight": 395,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-rows.md",
|
||||
@@ -9689,7 +9708,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createRow",
|
||||
"group": "rows",
|
||||
"weight": 411,
|
||||
"weight": 387,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-row.md",
|
||||
@@ -9845,7 +9864,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getRow",
|
||||
"group": "rows",
|
||||
"weight": 412,
|
||||
"weight": 388,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-row.md",
|
||||
@@ -9955,7 +9974,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "upsertRow",
|
||||
"group": "rows",
|
||||
"weight": 415,
|
||||
"weight": 391,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/upsert-row.md",
|
||||
@@ -10105,7 +10124,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateRow",
|
||||
"group": "rows",
|
||||
"weight": 413,
|
||||
"weight": 389,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-row.md",
|
||||
@@ -10216,7 +10235,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteRow",
|
||||
"group": "rows",
|
||||
"weight": 417,
|
||||
"weight": 393,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-row.md",
|
||||
@@ -10322,7 +10341,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "decrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 422,
|
||||
"weight": 398,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/decrement-row-column.md",
|
||||
@@ -10403,12 +10422,14 @@
|
||||
"value": {
|
||||
"type": "number",
|
||||
"description": "Value to increment the column by. The value must be a number.",
|
||||
"x-example": null
|
||||
"x-example": null,
|
||||
"format": "float"
|
||||
},
|
||||
"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": {
|
||||
@@ -10448,7 +10469,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "incrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 421,
|
||||
"weight": 397,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/increment-row-column.md",
|
||||
@@ -10529,12 +10550,14 @@
|
||||
"value": {
|
||||
"type": "number",
|
||||
"description": "Value to increment the column by. The value must be a number.",
|
||||
"x-example": null
|
||||
"x-example": null,
|
||||
"format": "float"
|
||||
},
|
||||
"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": {
|
||||
@@ -10574,7 +10597,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "list",
|
||||
"group": "teams",
|
||||
"weight": 145,
|
||||
"weight": 110,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/list.md",
|
||||
@@ -10663,7 +10686,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "create",
|
||||
"group": "teams",
|
||||
"weight": 144,
|
||||
"weight": 109,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/create.md",
|
||||
@@ -10750,7 +10773,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"group": "teams",
|
||||
"weight": 146,
|
||||
"weight": 111,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/get.md",
|
||||
@@ -10814,7 +10837,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateName",
|
||||
"group": "teams",
|
||||
"weight": 148,
|
||||
"weight": 113,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/update-name.md",
|
||||
@@ -10890,7 +10913,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "delete",
|
||||
"group": "teams",
|
||||
"weight": 150,
|
||||
"weight": 115,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/delete.md",
|
||||
@@ -10956,7 +10979,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listMemberships",
|
||||
"group": "memberships",
|
||||
"weight": 152,
|
||||
"weight": 117,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/list-memberships.md",
|
||||
@@ -11055,7 +11078,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMembership",
|
||||
"group": "memberships",
|
||||
"weight": 151,
|
||||
"weight": 116,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/create-membership.md",
|
||||
@@ -11104,7 +11127,8 @@
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "Email of the new team member.",
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"userId": {
|
||||
"type": "string",
|
||||
@@ -11114,7 +11138,8 @@
|
||||
"phone": {
|
||||
"type": "string",
|
||||
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
|
||||
"x-example": "+12065550100"
|
||||
"x-example": "+12065550100",
|
||||
"format": "phone"
|
||||
},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
@@ -11134,7 +11159,8 @@
|
||||
"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"
|
||||
"x-example": "https:\/\/example.com",
|
||||
"format": "url"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
@@ -11175,7 +11201,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getMembership",
|
||||
"group": "memberships",
|
||||
"weight": 153,
|
||||
"weight": 118,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/get-membership.md",
|
||||
@@ -11249,7 +11275,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMembership",
|
||||
"group": "memberships",
|
||||
"weight": 154,
|
||||
"weight": 119,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/update-membership.md",
|
||||
@@ -11345,7 +11371,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteMembership",
|
||||
"group": "memberships",
|
||||
"weight": 156,
|
||||
"weight": 121,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/delete-membership.md",
|
||||
@@ -11421,7 +11447,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMembershipStatus",
|
||||
"group": "memberships",
|
||||
"weight": 155,
|
||||
"weight": 120,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/update-membership-status.md",
|
||||
@@ -11521,7 +11547,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getPrefs",
|
||||
"group": "teams",
|
||||
"weight": 147,
|
||||
"weight": 112,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/get-prefs.md",
|
||||
@@ -11584,7 +11610,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updatePrefs",
|
||||
"group": "teams",
|
||||
"weight": 149,
|
||||
"weight": 114,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/update-prefs.md",
|
||||
@@ -13428,6 +13454,16 @@
|
||||
"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": [
|
||||
@@ -13441,7 +13477,9 @@
|
||||
"mimeType",
|
||||
"sizeOriginal",
|
||||
"chunksTotal",
|
||||
"chunksUploaded"
|
||||
"chunksUploaded",
|
||||
"encryption",
|
||||
"compression"
|
||||
],
|
||||
"example": {
|
||||
"$id": "5e5ea5c16897e",
|
||||
@@ -13456,7 +13494,9 @@
|
||||
"mimeType": "image\/png",
|
||||
"sizeOriginal": 17890,
|
||||
"chunksTotal": 17890,
|
||||
"chunksUploaded": 17890
|
||||
"chunksUploaded": 17890,
|
||||
"encryption": true,
|
||||
"compression": "gzip"
|
||||
}
|
||||
},
|
||||
"team": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -140,7 +140,8 @@
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
@@ -224,12 +225,14 @@
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"description": "User password. Must be at least 8 chars.",
|
||||
"x-example": "password"
|
||||
"x-example": "password",
|
||||
"format": "password"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -439,7 +442,8 @@
|
||||
"duration": {
|
||||
"type": "integer",
|
||||
"description": "Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.",
|
||||
"x-example": 0
|
||||
"x-example": 0,
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -551,7 +555,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMFA",
|
||||
"group": "mfa",
|
||||
"weight": 288,
|
||||
"weight": 253,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa.md",
|
||||
@@ -623,7 +627,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 290,
|
||||
"weight": 255,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-authenticator.md",
|
||||
@@ -747,7 +751,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 291,
|
||||
"weight": 256,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-authenticator.md",
|
||||
@@ -887,7 +891,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 292,
|
||||
"weight": 257,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/delete-mfa-authenticator.md",
|
||||
@@ -1011,7 +1015,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaChallenge",
|
||||
"group": "mfa",
|
||||
"weight": 296,
|
||||
"weight": 261,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-challenge.md",
|
||||
@@ -1145,7 +1149,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaChallenge",
|
||||
"group": "mfa",
|
||||
"weight": 297,
|
||||
"weight": 262,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-challenge.md",
|
||||
@@ -1283,7 +1287,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listMfaFactors",
|
||||
"group": "mfa",
|
||||
"weight": 289,
|
||||
"weight": 254,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/list-mfa-factors.md",
|
||||
@@ -1384,7 +1388,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 295,
|
||||
"weight": 260,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/get-mfa-recovery-codes.md",
|
||||
@@ -1483,7 +1487,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 293,
|
||||
"weight": 258,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-recovery-codes.md",
|
||||
@@ -1582,7 +1586,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 294,
|
||||
"weight": 259,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-recovery-codes.md",
|
||||
@@ -1796,7 +1800,8 @@
|
||||
"oldPassword": {
|
||||
"type": "string",
|
||||
"description": "Current user password. Must be at least 8 chars.",
|
||||
"x-example": "password"
|
||||
"x-example": "password",
|
||||
"format": "password"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1868,12 +1873,14 @@
|
||||
"phone": {
|
||||
"type": "string",
|
||||
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
|
||||
"x-example": "+12065550100"
|
||||
"x-example": "+12065550100",
|
||||
"format": "phone"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"description": "User password. Must be at least 8 chars.",
|
||||
"x-example": "password"
|
||||
"x-example": "password",
|
||||
"format": "password"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -2072,12 +2079,14 @@
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"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"
|
||||
"x-example": "https:\/\/example.com",
|
||||
"format": "url"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -2382,12 +2391,14 @@
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"description": "User password. Must be at least 8 chars.",
|
||||
"x-example": "password"
|
||||
"x-example": "password",
|
||||
"format": "password"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -2573,9 +2584,7 @@
|
||||
"yammer",
|
||||
"yandex",
|
||||
"zoho",
|
||||
"zoom",
|
||||
"mock",
|
||||
"mock-unverified"
|
||||
"zoom"
|
||||
],
|
||||
"x-enum-name": "OAuthProvider",
|
||||
"x-enum-keys": []
|
||||
@@ -3304,7 +3313,8 @@
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"phrase": {
|
||||
"type": "boolean",
|
||||
@@ -3390,12 +3400,14 @@
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"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"
|
||||
"x-example": "https:\/\/example.com",
|
||||
"format": "url"
|
||||
},
|
||||
"phrase": {
|
||||
"type": "boolean",
|
||||
@@ -3504,9 +3516,7 @@
|
||||
"yammer",
|
||||
"yandex",
|
||||
"zoho",
|
||||
"zoom",
|
||||
"mock",
|
||||
"mock-unverified"
|
||||
"zoom"
|
||||
],
|
||||
"x-enum-name": "OAuthProvider",
|
||||
"x-enum-keys": []
|
||||
@@ -3621,7 +3631,8 @@
|
||||
"phone": {
|
||||
"type": "string",
|
||||
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
|
||||
"x-example": "+12065550100"
|
||||
"x-example": "+12065550100",
|
||||
"format": "phone"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -3746,7 +3757,8 @@
|
||||
"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"
|
||||
"x-example": "https:\/\/example.com",
|
||||
"format": "url"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -4039,7 +4051,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getBrowser",
|
||||
"group": null,
|
||||
"weight": 50,
|
||||
"weight": 264,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-browser.md",
|
||||
@@ -4167,7 +4179,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getCreditCard",
|
||||
"group": null,
|
||||
"weight": 49,
|
||||
"weight": 263,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-credit-card.md",
|
||||
@@ -4301,7 +4313,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFavicon",
|
||||
"group": null,
|
||||
"weight": 53,
|
||||
"weight": 267,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-favicon.md",
|
||||
@@ -4361,7 +4373,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFlag",
|
||||
"group": null,
|
||||
"weight": 51,
|
||||
"weight": 265,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-flag.md",
|
||||
@@ -4851,7 +4863,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getImage",
|
||||
"group": null,
|
||||
"weight": 52,
|
||||
"weight": 266,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-image.md",
|
||||
@@ -4935,7 +4947,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getInitials",
|
||||
"group": null,
|
||||
"weight": 55,
|
||||
"weight": 269,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-initials.md",
|
||||
@@ -5029,7 +5041,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getQR",
|
||||
"group": null,
|
||||
"weight": 54,
|
||||
"weight": 268,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-qr.md",
|
||||
@@ -5123,7 +5135,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getScreenshot",
|
||||
"group": null,
|
||||
"weight": 56,
|
||||
"weight": 270,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-screenshot.md",
|
||||
@@ -5876,7 +5888,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 362,
|
||||
"weight": 338,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-transactions.md",
|
||||
@@ -5943,7 +5955,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 358,
|
||||
"weight": 334,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-transaction.md",
|
||||
@@ -5980,7 +5992,8 @@
|
||||
"ttl": {
|
||||
"type": "integer",
|
||||
"description": "Seconds before the transaction expires.",
|
||||
"x-example": 60
|
||||
"x-example": 60,
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6013,7 +6026,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 359,
|
||||
"weight": 335,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-transaction.md",
|
||||
@@ -6077,7 +6090,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 360,
|
||||
"weight": 336,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-transaction.md",
|
||||
@@ -6155,7 +6168,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 361,
|
||||
"weight": 337,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-transaction.md",
|
||||
@@ -6221,7 +6234,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 363,
|
||||
"weight": 339,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-operations.md",
|
||||
@@ -6306,7 +6319,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listDocuments",
|
||||
"group": "documents",
|
||||
"weight": 321,
|
||||
"weight": 297,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-documents.md",
|
||||
@@ -6418,7 +6431,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createDocument",
|
||||
"group": "documents",
|
||||
"weight": 313,
|
||||
"weight": 289,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-document.md",
|
||||
@@ -6579,7 +6592,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getDocument",
|
||||
"group": "documents",
|
||||
"weight": 314,
|
||||
"weight": 290,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-document.md",
|
||||
@@ -6690,7 +6703,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "upsertDocument",
|
||||
"group": "documents",
|
||||
"weight": 317,
|
||||
"weight": 293,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/upsert-document.md",
|
||||
@@ -6845,7 +6858,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateDocument",
|
||||
"group": "documents",
|
||||
"weight": 315,
|
||||
"weight": 291,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-document.md",
|
||||
@@ -6957,7 +6970,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteDocument",
|
||||
"group": "documents",
|
||||
"weight": 319,
|
||||
"weight": 295,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-document.md",
|
||||
@@ -7064,7 +7077,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "decrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 324,
|
||||
"weight": 300,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/decrement-document-attribute.md",
|
||||
@@ -7146,12 +7159,14 @@
|
||||
"value": {
|
||||
"type": "number",
|
||||
"description": "Value to increment the attribute by. The value must be a number.",
|
||||
"x-example": null
|
||||
"x-example": null,
|
||||
"format": "float"
|
||||
},
|
||||
"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": {
|
||||
@@ -7191,7 +7206,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "incrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 323,
|
||||
"weight": 299,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/increment-document-attribute.md",
|
||||
@@ -7273,12 +7288,14 @@
|
||||
"value": {
|
||||
"type": "number",
|
||||
"description": "Value to increment the attribute by. The value must be a number.",
|
||||
"x-example": null
|
||||
"x-example": null,
|
||||
"format": "float"
|
||||
},
|
||||
"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": {
|
||||
@@ -7318,7 +7335,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listExecutions",
|
||||
"group": "executions",
|
||||
"weight": 455,
|
||||
"weight": 431,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/list-executions.md",
|
||||
@@ -7405,7 +7422,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createExecution",
|
||||
"group": "executions",
|
||||
"weight": 453,
|
||||
"weight": 429,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/create-execution.md",
|
||||
@@ -7523,7 +7540,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getExecution",
|
||||
"group": "executions",
|
||||
"weight": 454,
|
||||
"weight": 430,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/get-execution.md",
|
||||
@@ -7598,7 +7615,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "query",
|
||||
"group": "graphql",
|
||||
"weight": 225,
|
||||
"weight": 190,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"demo": "graphql\/query.md",
|
||||
@@ -7652,7 +7669,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "mutation",
|
||||
"group": "graphql",
|
||||
"weight": 224,
|
||||
"weight": 189,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"demo": "graphql\/mutation.md",
|
||||
@@ -7706,7 +7723,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"group": null,
|
||||
"weight": 60,
|
||||
"weight": 49,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/get.md",
|
||||
@@ -7760,7 +7777,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCodes",
|
||||
"group": null,
|
||||
"weight": 61,
|
||||
"weight": 50,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-codes.md",
|
||||
@@ -7814,7 +7831,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listContinents",
|
||||
"group": null,
|
||||
"weight": 65,
|
||||
"weight": 54,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-continents.md",
|
||||
@@ -7868,7 +7885,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCountries",
|
||||
"group": null,
|
||||
"weight": 62,
|
||||
"weight": 51,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-countries.md",
|
||||
@@ -7922,7 +7939,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCountriesEU",
|
||||
"group": null,
|
||||
"weight": 63,
|
||||
"weight": 52,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-countries-eu.md",
|
||||
@@ -7976,7 +7993,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCountriesPhones",
|
||||
"group": null,
|
||||
"weight": 64,
|
||||
"weight": 53,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-countries-phones.md",
|
||||
@@ -8030,7 +8047,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCurrencies",
|
||||
"group": null,
|
||||
"weight": 66,
|
||||
"weight": 55,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-currencies.md",
|
||||
@@ -8084,7 +8101,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listLanguages",
|
||||
"group": null,
|
||||
"weight": 67,
|
||||
"weight": 56,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-languages.md",
|
||||
@@ -8138,7 +8155,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 272,
|
||||
"weight": 237,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/create-subscriber.md",
|
||||
@@ -8222,7 +8239,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 276,
|
||||
"weight": 241,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/delete-subscriber.md",
|
||||
@@ -8298,7 +8315,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listFiles",
|
||||
"group": "files",
|
||||
"weight": 526,
|
||||
"weight": 527,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "storage\/list-files.md",
|
||||
@@ -8397,7 +8414,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createFile",
|
||||
"group": "files",
|
||||
"weight": 524,
|
||||
"weight": 525,
|
||||
"cookies": false,
|
||||
"type": "upload",
|
||||
"demo": "storage\/create-file.md",
|
||||
@@ -8452,7 +8469,8 @@
|
||||
"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
|
||||
"x-example": null,
|
||||
"format": "binary"
|
||||
},
|
||||
"permissions": {
|
||||
"type": "array",
|
||||
@@ -8498,7 +8516,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFile",
|
||||
"group": "files",
|
||||
"weight": 525,
|
||||
"weight": 526,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "storage\/get-file.md",
|
||||
@@ -8572,7 +8590,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateFile",
|
||||
"group": "files",
|
||||
"weight": 527,
|
||||
"weight": 528,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "storage\/update-file.md",
|
||||
@@ -8664,7 +8682,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteFile",
|
||||
"group": "files",
|
||||
"weight": 528,
|
||||
"weight": 529,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "storage\/delete-file.md",
|
||||
@@ -8733,7 +8751,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFileDownload",
|
||||
"group": "files",
|
||||
"weight": 530,
|
||||
"weight": 531,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "storage\/get-file-download.md",
|
||||
@@ -8813,7 +8831,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFilePreview",
|
||||
"group": "files",
|
||||
"weight": 529,
|
||||
"weight": 530,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "storage\/get-file-preview.md",
|
||||
@@ -9043,7 +9061,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFileView",
|
||||
"group": "files",
|
||||
"weight": 531,
|
||||
"weight": 532,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "storage\/get-file-view.md",
|
||||
@@ -9130,7 +9148,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 427,
|
||||
"weight": 403,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-transactions.md",
|
||||
@@ -9200,7 +9218,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 423,
|
||||
"weight": 399,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-transaction.md",
|
||||
@@ -9240,7 +9258,8 @@
|
||||
"ttl": {
|
||||
"type": "integer",
|
||||
"description": "Seconds before the transaction expires.",
|
||||
"x-example": 60
|
||||
"x-example": 60,
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9273,7 +9292,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 424,
|
||||
"weight": 400,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-transaction.md",
|
||||
@@ -9340,7 +9359,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 425,
|
||||
"weight": 401,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-transaction.md",
|
||||
@@ -9421,7 +9440,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 426,
|
||||
"weight": 402,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-transaction.md",
|
||||
@@ -9490,7 +9509,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 428,
|
||||
"weight": 404,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-operations.md",
|
||||
@@ -9578,7 +9597,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listRows",
|
||||
"group": "rows",
|
||||
"weight": 419,
|
||||
"weight": 395,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-rows.md",
|
||||
@@ -9689,7 +9708,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createRow",
|
||||
"group": "rows",
|
||||
"weight": 411,
|
||||
"weight": 387,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-row.md",
|
||||
@@ -9845,7 +9864,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getRow",
|
||||
"group": "rows",
|
||||
"weight": 412,
|
||||
"weight": 388,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-row.md",
|
||||
@@ -9955,7 +9974,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "upsertRow",
|
||||
"group": "rows",
|
||||
"weight": 415,
|
||||
"weight": 391,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/upsert-row.md",
|
||||
@@ -10105,7 +10124,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateRow",
|
||||
"group": "rows",
|
||||
"weight": 413,
|
||||
"weight": 389,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-row.md",
|
||||
@@ -10216,7 +10235,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteRow",
|
||||
"group": "rows",
|
||||
"weight": 417,
|
||||
"weight": 393,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-row.md",
|
||||
@@ -10322,7 +10341,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "decrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 422,
|
||||
"weight": 398,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/decrement-row-column.md",
|
||||
@@ -10403,12 +10422,14 @@
|
||||
"value": {
|
||||
"type": "number",
|
||||
"description": "Value to increment the column by. The value must be a number.",
|
||||
"x-example": null
|
||||
"x-example": null,
|
||||
"format": "float"
|
||||
},
|
||||
"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": {
|
||||
@@ -10448,7 +10469,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "incrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 421,
|
||||
"weight": 397,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/increment-row-column.md",
|
||||
@@ -10529,12 +10550,14 @@
|
||||
"value": {
|
||||
"type": "number",
|
||||
"description": "Value to increment the column by. The value must be a number.",
|
||||
"x-example": null
|
||||
"x-example": null,
|
||||
"format": "float"
|
||||
},
|
||||
"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": {
|
||||
@@ -10574,7 +10597,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "list",
|
||||
"group": "teams",
|
||||
"weight": 145,
|
||||
"weight": 110,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/list.md",
|
||||
@@ -10663,7 +10686,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "create",
|
||||
"group": "teams",
|
||||
"weight": 144,
|
||||
"weight": 109,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/create.md",
|
||||
@@ -10750,7 +10773,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"group": "teams",
|
||||
"weight": 146,
|
||||
"weight": 111,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/get.md",
|
||||
@@ -10814,7 +10837,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateName",
|
||||
"group": "teams",
|
||||
"weight": 148,
|
||||
"weight": 113,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/update-name.md",
|
||||
@@ -10890,7 +10913,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "delete",
|
||||
"group": "teams",
|
||||
"weight": 150,
|
||||
"weight": 115,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/delete.md",
|
||||
@@ -10956,7 +10979,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listMemberships",
|
||||
"group": "memberships",
|
||||
"weight": 152,
|
||||
"weight": 117,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/list-memberships.md",
|
||||
@@ -11055,7 +11078,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMembership",
|
||||
"group": "memberships",
|
||||
"weight": 151,
|
||||
"weight": 116,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/create-membership.md",
|
||||
@@ -11104,7 +11127,8 @@
|
||||
"email": {
|
||||
"type": "string",
|
||||
"description": "Email of the new team member.",
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"userId": {
|
||||
"type": "string",
|
||||
@@ -11114,7 +11138,8 @@
|
||||
"phone": {
|
||||
"type": "string",
|
||||
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
|
||||
"x-example": "+12065550100"
|
||||
"x-example": "+12065550100",
|
||||
"format": "phone"
|
||||
},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
@@ -11134,7 +11159,8 @@
|
||||
"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"
|
||||
"x-example": "https:\/\/example.com",
|
||||
"format": "url"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
@@ -11175,7 +11201,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getMembership",
|
||||
"group": "memberships",
|
||||
"weight": 153,
|
||||
"weight": 118,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/get-membership.md",
|
||||
@@ -11249,7 +11275,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMembership",
|
||||
"group": "memberships",
|
||||
"weight": 154,
|
||||
"weight": 119,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/update-membership.md",
|
||||
@@ -11345,7 +11371,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteMembership",
|
||||
"group": "memberships",
|
||||
"weight": 156,
|
||||
"weight": 121,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/delete-membership.md",
|
||||
@@ -11421,7 +11447,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMembershipStatus",
|
||||
"group": "memberships",
|
||||
"weight": 155,
|
||||
"weight": 120,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/update-membership-status.md",
|
||||
@@ -11521,7 +11547,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getPrefs",
|
||||
"group": "teams",
|
||||
"weight": 147,
|
||||
"weight": 112,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/get-prefs.md",
|
||||
@@ -11584,7 +11610,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updatePrefs",
|
||||
"group": "teams",
|
||||
"weight": 149,
|
||||
"weight": 114,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/update-prefs.md",
|
||||
@@ -13428,6 +13454,16 @@
|
||||
"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": [
|
||||
@@ -13441,7 +13477,9 @@
|
||||
"mimeType",
|
||||
"sizeOriginal",
|
||||
"chunksTotal",
|
||||
"chunksUploaded"
|
||||
"chunksUploaded",
|
||||
"encryption",
|
||||
"compression"
|
||||
],
|
||||
"example": {
|
||||
"$id": "5e5ea5c16897e",
|
||||
@@ -13456,7 +13494,9 @@
|
||||
"mimeType": "image\/png",
|
||||
"sizeOriginal": 17890,
|
||||
"chunksTotal": 17890,
|
||||
"chunksUploaded": 17890
|
||||
"chunksUploaded": 17890,
|
||||
"encryption": true,
|
||||
"compression": "gzip"
|
||||
}
|
||||
},
|
||||
"team": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"version": "1.8.0",
|
||||
"version": "1.8.1",
|
||||
"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,7 +191,8 @@
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"default": null,
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
@@ -280,13 +281,15 @@
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"default": null,
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"description": "User password. Must be at least 8 chars.",
|
||||
"default": null,
|
||||
"x-example": "password"
|
||||
"x-example": "password",
|
||||
"format": "password"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -498,7 +501,8 @@
|
||||
"type": "integer",
|
||||
"description": "Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.",
|
||||
"default": 900,
|
||||
"x-example": 0
|
||||
"x-example": 0,
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -608,7 +612,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMFA",
|
||||
"group": "mfa",
|
||||
"weight": 288,
|
||||
"weight": 253,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa.md",
|
||||
@@ -683,7 +687,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 290,
|
||||
"weight": 255,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-authenticator.md",
|
||||
@@ -807,7 +811,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 291,
|
||||
"weight": 256,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-authenticator.md",
|
||||
@@ -948,7 +952,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 292,
|
||||
"weight": 257,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/delete-mfa-authenticator.md",
|
||||
@@ -1072,7 +1076,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaChallenge",
|
||||
"group": "mfa",
|
||||
"weight": 296,
|
||||
"weight": 261,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-challenge.md",
|
||||
@@ -1209,7 +1213,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaChallenge",
|
||||
"group": "mfa",
|
||||
"weight": 297,
|
||||
"weight": 262,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-challenge.md",
|
||||
@@ -1349,7 +1353,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listMfaFactors",
|
||||
"group": "mfa",
|
||||
"weight": 289,
|
||||
"weight": 254,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/list-mfa-factors.md",
|
||||
@@ -1450,7 +1454,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 295,
|
||||
"weight": 260,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/get-mfa-recovery-codes.md",
|
||||
@@ -1551,7 +1555,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 293,
|
||||
"weight": 258,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-recovery-codes.md",
|
||||
@@ -1652,7 +1656,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 294,
|
||||
"weight": 259,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-recovery-codes.md",
|
||||
@@ -1874,7 +1878,8 @@
|
||||
"type": "string",
|
||||
"description": "Current user password. Must be at least 8 chars.",
|
||||
"default": "",
|
||||
"x-example": "password"
|
||||
"x-example": "password",
|
||||
"format": "password"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1949,13 +1954,15 @@
|
||||
"type": "string",
|
||||
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
|
||||
"default": null,
|
||||
"x-example": "+12065550100"
|
||||
"x-example": "+12065550100",
|
||||
"format": "phone"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"description": "User password. Must be at least 8 chars.",
|
||||
"default": null,
|
||||
"x-example": "password"
|
||||
"x-example": "password",
|
||||
"format": "password"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -2160,13 +2167,15 @@
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"default": null,
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"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"
|
||||
"x-example": "https:\/\/example.com",
|
||||
"format": "url"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -2485,13 +2494,15 @@
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"default": null,
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"description": "User password. Must be at least 8 chars.",
|
||||
"default": null,
|
||||
"x-example": "password"
|
||||
"x-example": "password",
|
||||
"format": "password"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -2683,9 +2694,7 @@
|
||||
"yammer",
|
||||
"yandex",
|
||||
"zoho",
|
||||
"zoom",
|
||||
"mock",
|
||||
"mock-unverified"
|
||||
"zoom"
|
||||
],
|
||||
"x-enum-name": "OAuthProvider",
|
||||
"x-enum-keys": [],
|
||||
@@ -3429,7 +3438,8 @@
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"default": null,
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"phrase": {
|
||||
"type": "boolean",
|
||||
@@ -3520,13 +3530,15 @@
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"default": null,
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"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"
|
||||
"x-example": "https:\/\/example.com",
|
||||
"format": "url"
|
||||
},
|
||||
"phrase": {
|
||||
"type": "boolean",
|
||||
@@ -3638,9 +3650,7 @@
|
||||
"yammer",
|
||||
"yandex",
|
||||
"zoho",
|
||||
"zoom",
|
||||
"mock",
|
||||
"mock-unverified"
|
||||
"zoom"
|
||||
],
|
||||
"x-enum-name": "OAuthProvider",
|
||||
"x-enum-keys": [],
|
||||
@@ -3754,7 +3764,8 @@
|
||||
"type": "string",
|
||||
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
|
||||
"default": null,
|
||||
"x-example": "+12065550100"
|
||||
"x-example": "+12065550100",
|
||||
"format": "phone"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -3882,7 +3893,8 @@
|
||||
"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"
|
||||
"x-example": "https:\/\/example.com",
|
||||
"format": "url"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -4191,7 +4203,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getBrowser",
|
||||
"group": null,
|
||||
"weight": 50,
|
||||
"weight": 264,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-browser.md",
|
||||
@@ -4317,7 +4329,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getCreditCard",
|
||||
"group": null,
|
||||
"weight": 49,
|
||||
"weight": 263,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-credit-card.md",
|
||||
@@ -4449,7 +4461,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFavicon",
|
||||
"group": null,
|
||||
"weight": 53,
|
||||
"weight": 267,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-favicon.md",
|
||||
@@ -4513,7 +4525,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFlag",
|
||||
"group": null,
|
||||
"weight": 51,
|
||||
"weight": 265,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-flag.md",
|
||||
@@ -5001,7 +5013,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getImage",
|
||||
"group": null,
|
||||
"weight": 52,
|
||||
"weight": 266,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-image.md",
|
||||
@@ -5085,7 +5097,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getInitials",
|
||||
"group": null,
|
||||
"weight": 55,
|
||||
"weight": 269,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-initials.md",
|
||||
@@ -5177,7 +5189,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getQR",
|
||||
"group": null,
|
||||
"weight": 54,
|
||||
"weight": 268,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-qr.md",
|
||||
@@ -5269,7 +5281,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getScreenshot",
|
||||
"group": null,
|
||||
"weight": 56,
|
||||
"weight": 270,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-screenshot.md",
|
||||
@@ -5982,7 +5994,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 362,
|
||||
"weight": 338,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-transactions.md",
|
||||
@@ -6049,7 +6061,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 358,
|
||||
"weight": 334,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-transaction.md",
|
||||
@@ -6087,7 +6099,8 @@
|
||||
"type": "integer",
|
||||
"description": "Seconds before the transaction expires.",
|
||||
"default": 300,
|
||||
"x-example": 60
|
||||
"x-example": 60,
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6119,7 +6132,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 359,
|
||||
"weight": 335,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-transaction.md",
|
||||
@@ -6182,7 +6195,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 360,
|
||||
"weight": 336,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-transaction.md",
|
||||
@@ -6261,7 +6274,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 361,
|
||||
"weight": 337,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-transaction.md",
|
||||
@@ -6326,7 +6339,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 363,
|
||||
"weight": 339,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-operations.md",
|
||||
@@ -6407,7 +6420,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listDocuments",
|
||||
"group": "documents",
|
||||
"weight": 321,
|
||||
"weight": 297,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-documents.md",
|
||||
@@ -6511,7 +6524,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createDocument",
|
||||
"group": "documents",
|
||||
"weight": 313,
|
||||
"weight": 289,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-document.md",
|
||||
@@ -6670,7 +6683,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getDocument",
|
||||
"group": "documents",
|
||||
"weight": 314,
|
||||
"weight": 290,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-document.md",
|
||||
@@ -6773,7 +6786,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "upsertDocument",
|
||||
"group": "documents",
|
||||
"weight": 317,
|
||||
"weight": 293,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/upsert-document.md",
|
||||
@@ -6924,7 +6937,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateDocument",
|
||||
"group": "documents",
|
||||
"weight": 315,
|
||||
"weight": 291,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-document.md",
|
||||
@@ -7034,7 +7047,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteDocument",
|
||||
"group": "documents",
|
||||
"weight": 319,
|
||||
"weight": 295,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-document.md",
|
||||
@@ -7135,7 +7148,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "decrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 324,
|
||||
"weight": 300,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/decrement-document-attribute.md",
|
||||
@@ -7208,13 +7221,15 @@
|
||||
"type": "number",
|
||||
"description": "Value to increment the attribute by. The value must be a number.",
|
||||
"default": 1,
|
||||
"x-example": null
|
||||
"x-example": null,
|
||||
"format": "float"
|
||||
},
|
||||
"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": {
|
||||
@@ -7256,7 +7271,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "incrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 323,
|
||||
"weight": 299,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/increment-document-attribute.md",
|
||||
@@ -7329,13 +7344,15 @@
|
||||
"type": "number",
|
||||
"description": "Value to increment the attribute by. The value must be a number.",
|
||||
"default": 1,
|
||||
"x-example": null
|
||||
"x-example": null,
|
||||
"format": "float"
|
||||
},
|
||||
"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": {
|
||||
@@ -7375,7 +7392,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listExecutions",
|
||||
"group": "executions",
|
||||
"weight": 454,
|
||||
"weight": 431,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/list-executions.md",
|
||||
@@ -7458,7 +7475,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createExecution",
|
||||
"group": "executions",
|
||||
"weight": 452,
|
||||
"weight": 429,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/create-execution.md",
|
||||
@@ -7577,7 +7594,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getExecution",
|
||||
"group": "executions",
|
||||
"weight": 453,
|
||||
"weight": 430,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/get-execution.md",
|
||||
@@ -7649,7 +7666,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "query",
|
||||
"group": "graphql",
|
||||
"weight": 225,
|
||||
"weight": 190,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"demo": "graphql\/query.md",
|
||||
@@ -7724,7 +7741,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "mutation",
|
||||
"group": "graphql",
|
||||
"weight": 224,
|
||||
"weight": 189,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"demo": "graphql\/mutation.md",
|
||||
@@ -7797,7 +7814,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"group": null,
|
||||
"weight": 60,
|
||||
"weight": 49,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/get.md",
|
||||
@@ -7850,7 +7867,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCodes",
|
||||
"group": null,
|
||||
"weight": 61,
|
||||
"weight": 50,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-codes.md",
|
||||
@@ -7903,7 +7920,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listContinents",
|
||||
"group": null,
|
||||
"weight": 65,
|
||||
"weight": 54,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-continents.md",
|
||||
@@ -7956,7 +7973,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCountries",
|
||||
"group": null,
|
||||
"weight": 62,
|
||||
"weight": 51,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-countries.md",
|
||||
@@ -8009,7 +8026,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCountriesEU",
|
||||
"group": null,
|
||||
"weight": 63,
|
||||
"weight": 52,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-countries-eu.md",
|
||||
@@ -8062,7 +8079,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCountriesPhones",
|
||||
"group": null,
|
||||
"weight": 64,
|
||||
"weight": 53,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-countries-phones.md",
|
||||
@@ -8115,7 +8132,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCurrencies",
|
||||
"group": null,
|
||||
"weight": 66,
|
||||
"weight": 55,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-currencies.md",
|
||||
@@ -8168,7 +8185,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listLanguages",
|
||||
"group": null,
|
||||
"weight": 67,
|
||||
"weight": 56,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-languages.md",
|
||||
@@ -8223,7 +8240,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 272,
|
||||
"weight": 237,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/create-subscriber.md",
|
||||
@@ -8308,7 +8325,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 276,
|
||||
"weight": 241,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/delete-subscriber.md",
|
||||
@@ -8379,7 +8396,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listFiles",
|
||||
"group": "files",
|
||||
"weight": 525,
|
||||
"weight": 527,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "storage\/list-files.md",
|
||||
@@ -8472,7 +8489,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createFile",
|
||||
"group": "files",
|
||||
"weight": 523,
|
||||
"weight": 525,
|
||||
"cookies": false,
|
||||
"type": "upload",
|
||||
"demo": "storage\/create-file.md",
|
||||
@@ -8563,7 +8580,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFile",
|
||||
"group": "files",
|
||||
"weight": 524,
|
||||
"weight": 526,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "storage\/get-file.md",
|
||||
@@ -8634,7 +8651,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateFile",
|
||||
"group": "files",
|
||||
"weight": 526,
|
||||
"weight": 528,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "storage\/update-file.md",
|
||||
@@ -8725,7 +8742,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteFile",
|
||||
"group": "files",
|
||||
"weight": 527,
|
||||
"weight": 529,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "storage\/delete-file.md",
|
||||
@@ -8796,7 +8813,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFileDownload",
|
||||
"group": "files",
|
||||
"weight": 529,
|
||||
"weight": 531,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "storage\/get-file-download.md",
|
||||
@@ -8876,7 +8893,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFilePreview",
|
||||
"group": "files",
|
||||
"weight": 528,
|
||||
"weight": 530,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "storage\/get-file-preview.md",
|
||||
@@ -9084,7 +9101,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFileView",
|
||||
"group": "files",
|
||||
"weight": 530,
|
||||
"weight": 532,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "storage\/get-file-view.md",
|
||||
@@ -9164,7 +9181,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 427,
|
||||
"weight": 403,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-transactions.md",
|
||||
@@ -9234,7 +9251,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 423,
|
||||
"weight": 399,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-transaction.md",
|
||||
@@ -9275,7 +9292,8 @@
|
||||
"type": "integer",
|
||||
"description": "Seconds before the transaction expires.",
|
||||
"default": 300,
|
||||
"x-example": 60
|
||||
"x-example": 60,
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9307,7 +9325,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 424,
|
||||
"weight": 400,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-transaction.md",
|
||||
@@ -9373,7 +9391,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 425,
|
||||
"weight": 401,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-transaction.md",
|
||||
@@ -9455,7 +9473,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 426,
|
||||
"weight": 402,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-transaction.md",
|
||||
@@ -9523,7 +9541,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 428,
|
||||
"weight": 404,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-operations.md",
|
||||
@@ -9607,7 +9625,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listRows",
|
||||
"group": "rows",
|
||||
"weight": 419,
|
||||
"weight": 395,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-rows.md",
|
||||
@@ -9710,7 +9728,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createRow",
|
||||
"group": "rows",
|
||||
"weight": 411,
|
||||
"weight": 387,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-row.md",
|
||||
@@ -9864,7 +9882,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getRow",
|
||||
"group": "rows",
|
||||
"weight": 412,
|
||||
"weight": 388,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-row.md",
|
||||
@@ -9966,7 +9984,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "upsertRow",
|
||||
"group": "rows",
|
||||
"weight": 415,
|
||||
"weight": 391,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/upsert-row.md",
|
||||
@@ -10112,7 +10130,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateRow",
|
||||
"group": "rows",
|
||||
"weight": 413,
|
||||
"weight": 389,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-row.md",
|
||||
@@ -10221,7 +10239,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteRow",
|
||||
"group": "rows",
|
||||
"weight": 417,
|
||||
"weight": 393,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-row.md",
|
||||
@@ -10321,7 +10339,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "decrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 422,
|
||||
"weight": 398,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/decrement-row-column.md",
|
||||
@@ -10393,13 +10411,15 @@
|
||||
"type": "number",
|
||||
"description": "Value to increment the column by. The value must be a number.",
|
||||
"default": 1,
|
||||
"x-example": null
|
||||
"x-example": null,
|
||||
"format": "float"
|
||||
},
|
||||
"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": {
|
||||
@@ -10441,7 +10461,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "incrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 421,
|
||||
"weight": 397,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/increment-row-column.md",
|
||||
@@ -10513,13 +10533,15 @@
|
||||
"type": "number",
|
||||
"description": "Value to increment the column by. The value must be a number.",
|
||||
"default": 1,
|
||||
"x-example": null
|
||||
"x-example": null,
|
||||
"format": "float"
|
||||
},
|
||||
"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": {
|
||||
@@ -10559,7 +10581,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "list",
|
||||
"group": "teams",
|
||||
"weight": 145,
|
||||
"weight": 110,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/list.md",
|
||||
@@ -10644,7 +10666,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "create",
|
||||
"group": "teams",
|
||||
"weight": 144,
|
||||
"weight": 109,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/create.md",
|
||||
@@ -10735,7 +10757,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"group": "teams",
|
||||
"weight": 146,
|
||||
"weight": 111,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/get.md",
|
||||
@@ -10798,7 +10820,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateName",
|
||||
"group": "teams",
|
||||
"weight": 148,
|
||||
"weight": 113,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/update-name.md",
|
||||
@@ -10874,7 +10896,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "delete",
|
||||
"group": "teams",
|
||||
"weight": 150,
|
||||
"weight": 115,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/delete.md",
|
||||
@@ -10937,7 +10959,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listMemberships",
|
||||
"group": "memberships",
|
||||
"weight": 152,
|
||||
"weight": 117,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/list-memberships.md",
|
||||
@@ -11030,7 +11052,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMembership",
|
||||
"group": "memberships",
|
||||
"weight": 151,
|
||||
"weight": 116,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/create-membership.md",
|
||||
@@ -11076,7 +11098,8 @@
|
||||
"type": "string",
|
||||
"description": "Email of the new team member.",
|
||||
"default": "",
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"userId": {
|
||||
"type": "string",
|
||||
@@ -11088,7 +11111,8 @@
|
||||
"type": "string",
|
||||
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
|
||||
"default": "",
|
||||
"x-example": "+12065550100"
|
||||
"x-example": "+12065550100",
|
||||
"format": "phone"
|
||||
},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
@@ -11110,7 +11134,8 @@
|
||||
"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"
|
||||
"x-example": "https:\/\/example.com",
|
||||
"format": "url"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
@@ -11151,7 +11176,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getMembership",
|
||||
"group": "memberships",
|
||||
"weight": 153,
|
||||
"weight": 118,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/get-membership.md",
|
||||
@@ -11222,7 +11247,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMembership",
|
||||
"group": "memberships",
|
||||
"weight": 154,
|
||||
"weight": 119,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/update-membership.md",
|
||||
@@ -11316,7 +11341,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteMembership",
|
||||
"group": "memberships",
|
||||
"weight": 156,
|
||||
"weight": 121,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/delete-membership.md",
|
||||
@@ -11389,7 +11414,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMembershipStatus",
|
||||
"group": "memberships",
|
||||
"weight": 155,
|
||||
"weight": 120,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/update-membership-status.md",
|
||||
@@ -11485,7 +11510,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getPrefs",
|
||||
"group": "teams",
|
||||
"weight": 147,
|
||||
"weight": 112,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/get-prefs.md",
|
||||
@@ -11548,7 +11573,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updatePrefs",
|
||||
"group": "teams",
|
||||
"weight": 149,
|
||||
"weight": 114,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/update-prefs.md",
|
||||
@@ -13368,6 +13393,16 @@
|
||||
"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": [
|
||||
@@ -13381,7 +13416,9 @@
|
||||
"mimeType",
|
||||
"sizeOriginal",
|
||||
"chunksTotal",
|
||||
"chunksUploaded"
|
||||
"chunksUploaded",
|
||||
"encryption",
|
||||
"compression"
|
||||
],
|
||||
"example": {
|
||||
"$id": "5e5ea5c16897e",
|
||||
@@ -13396,7 +13433,9 @@
|
||||
"mimeType": "image\/png",
|
||||
"sizeOriginal": 17890,
|
||||
"chunksTotal": 17890,
|
||||
"chunksUploaded": 17890
|
||||
"chunksUploaded": 17890,
|
||||
"encryption": true,
|
||||
"compression": "gzip"
|
||||
}
|
||||
},
|
||||
"team": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -191,7 +191,8 @@
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"default": null,
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
@@ -280,13 +281,15 @@
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"default": null,
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"description": "User password. Must be at least 8 chars.",
|
||||
"default": null,
|
||||
"x-example": "password"
|
||||
"x-example": "password",
|
||||
"format": "password"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -498,7 +501,8 @@
|
||||
"type": "integer",
|
||||
"description": "Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.",
|
||||
"default": 900,
|
||||
"x-example": 0
|
||||
"x-example": 0,
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -608,7 +612,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMFA",
|
||||
"group": "mfa",
|
||||
"weight": 288,
|
||||
"weight": 253,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa.md",
|
||||
@@ -683,7 +687,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 290,
|
||||
"weight": 255,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-authenticator.md",
|
||||
@@ -807,7 +811,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 291,
|
||||
"weight": 256,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-authenticator.md",
|
||||
@@ -948,7 +952,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteMfaAuthenticator",
|
||||
"group": "mfa",
|
||||
"weight": 292,
|
||||
"weight": 257,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/delete-mfa-authenticator.md",
|
||||
@@ -1072,7 +1076,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaChallenge",
|
||||
"group": "mfa",
|
||||
"weight": 296,
|
||||
"weight": 261,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-challenge.md",
|
||||
@@ -1209,7 +1213,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaChallenge",
|
||||
"group": "mfa",
|
||||
"weight": 297,
|
||||
"weight": 262,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-challenge.md",
|
||||
@@ -1349,7 +1353,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listMfaFactors",
|
||||
"group": "mfa",
|
||||
"weight": 289,
|
||||
"weight": 254,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/list-mfa-factors.md",
|
||||
@@ -1450,7 +1454,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 295,
|
||||
"weight": 260,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/get-mfa-recovery-codes.md",
|
||||
@@ -1551,7 +1555,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 293,
|
||||
"weight": 258,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/create-mfa-recovery-codes.md",
|
||||
@@ -1652,7 +1656,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMfaRecoveryCodes",
|
||||
"group": "mfa",
|
||||
"weight": 294,
|
||||
"weight": 259,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "account\/update-mfa-recovery-codes.md",
|
||||
@@ -1874,7 +1878,8 @@
|
||||
"type": "string",
|
||||
"description": "Current user password. Must be at least 8 chars.",
|
||||
"default": "",
|
||||
"x-example": "password"
|
||||
"x-example": "password",
|
||||
"format": "password"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -1949,13 +1954,15 @@
|
||||
"type": "string",
|
||||
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
|
||||
"default": null,
|
||||
"x-example": "+12065550100"
|
||||
"x-example": "+12065550100",
|
||||
"format": "phone"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"description": "User password. Must be at least 8 chars.",
|
||||
"default": null,
|
||||
"x-example": "password"
|
||||
"x-example": "password",
|
||||
"format": "password"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -2160,13 +2167,15 @@
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"default": null,
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"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"
|
||||
"x-example": "https:\/\/example.com",
|
||||
"format": "url"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -2485,13 +2494,15 @@
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"default": null,
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"description": "User password. Must be at least 8 chars.",
|
||||
"default": null,
|
||||
"x-example": "password"
|
||||
"x-example": "password",
|
||||
"format": "password"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -2683,9 +2694,7 @@
|
||||
"yammer",
|
||||
"yandex",
|
||||
"zoho",
|
||||
"zoom",
|
||||
"mock",
|
||||
"mock-unverified"
|
||||
"zoom"
|
||||
],
|
||||
"x-enum-name": "OAuthProvider",
|
||||
"x-enum-keys": [],
|
||||
@@ -3429,7 +3438,8 @@
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"default": null,
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"phrase": {
|
||||
"type": "boolean",
|
||||
@@ -3520,13 +3530,15 @@
|
||||
"type": "string",
|
||||
"description": "User email.",
|
||||
"default": null,
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"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"
|
||||
"x-example": "https:\/\/example.com",
|
||||
"format": "url"
|
||||
},
|
||||
"phrase": {
|
||||
"type": "boolean",
|
||||
@@ -3638,9 +3650,7 @@
|
||||
"yammer",
|
||||
"yandex",
|
||||
"zoho",
|
||||
"zoom",
|
||||
"mock",
|
||||
"mock-unverified"
|
||||
"zoom"
|
||||
],
|
||||
"x-enum-name": "OAuthProvider",
|
||||
"x-enum-keys": [],
|
||||
@@ -3754,7 +3764,8 @@
|
||||
"type": "string",
|
||||
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
|
||||
"default": null,
|
||||
"x-example": "+12065550100"
|
||||
"x-example": "+12065550100",
|
||||
"format": "phone"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -3882,7 +3893,8 @@
|
||||
"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"
|
||||
"x-example": "https:\/\/example.com",
|
||||
"format": "url"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
@@ -4191,7 +4203,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getBrowser",
|
||||
"group": null,
|
||||
"weight": 50,
|
||||
"weight": 264,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-browser.md",
|
||||
@@ -4317,7 +4329,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getCreditCard",
|
||||
"group": null,
|
||||
"weight": 49,
|
||||
"weight": 263,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-credit-card.md",
|
||||
@@ -4449,7 +4461,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFavicon",
|
||||
"group": null,
|
||||
"weight": 53,
|
||||
"weight": 267,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-favicon.md",
|
||||
@@ -4513,7 +4525,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFlag",
|
||||
"group": null,
|
||||
"weight": 51,
|
||||
"weight": 265,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-flag.md",
|
||||
@@ -5001,7 +5013,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getImage",
|
||||
"group": null,
|
||||
"weight": 52,
|
||||
"weight": 266,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-image.md",
|
||||
@@ -5085,7 +5097,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getInitials",
|
||||
"group": null,
|
||||
"weight": 55,
|
||||
"weight": 269,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-initials.md",
|
||||
@@ -5177,7 +5189,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getQR",
|
||||
"group": null,
|
||||
"weight": 54,
|
||||
"weight": 268,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-qr.md",
|
||||
@@ -5269,7 +5281,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getScreenshot",
|
||||
"group": null,
|
||||
"weight": 56,
|
||||
"weight": 270,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "avatars\/get-screenshot.md",
|
||||
@@ -5982,7 +5994,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 362,
|
||||
"weight": 338,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-transactions.md",
|
||||
@@ -6049,7 +6061,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 358,
|
||||
"weight": 334,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-transaction.md",
|
||||
@@ -6087,7 +6099,8 @@
|
||||
"type": "integer",
|
||||
"description": "Seconds before the transaction expires.",
|
||||
"default": 300,
|
||||
"x-example": 60
|
||||
"x-example": 60,
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6119,7 +6132,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 359,
|
||||
"weight": 335,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-transaction.md",
|
||||
@@ -6182,7 +6195,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 360,
|
||||
"weight": 336,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-transaction.md",
|
||||
@@ -6261,7 +6274,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 361,
|
||||
"weight": 337,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-transaction.md",
|
||||
@@ -6326,7 +6339,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 363,
|
||||
"weight": 339,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-operations.md",
|
||||
@@ -6407,7 +6420,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listDocuments",
|
||||
"group": "documents",
|
||||
"weight": 321,
|
||||
"weight": 297,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/list-documents.md",
|
||||
@@ -6511,7 +6524,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createDocument",
|
||||
"group": "documents",
|
||||
"weight": 313,
|
||||
"weight": 289,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/create-document.md",
|
||||
@@ -6670,7 +6683,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getDocument",
|
||||
"group": "documents",
|
||||
"weight": 314,
|
||||
"weight": 290,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/get-document.md",
|
||||
@@ -6773,7 +6786,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "upsertDocument",
|
||||
"group": "documents",
|
||||
"weight": 317,
|
||||
"weight": 293,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/upsert-document.md",
|
||||
@@ -6924,7 +6937,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateDocument",
|
||||
"group": "documents",
|
||||
"weight": 315,
|
||||
"weight": 291,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/update-document.md",
|
||||
@@ -7034,7 +7047,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteDocument",
|
||||
"group": "documents",
|
||||
"weight": 319,
|
||||
"weight": 295,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/delete-document.md",
|
||||
@@ -7135,7 +7148,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "decrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 324,
|
||||
"weight": 300,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/decrement-document-attribute.md",
|
||||
@@ -7208,13 +7221,15 @@
|
||||
"type": "number",
|
||||
"description": "Value to increment the attribute by. The value must be a number.",
|
||||
"default": 1,
|
||||
"x-example": null
|
||||
"x-example": null,
|
||||
"format": "float"
|
||||
},
|
||||
"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": {
|
||||
@@ -7256,7 +7271,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "incrementDocumentAttribute",
|
||||
"group": "documents",
|
||||
"weight": 323,
|
||||
"weight": 299,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "databases\/increment-document-attribute.md",
|
||||
@@ -7329,13 +7344,15 @@
|
||||
"type": "number",
|
||||
"description": "Value to increment the attribute by. The value must be a number.",
|
||||
"default": 1,
|
||||
"x-example": null
|
||||
"x-example": null,
|
||||
"format": "float"
|
||||
},
|
||||
"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": {
|
||||
@@ -7375,7 +7392,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listExecutions",
|
||||
"group": "executions",
|
||||
"weight": 455,
|
||||
"weight": 431,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/list-executions.md",
|
||||
@@ -7458,7 +7475,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createExecution",
|
||||
"group": "executions",
|
||||
"weight": 453,
|
||||
"weight": 429,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/create-execution.md",
|
||||
@@ -7577,7 +7594,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getExecution",
|
||||
"group": "executions",
|
||||
"weight": 454,
|
||||
"weight": 430,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "functions\/get-execution.md",
|
||||
@@ -7649,7 +7666,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "query",
|
||||
"group": "graphql",
|
||||
"weight": 225,
|
||||
"weight": 190,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"demo": "graphql\/query.md",
|
||||
@@ -7724,7 +7741,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "mutation",
|
||||
"group": "graphql",
|
||||
"weight": 224,
|
||||
"weight": 189,
|
||||
"cookies": false,
|
||||
"type": "graphql",
|
||||
"demo": "graphql\/mutation.md",
|
||||
@@ -7797,7 +7814,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"group": null,
|
||||
"weight": 60,
|
||||
"weight": 49,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/get.md",
|
||||
@@ -7850,7 +7867,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCodes",
|
||||
"group": null,
|
||||
"weight": 61,
|
||||
"weight": 50,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-codes.md",
|
||||
@@ -7903,7 +7920,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listContinents",
|
||||
"group": null,
|
||||
"weight": 65,
|
||||
"weight": 54,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-continents.md",
|
||||
@@ -7956,7 +7973,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCountries",
|
||||
"group": null,
|
||||
"weight": 62,
|
||||
"weight": 51,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-countries.md",
|
||||
@@ -8009,7 +8026,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCountriesEU",
|
||||
"group": null,
|
||||
"weight": 63,
|
||||
"weight": 52,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-countries-eu.md",
|
||||
@@ -8062,7 +8079,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCountriesPhones",
|
||||
"group": null,
|
||||
"weight": 64,
|
||||
"weight": 53,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-countries-phones.md",
|
||||
@@ -8115,7 +8132,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listCurrencies",
|
||||
"group": null,
|
||||
"weight": 66,
|
||||
"weight": 55,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-currencies.md",
|
||||
@@ -8168,7 +8185,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listLanguages",
|
||||
"group": null,
|
||||
"weight": 67,
|
||||
"weight": 56,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "locale\/list-languages.md",
|
||||
@@ -8223,7 +8240,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 272,
|
||||
"weight": 237,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/create-subscriber.md",
|
||||
@@ -8308,7 +8325,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteSubscriber",
|
||||
"group": "subscribers",
|
||||
"weight": 276,
|
||||
"weight": 241,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "messaging\/delete-subscriber.md",
|
||||
@@ -8379,7 +8396,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listFiles",
|
||||
"group": "files",
|
||||
"weight": 526,
|
||||
"weight": 527,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "storage\/list-files.md",
|
||||
@@ -8472,7 +8489,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createFile",
|
||||
"group": "files",
|
||||
"weight": 524,
|
||||
"weight": 525,
|
||||
"cookies": false,
|
||||
"type": "upload",
|
||||
"demo": "storage\/create-file.md",
|
||||
@@ -8563,7 +8580,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFile",
|
||||
"group": "files",
|
||||
"weight": 525,
|
||||
"weight": 526,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "storage\/get-file.md",
|
||||
@@ -8634,7 +8651,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateFile",
|
||||
"group": "files",
|
||||
"weight": 527,
|
||||
"weight": 528,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "storage\/update-file.md",
|
||||
@@ -8725,7 +8742,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteFile",
|
||||
"group": "files",
|
||||
"weight": 528,
|
||||
"weight": 529,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "storage\/delete-file.md",
|
||||
@@ -8796,7 +8813,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFileDownload",
|
||||
"group": "files",
|
||||
"weight": 530,
|
||||
"weight": 531,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "storage\/get-file-download.md",
|
||||
@@ -8876,7 +8893,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFilePreview",
|
||||
"group": "files",
|
||||
"weight": 529,
|
||||
"weight": 530,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "storage\/get-file-preview.md",
|
||||
@@ -9084,7 +9101,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getFileView",
|
||||
"group": "files",
|
||||
"weight": 531,
|
||||
"weight": 532,
|
||||
"cookies": false,
|
||||
"type": "location",
|
||||
"demo": "storage\/get-file-view.md",
|
||||
@@ -9164,7 +9181,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listTransactions",
|
||||
"group": "transactions",
|
||||
"weight": 427,
|
||||
"weight": 403,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-transactions.md",
|
||||
@@ -9234,7 +9251,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 423,
|
||||
"weight": 399,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-transaction.md",
|
||||
@@ -9275,7 +9292,8 @@
|
||||
"type": "integer",
|
||||
"description": "Seconds before the transaction expires.",
|
||||
"default": 300,
|
||||
"x-example": 60
|
||||
"x-example": 60,
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9307,7 +9325,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 424,
|
||||
"weight": 400,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-transaction.md",
|
||||
@@ -9373,7 +9391,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 425,
|
||||
"weight": 401,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-transaction.md",
|
||||
@@ -9455,7 +9473,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteTransaction",
|
||||
"group": "transactions",
|
||||
"weight": 426,
|
||||
"weight": 402,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-transaction.md",
|
||||
@@ -9523,7 +9541,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createOperations",
|
||||
"group": "transactions",
|
||||
"weight": 428,
|
||||
"weight": 404,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-operations.md",
|
||||
@@ -9607,7 +9625,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listRows",
|
||||
"group": "rows",
|
||||
"weight": 419,
|
||||
"weight": 395,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/list-rows.md",
|
||||
@@ -9710,7 +9728,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createRow",
|
||||
"group": "rows",
|
||||
"weight": 411,
|
||||
"weight": 387,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/create-row.md",
|
||||
@@ -9864,7 +9882,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getRow",
|
||||
"group": "rows",
|
||||
"weight": 412,
|
||||
"weight": 388,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/get-row.md",
|
||||
@@ -9966,7 +9984,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "upsertRow",
|
||||
"group": "rows",
|
||||
"weight": 415,
|
||||
"weight": 391,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/upsert-row.md",
|
||||
@@ -10112,7 +10130,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateRow",
|
||||
"group": "rows",
|
||||
"weight": 413,
|
||||
"weight": 389,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/update-row.md",
|
||||
@@ -10221,7 +10239,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteRow",
|
||||
"group": "rows",
|
||||
"weight": 417,
|
||||
"weight": 393,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/delete-row.md",
|
||||
@@ -10321,7 +10339,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "decrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 422,
|
||||
"weight": 398,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/decrement-row-column.md",
|
||||
@@ -10393,13 +10411,15 @@
|
||||
"type": "number",
|
||||
"description": "Value to increment the column by. The value must be a number.",
|
||||
"default": 1,
|
||||
"x-example": null
|
||||
"x-example": null,
|
||||
"format": "float"
|
||||
},
|
||||
"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": {
|
||||
@@ -10441,7 +10461,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "incrementRowColumn",
|
||||
"group": "rows",
|
||||
"weight": 421,
|
||||
"weight": 397,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "tablesdb\/increment-row-column.md",
|
||||
@@ -10513,13 +10533,15 @@
|
||||
"type": "number",
|
||||
"description": "Value to increment the column by. The value must be a number.",
|
||||
"default": 1,
|
||||
"x-example": null
|
||||
"x-example": null,
|
||||
"format": "float"
|
||||
},
|
||||
"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": {
|
||||
@@ -10559,7 +10581,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "list",
|
||||
"group": "teams",
|
||||
"weight": 145,
|
||||
"weight": 110,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/list.md",
|
||||
@@ -10644,7 +10666,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "create",
|
||||
"group": "teams",
|
||||
"weight": 144,
|
||||
"weight": 109,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/create.md",
|
||||
@@ -10735,7 +10757,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "get",
|
||||
"group": "teams",
|
||||
"weight": 146,
|
||||
"weight": 111,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/get.md",
|
||||
@@ -10798,7 +10820,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateName",
|
||||
"group": "teams",
|
||||
"weight": 148,
|
||||
"weight": 113,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/update-name.md",
|
||||
@@ -10874,7 +10896,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "delete",
|
||||
"group": "teams",
|
||||
"weight": 150,
|
||||
"weight": 115,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/delete.md",
|
||||
@@ -10937,7 +10959,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "listMemberships",
|
||||
"group": "memberships",
|
||||
"weight": 152,
|
||||
"weight": 117,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/list-memberships.md",
|
||||
@@ -11030,7 +11052,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "createMembership",
|
||||
"group": "memberships",
|
||||
"weight": 151,
|
||||
"weight": 116,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/create-membership.md",
|
||||
@@ -11076,7 +11098,8 @@
|
||||
"type": "string",
|
||||
"description": "Email of the new team member.",
|
||||
"default": "",
|
||||
"x-example": "email@example.com"
|
||||
"x-example": "email@example.com",
|
||||
"format": "email"
|
||||
},
|
||||
"userId": {
|
||||
"type": "string",
|
||||
@@ -11088,7 +11111,8 @@
|
||||
"type": "string",
|
||||
"description": "Phone number. Format this number with a leading '+' and a country code, e.g., +16175551212.",
|
||||
"default": "",
|
||||
"x-example": "+12065550100"
|
||||
"x-example": "+12065550100",
|
||||
"format": "phone"
|
||||
},
|
||||
"roles": {
|
||||
"type": "array",
|
||||
@@ -11110,7 +11134,8 @@
|
||||
"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"
|
||||
"x-example": "https:\/\/example.com",
|
||||
"format": "url"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
@@ -11151,7 +11176,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getMembership",
|
||||
"group": "memberships",
|
||||
"weight": 153,
|
||||
"weight": 118,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/get-membership.md",
|
||||
@@ -11222,7 +11247,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMembership",
|
||||
"group": "memberships",
|
||||
"weight": 154,
|
||||
"weight": 119,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/update-membership.md",
|
||||
@@ -11316,7 +11341,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "deleteMembership",
|
||||
"group": "memberships",
|
||||
"weight": 156,
|
||||
"weight": 121,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/delete-membership.md",
|
||||
@@ -11389,7 +11414,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updateMembershipStatus",
|
||||
"group": "memberships",
|
||||
"weight": 155,
|
||||
"weight": 120,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/update-membership-status.md",
|
||||
@@ -11485,7 +11510,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "getPrefs",
|
||||
"group": "teams",
|
||||
"weight": 147,
|
||||
"weight": 112,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/get-prefs.md",
|
||||
@@ -11548,7 +11573,7 @@
|
||||
"x-appwrite": {
|
||||
"method": "updatePrefs",
|
||||
"group": "teams",
|
||||
"weight": 149,
|
||||
"weight": 114,
|
||||
"cookies": false,
|
||||
"type": "",
|
||||
"demo": "teams\/update-prefs.md",
|
||||
@@ -13368,6 +13393,16 @@
|
||||
"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": [
|
||||
@@ -13381,7 +13416,9 @@
|
||||
"mimeType",
|
||||
"sizeOriginal",
|
||||
"chunksTotal",
|
||||
"chunksUploaded"
|
||||
"chunksUploaded",
|
||||
"encryption",
|
||||
"compression"
|
||||
],
|
||||
"example": {
|
||||
"$id": "5e5ea5c16897e",
|
||||
@@ -13396,7 +13433,9 @@
|
||||
"mimeType": "image\/png",
|
||||
"sizeOriginal": 17890,
|
||||
"chunksTotal": 17890,
|
||||
"chunksUploaded": 17890
|
||||
"chunksUploaded": 17890,
|
||||
"encryption": true,
|
||||
"compression": "gzip"
|
||||
}
|
||||
},
|
||||
"team": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,4 +3,6 @@
|
||||
use Utopia\Image\Image;
|
||||
use Utopia\System\System;
|
||||
|
||||
Image::setResourceLimit('memory', intval(System::getEnv('_APP_IMAGES_RESOURCE_LIMIT_MEMORY', 1024*1024*64)));
|
||||
if (\class_exists('Imagick')) {
|
||||
Image::setResourceLimit('memory', intval(System::getEnv('_APP_IMAGES_RESOURCE_LIMIT_MEMORY', 1024*1024*64)));
|
||||
}
|
||||
|
||||
@@ -1285,6 +1285,15 @@ 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.',
|
||||
|
||||
+109
-97
@@ -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;
|
||||
@@ -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) {
|
||||
$createSession = function (string $userId, string $secret, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Store $store, ProofsToken $proofForToken, ProofsCode $proofForCode, Authorization $authorization) {
|
||||
|
||||
/** @var Appwrite\Utopia\Database\Documents\User $userFromRequest */
|
||||
$userFromRequest = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId));
|
||||
$userFromRequest = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId));
|
||||
|
||||
if ($userFromRequest->isEmpty()) {
|
||||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
@@ -266,7 +266,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
|
||||
$detector->getDevice()
|
||||
));
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session
|
||||
->setAttribute('$permissions', [
|
||||
@@ -275,7 +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,8 +376,9 @@ App::post('/v1/account')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Authorization $authorization, Hooks $hooks) {
|
||||
|
||||
$email = \strtolower($email);
|
||||
if ('console' === $project->getId()) {
|
||||
@@ -469,9 +470,9 @@ App::post('/v1/account')
|
||||
]);
|
||||
|
||||
$user->removeAttribute('$sequence');
|
||||
$user = Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
$user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
try {
|
||||
$target = Authorization::skip(fn () => $dbForProject->createDocument('targets', new Document([
|
||||
$target = $authorization->skip(fn () => $dbForProject->createDocument('targets', new Document([
|
||||
'$permissions' => [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
@@ -497,9 +498,9 @@ App::post('/v1/account')
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
Authorization::unsetRole(Role::guests()->toString());
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
Authorization::setRole(Role::users()->toString());
|
||||
$authorization->removeRole(Role::guests()->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::users()->toString());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -976,7 +977,8 @@ App::post('/v1/account/sessions/email')
|
||||
->inject('store')
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForToken')
|
||||
->action(function (string $email, string $password, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) {
|
||||
->inject('authorization')
|
||||
->action(function (string $email, string $password, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, Authorization $authorization) {
|
||||
$email = \strtolower($email);
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
@@ -1021,7 +1023,7 @@ App::post('/v1/account/sessions/email')
|
||||
$detector->getDevice()
|
||||
));
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
// Re-hash if not using recommended algo
|
||||
if ($user->getAttribute('hash') !== $proofForPassword->getHash()->getName()) {
|
||||
@@ -1120,7 +1122,8 @@ App::post('/v1/account/sessions/anonymous')
|
||||
->inject('store')
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForToken')
|
||||
->action(function (Request $request, Response $response, Locale $locale, User $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) {
|
||||
->inject('authorization')
|
||||
->action(function (Request $request, Response $response, Locale $locale, User $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, Authorization $authorization) {
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
if ('console' === $project->getId()) {
|
||||
@@ -1165,7 +1168,7 @@ App::post('/v1/account/sessions/anonymous')
|
||||
'accessedAt' => DateTime::now(),
|
||||
]);
|
||||
$user->removeAttribute('$sequence');
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
$user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
|
||||
// Create session token
|
||||
$duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
@@ -1191,7 +1194,7 @@ App::post('/v1/account/sessions/anonymous')
|
||||
$detector->getDevice()
|
||||
));
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session->setAttribute('$permissions', [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
@@ -1274,6 +1277,7 @@ App::post('/v1/account/sessions/token')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->inject('proofForCode')
|
||||
->inject('authorization')
|
||||
->action($createSession);
|
||||
|
||||
App::get('/v1/account/sessions/oauth2/:provider')
|
||||
@@ -1470,7 +1474,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
->inject('store')
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForToken')
|
||||
->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Validator $redirectValidator, Document $devKey, User $user, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken) use ($oauthDefaultSuccess) {
|
||||
->inject('authorization')
|
||||
->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Validator $redirectValidator, Document $devKey, User $user, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, Authorization $authorization) use ($oauthDefaultSuccess) {
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$port = $request->getPort();
|
||||
$callbackBase = $protocol . '://' . $request->getHostname();
|
||||
@@ -1726,7 +1731,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())),
|
||||
@@ -1744,8 +1749,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
}
|
||||
}
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
Authorization::setRole(Role::users()->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::users()->toString());
|
||||
|
||||
if (false === $user->getAttribute('status')) { // Account is blocked
|
||||
$failureRedirect(Exception::USER_BLOCKED); // User is in status blocked
|
||||
@@ -1816,7 +1821,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$state['success'] = URLParser::parse($state['success']);
|
||||
$query = URLParser::parseQuery($state['success']['query']);
|
||||
@@ -1840,7 +1845,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$token = $dbForProject->createDocument('tokens', $token
|
||||
->setAttribute('$permissions', [
|
||||
@@ -2077,7 +2082,8 @@ App::post('/v1/account/tokens/magic-url')
|
||||
->inject('queueForMails')
|
||||
->inject('proofForPassword')
|
||||
->inject('platform')
|
||||
->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, User $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword, array $platform) {
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword, array $platform, Authorization $authorization) {
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
|
||||
}
|
||||
@@ -2150,7 +2156,7 @@ App::post('/v1/account/tokens/magic-url')
|
||||
]);
|
||||
|
||||
$user->removeAttribute('$sequence');
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
$user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
}
|
||||
|
||||
$proofForToken = new ProofsToken(TOKEN_LENGTH_MAGIC_URL);
|
||||
@@ -2170,7 +2176,7 @@ App::post('/v1/account/tokens/magic-url')
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$token = $dbForProject->createDocument('tokens', $token
|
||||
->setAttribute('$permissions', [
|
||||
@@ -2356,7 +2362,8 @@ App::post('/v1/account/tokens/email')
|
||||
->inject('queueForMails')
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForCode')
|
||||
->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, User $user, Document $project, array $platform, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword, ProofsCode $proofForCode) {
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, User $user, Document $project, array $platform, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsPassword $proofForPassword, ProofsCode $proofForCode, Authorization $authorization) {
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
|
||||
}
|
||||
@@ -2425,9 +2432,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())),
|
||||
@@ -2465,7 +2472,7 @@ App::post('/v1/account/tokens/email')
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$token = $dbForProject->createDocument('tokens', $token
|
||||
->setAttribute('$permissions', [
|
||||
@@ -2662,10 +2669,11 @@ App::put('/v1/account/sessions/magic-url')
|
||||
->inject('queueForMails')
|
||||
->inject('store')
|
||||
->inject('proofForCode')
|
||||
->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForCode) use ($createSession) {
|
||||
->inject('authorization')
|
||||
->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForCode, $authorization) use ($createSession) {
|
||||
$proofForToken = new ProofsToken(TOKEN_LENGTH_MAGIC_URL);
|
||||
$proofForToken->setHash(new Sha());
|
||||
$createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForToken, $proofForCode);
|
||||
$createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForToken, $proofForCode, $authorization);
|
||||
});
|
||||
|
||||
App::put('/v1/account/sessions/phone')
|
||||
@@ -2711,6 +2719,7 @@ App::put('/v1/account/sessions/phone')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->inject('proofForCode')
|
||||
->inject('authorization')
|
||||
->action($createSession);
|
||||
|
||||
App::post('/v1/account/tokens/phone')
|
||||
@@ -2754,7 +2763,8 @@ App::post('/v1/account/tokens/phone')
|
||||
->inject('plan')
|
||||
->inject('store')
|
||||
->inject('proofForCode')
|
||||
->action(function (string $userId, string $phone, Request $request, Response $response, User $user, Document $project, array $platform, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Store $store, ProofsCode $proofForCode) {
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $phone, Request $request, Response $response, User $user, Document $project, array $platform, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Store $store, ProofsCode $proofForCode, Authorization $authorization) {
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
}
|
||||
@@ -2804,9 +2814,9 @@ App::post('/v1/account/tokens/phone')
|
||||
]);
|
||||
|
||||
$user->removeAttribute('$sequence');
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
$user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
try {
|
||||
$target = Authorization::skip(fn () => $dbForProject->createDocument('targets', new Document([
|
||||
$target = $authorization->skip(fn () => $dbForProject->createDocument('targets', new Document([
|
||||
'$permissions' => [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
@@ -2852,7 +2862,7 @@ App::post('/v1/account/tokens/phone')
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$token = $dbForProject->createDocument('tokens', $token
|
||||
->setAttribute('$permissions', [
|
||||
@@ -2898,26 +2908,21 @@ App::post('/v1/account/tokens/phone')
|
||||
->setRecipients([$phone])
|
||||
->setProviderType(MESSAGE_TYPE_SMS);
|
||||
|
||||
if (isset($plan['authPhone'])) {
|
||||
$timelimit = $timelimit('organization:{organizationId}', $plan['authPhone'], 30 * 24 * 60 * 60); // 30 days
|
||||
$timelimit
|
||||
->setParam('{organizationId}', $project->getAttribute('teamId'));
|
||||
$helper = PhoneNumberUtil::getInstance();
|
||||
try {
|
||||
$countryCode = $helper->parse($phone)->getCountryCode();
|
||||
|
||||
$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);
|
||||
}
|
||||
if (!empty($countryCode)) {
|
||||
$queueForStatsUsage
|
||||
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
|
||||
}
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
} catch (NumberParseException $e) {
|
||||
// Ignore invalid phone number for country code stats
|
||||
}
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
$token->setAttribute('secret', $secret);
|
||||
@@ -3243,7 +3248,8 @@ App::patch('/v1/account/email')
|
||||
->inject('project')
|
||||
->inject('hooks')
|
||||
->inject('proofForPassword')
|
||||
->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, User $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, ProofsPassword $proofForPassword) {
|
||||
->inject('authorization')
|
||||
->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, User $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, ProofsPassword $proofForPassword, Authorization $authorization) {
|
||||
// passwordUpdate will be empty if the user has never set a password
|
||||
$passwordUpdate = $user->getAttribute('passwordUpdate');
|
||||
|
||||
@@ -3295,7 +3301,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]),
|
||||
]));
|
||||
|
||||
@@ -3311,7 +3317,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) {
|
||||
@@ -3352,8 +3358,9 @@ App::patch('/v1/account/phone')
|
||||
->inject('queueForEvents')
|
||||
->inject('project')
|
||||
->inject('hooks')
|
||||
->inject('proofForPassword')
|
||||
->action(function (string $phone, string $password, Response $response, User $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, ProofsPassword $proofForPassword) {
|
||||
->inject('proofForPassword')
|
||||
->inject('authorization')
|
||||
->action(function (string $phone, string $password, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, ProofsPassword $proofForPassword, Authorization $authorization) {
|
||||
// passwordUpdate will be empty if the user has never set a password
|
||||
$passwordUpdate = $user->getAttribute('passwordUpdate');
|
||||
|
||||
@@ -3368,7 +3375,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]),
|
||||
]));
|
||||
|
||||
@@ -3399,7 +3406,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) {
|
||||
@@ -3535,7 +3542,9 @@ App::post('/v1/account/recovery')
|
||||
->inject('queueForMails')
|
||||
->inject('queueForEvents')
|
||||
->inject('proofForToken')
|
||||
->action(function (string $email, string $url, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Mail $queueForMails, Event $queueForEvents, ProofsToken $proofForToken) {
|
||||
->inject('authorization')
|
||||
->action(function (string $email, string $url, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Mail $queueForMails, Event $queueForEvents, ProofsToken $proofForToken, Authorization $authorization) {
|
||||
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
|
||||
}
|
||||
@@ -3571,7 +3580,7 @@ App::post('/v1/account/recovery')
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($profile->getId())->toString());
|
||||
$authorization->addRole(Role::user($profile->getId())->toString());
|
||||
|
||||
$recovery = $dbForProject->createDocument('tokens', $recovery
|
||||
->setAttribute('$permissions', [
|
||||
@@ -3727,7 +3736,8 @@ App::put('/v1/account/recovery')
|
||||
->inject('hooks')
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForToken')
|
||||
->action(function (string $userId, string $secret, string $password, Response $response, User $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks, ProofsPassword $proofForPassword, ProofsToken $proofForToken) {
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $secret, string $password, Response $response, User $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks, ProofsPassword $proofForPassword, ProofsToken $proofForToken, Authorization $authorization) {
|
||||
/** @var Appwrite\Utopia\Database\Documents\User $profile */
|
||||
$profile = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
@@ -3741,7 +3751,7 @@ App::put('/v1/account/recovery')
|
||||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
Authorization::setRole(Role::user($profile->getId())->toString());
|
||||
$authorization->addRole(Role::user($profile->getId())->toString());
|
||||
|
||||
$newPassword = $proofForPassword->hash($password);
|
||||
|
||||
@@ -3844,7 +3854,8 @@ App::post('/v1/account/verifications/email')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->inject('proofForToken')
|
||||
->action(function (string $url, Request $request, Response $response, Document $project, array $platform, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsToken $proofForToken) {
|
||||
->inject('authorization')
|
||||
->action(function (string $url, Request $request, Response $response, Document $project, array $platform, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, ProofsToken $proofForToken, Authorization $authorization) {
|
||||
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
|
||||
@@ -3873,7 +3884,7 @@ App::post('/v1/account/verifications/email')
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$verification = $dbForProject->createDocument('tokens', $verification
|
||||
->setAttribute('$permissions', [
|
||||
@@ -4072,9 +4083,10 @@ App::put('/v1/account/verifications/email')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('proofForToken')
|
||||
->action(function (string $userId, string $secret, Response $response, User $user, Database $dbForProject, Event $queueForEvents, ProofsToken $proofForToken) {
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $secret, Response $response, User $user, Database $dbForProject, Event $queueForEvents, ProofsToken $proofForToken, Authorization $authorization) {
|
||||
/** @var Appwrite\Utopia\Database\Documents\User $profile */
|
||||
$profile = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId));
|
||||
$profile = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId));
|
||||
|
||||
if ($profile->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
@@ -4086,7 +4098,7 @@ App::put('/v1/account/verifications/email')
|
||||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
Authorization::setRole(Role::user($profile->getId())->toString());
|
||||
$authorization->addRole(Role::user($profile->getId())->toString());
|
||||
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true));
|
||||
|
||||
@@ -4146,7 +4158,8 @@ App::post('/v1/account/verifications/phone')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('plan')
|
||||
->inject('proofForCode')
|
||||
->action(function (Request $request, Response $response, User $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, ProofsCode $proofForCode) {
|
||||
->inject('authorization')
|
||||
->action(function (Request $request, Response $response, User $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, ProofsCode $proofForCode, Authorization $authorization) {
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
}
|
||||
@@ -4185,7 +4198,7 @@ App::post('/v1/account/verifications/phone')
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$verification = $dbForProject->createDocument('tokens', $verification
|
||||
->setAttribute('$permissions', [
|
||||
@@ -4226,26 +4239,21 @@ App::post('/v1/account/verifications/phone')
|
||||
->setRecipients([$user->getAttribute('phone')])
|
||||
->setProviderType(MESSAGE_TYPE_SMS);
|
||||
|
||||
if (isset($plan['authPhone'])) {
|
||||
$timelimit = $timelimit('organization:{organizationId}', $plan['authPhone'], 30 * 24 * 60 * 60); // 30 days
|
||||
$timelimit
|
||||
->setParam('{organizationId}', $project->getAttribute('teamId'));
|
||||
$helper = PhoneNumberUtil::getInstance();
|
||||
try {
|
||||
$countryCode = $helper->parse($phone)->getCountryCode();
|
||||
|
||||
$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);
|
||||
}
|
||||
if (!empty($countryCode)) {
|
||||
$queueForStatsUsage
|
||||
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
|
||||
}
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
} catch (NumberParseException $e) {
|
||||
// Ignore invalid phone number for country code stats
|
||||
}
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
$verification->setAttribute('secret', $secret);
|
||||
@@ -4291,9 +4299,10 @@ App::put('/v1/account/verifications/phone')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('proofForCode')
|
||||
->action(function (string $userId, string $secret, Response $response, User $user, Database $dbForProject, Event $queueForEvents, ProofsCode $proofForCode) {
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $secret, Response $response, User $user, Database $dbForProject, Event $queueForEvents, ProofsCode $proofForCode, Authorization $authorization) {
|
||||
/** @var Appwrite\Utopia\Database\Documents\User $profile */
|
||||
$profile = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId));
|
||||
$profile = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId));
|
||||
|
||||
if ($profile->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
@@ -4305,7 +4314,7 @@ App::put('/v1/account/verifications/phone')
|
||||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
Authorization::setRole(Role::user($profile->getId())->toString());
|
||||
$authorization->addRole(Role::user($profile->getId())->toString());
|
||||
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('phoneVerification', true));
|
||||
|
||||
@@ -4358,12 +4367,13 @@ App::post('/v1/account/targets/push')
|
||||
->inject('dbForProject')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, User $user, Request $request, Response $response, Database $dbForProject, Store $store, ProofsToken $proofForToken) {
|
||||
->inject('authorization')
|
||||
->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, User $user, Request $request, Response $response, Database $dbForProject, Store $store, ProofsToken $proofForToken, Authorization $authorization) {
|
||||
$targetId = $targetId == 'unique()' ? ID::unique() : $targetId;
|
||||
|
||||
$provider = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId));
|
||||
$provider = $authorization->skip(fn () => $dbForProject->getDocument('providers', $providerId));
|
||||
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
|
||||
if (!$target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
|
||||
@@ -4438,9 +4448,10 @@ App::put('/v1/account/targets/:targetId/push')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject) {
|
||||
->inject('authorization')
|
||||
->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) {
|
||||
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
|
||||
if ($target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
@@ -4503,8 +4514,9 @@ App::delete('/v1/account/targets/:targetId/push')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $targetId, Event $queueForEvents, Delete $queueForDeletes, Document $user, Request $request, Response $response, Database $dbForProject) {
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
->inject('authorization')
|
||||
->action(function (string $targetId, Event $queueForEvents, Delete $queueForDeletes, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) {
|
||||
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
|
||||
if ($target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -28,11 +28,12 @@ use Utopia\Validator\Text;
|
||||
App::init()
|
||||
->groups(['graphql'])
|
||||
->inject('project')
|
||||
->action(function (Document $project) {
|
||||
->inject('authorization')
|
||||
->action(function (Document $project, Authorization $authorization) {
|
||||
if (
|
||||
array_key_exists('graphql', $project->getAttribute('apis', []))
|
||||
&& !$project->getAttribute('apis', [])['graphql']
|
||||
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles()))
|
||||
&& !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
@@ -223,8 +224,11 @@ function execute(
|
||||
$flags = DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE;
|
||||
$validations = GraphQL::getStandardValidationRules();
|
||||
|
||||
if (System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') {
|
||||
if (System::getEnv('_APP_GRAPHQL_INTROSPECTION', 'enabled') === 'disabled') {
|
||||
$validations[] = new DisableIntrospection();
|
||||
}
|
||||
|
||||
if (System::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') {
|
||||
$validations[] = new QueryComplexity($maxComplexity);
|
||||
$validations[] = new QueryDepth($maxDepth);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -36,6 +36,7 @@ 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;
|
||||
@@ -1073,8 +1074,9 @@ App::get('/v1/messaging/providers')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -1100,7 +1102,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.");
|
||||
@@ -2481,8 +2483,9 @@ App::get('/v1/messaging/topics')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -2508,7 +2511,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.");
|
||||
@@ -2782,29 +2785,27 @@ App::post('/v1/messaging/topics/:topicId/subscribers')
|
||||
->param('targetId', '', new UID(), 'Target ID. The target ID to link to the specified Topic ID.')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (string $subscriberId, string $topicId, string $targetId, Event $queueForEvents, Database $dbForProject, Response $response) {
|
||||
->action(function (string $subscriberId, string $topicId, string $targetId, Event $queueForEvents, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
$subscriberId = $subscriberId == 'unique()' ? ID::unique() : $subscriberId;
|
||||
|
||||
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
throw new Exception(Exception::TOPIC_NOT_FOUND);
|
||||
}
|
||||
|
||||
$validator = new Authorization('subscribe');
|
||||
|
||||
if (!$validator->isValid($topic->getAttribute('subscribe'))) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $validator->getDescription());
|
||||
if (!$authorization->isValid(new Input('subscribe', $topic->getAttribute('subscribe')))) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
|
||||
if ($target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
$user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
|
||||
$subscriber = new Document([
|
||||
'$id' => $subscriberId,
|
||||
@@ -2837,7 +2838,7 @@ App::post('/v1/messaging/topics/:topicId/subscribers')
|
||||
default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE),
|
||||
};
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute(
|
||||
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute(
|
||||
'topics',
|
||||
$topicId,
|
||||
$totalAttribute,
|
||||
@@ -2882,8 +2883,9 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (string $topicId, array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
|
||||
->action(function (string $topicId, array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -2894,7 +2896,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
throw new Exception(Exception::TOPIC_NOT_FOUND);
|
||||
@@ -2917,7 +2919,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
}
|
||||
|
||||
$subscriberId = $cursor->getValue();
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId));
|
||||
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId));
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Subscriber '{$subscriberId}' for the 'cursor' value not found.");
|
||||
@@ -2931,10 +2933,10 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
|
||||
}
|
||||
|
||||
$subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject) {
|
||||
return function () use ($subscriber, $dbForProject) {
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
|
||||
$user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
$subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject, $authorization) {
|
||||
return function () use ($subscriber, $dbForProject, $authorization) {
|
||||
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
|
||||
$user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
|
||||
return $subscriber
|
||||
->setAttribute('target', $target)
|
||||
@@ -3067,9 +3069,10 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
->param('topicId', '', new UID(), 'Topic ID. The topic ID subscribed to.')
|
||||
->param('subscriberId', '', new UID(), 'Subscriber ID.')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (string $topicId, string $subscriberId, Database $dbForProject, Response $response) {
|
||||
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
->action(function (string $topicId, string $subscriberId, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
throw new Exception(Exception::TOPIC_NOT_FOUND);
|
||||
@@ -3081,8 +3084,8 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
throw new Exception(Exception::SUBSCRIBER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
|
||||
$user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
|
||||
$user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
|
||||
$subscriber
|
||||
->setAttribute('target', $target)
|
||||
@@ -3118,9 +3121,10 @@ App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
->param('subscriberId', '', new UID(), 'Subscriber ID.')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (string $topicId, string $subscriberId, Event $queueForEvents, Database $dbForProject, Response $response) {
|
||||
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
->action(function (string $topicId, string $subscriberId, Event $queueForEvents, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
throw new Exception(Exception::TOPIC_NOT_FOUND);
|
||||
@@ -3143,7 +3147,7 @@ App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE),
|
||||
};
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute(
|
||||
$authorization->skip(fn () => $dbForProject->decreaseDocumentAttribute(
|
||||
'topics',
|
||||
$topicId,
|
||||
$totalAttribute,
|
||||
@@ -3702,8 +3706,9 @@ App::get('/v1/messaging/messages')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -3729,7 +3734,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.");
|
||||
|
||||
@@ -342,6 +342,7 @@ App::post('/v1/migrations/csv/imports')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('deviceForFiles')
|
||||
@@ -356,6 +357,7 @@ App::post('/v1/migrations/csv/imports')
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
Document $project,
|
||||
array $platform,
|
||||
Device $deviceForFiles,
|
||||
@@ -363,7 +365,7 @@ App::post('/v1/migrations/csv/imports')
|
||||
Event $queueForEvents,
|
||||
Migration $queueForMigrations
|
||||
) {
|
||||
$bucket = Authorization::skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) {
|
||||
$bucket = $authorization->skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) {
|
||||
if ($internalFile) {
|
||||
return $dbForPlatform->getDocument('buckets', 'default');
|
||||
}
|
||||
@@ -374,7 +376,7 @@ App::post('/v1/migrations/csv/imports')
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$file = Authorization::skip(fn () => $internalFile ? $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $fileId) : $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
$file = $authorization->skip(fn () => $internalFile ? $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $fileId) : $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
@@ -491,6 +493,7 @@ App::post('/v1/migrations/csv/exports')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('project')
|
||||
->inject('platform')
|
||||
->inject('queueForEvents')
|
||||
@@ -509,6 +512,7 @@ App::post('/v1/migrations/csv/exports')
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
Document $project,
|
||||
array $platform,
|
||||
Event $queueForEvents,
|
||||
@@ -520,7 +524,7 @@ App::post('/v1/migrations/csv/exports')
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$bucket = Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'default'));
|
||||
$bucket = $authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'default'));
|
||||
if ($bucket->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
@@ -533,12 +537,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);
|
||||
}
|
||||
|
||||
@@ -45,9 +45,10 @@ App::get('/v1/project/usage')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('getLogsDB')
|
||||
->inject('smsRates')
|
||||
->action(function (string $startDate, string $endDate, string $period, Response $response, Document $project, Database $dbForProject, callable $getLogsDB, array $smsRates) {
|
||||
->action(function (string $startDate, string $endDate, string $period, Response $response, Document $project, Database $dbForProject, Authorization $authorization, callable $getLogsDB, array $smsRates) {
|
||||
$stats = $total = $usage = [];
|
||||
$format = 'Y-m-d 00:00:00';
|
||||
$firstDay = (new DateTime($startDate))->format($format);
|
||||
@@ -102,7 +103,7 @@ App::get('/v1/project/usage')
|
||||
'1d' => 'Y-m-d\T00:00:00.000P',
|
||||
};
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $dbForLogs, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
|
||||
$authorization->skip(function () use ($dbForProject, $dbForLogs, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
|
||||
foreach ($metrics['total'] as $metric) {
|
||||
$db = ($metric === METRIC_FILES_IMAGES_TRANSFORMED) ? $dbForLogs : $dbForProject;
|
||||
|
||||
@@ -286,7 +287,7 @@ App::get('/v1/project/usage')
|
||||
}, $dbForProject->find('functions'));
|
||||
|
||||
// This total is includes free and paid SMS usage
|
||||
$authPhoneTotal = Authorization::skip(fn () => $dbForProject->sum('stats', 'value', [
|
||||
$authPhoneTotal = $authorization->skip(fn () => $dbForProject->sum('stats', 'value', [
|
||||
Query::equal('metric', [METRIC_AUTH_METHOD_PHONE]),
|
||||
Query::equal('period', ['1d']),
|
||||
Query::greaterThanEqual('time', $firstDay),
|
||||
@@ -294,7 +295,7 @@ App::get('/v1/project/usage')
|
||||
]));
|
||||
|
||||
// This estimate is only for paid SMS usage
|
||||
$authPhoneMetrics = Authorization::skip(fn () => $dbForProject->find('stats', [
|
||||
$authPhoneMetrics = $authorization->skip(fn () => $dbForProject->find('stats', [
|
||||
Query::startsWith('metric', METRIC_AUTH_METHOD_PHONE . '.'),
|
||||
Query::equal('period', ['1d']),
|
||||
Query::greaterThanEqual('time', $firstDay),
|
||||
|
||||
@@ -227,6 +227,7 @@ 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
|
||||
@@ -1482,7 +1483,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('scopes')), 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('projectScopes')), 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')
|
||||
@@ -1627,7 +1628,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('scopes')), 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('projectScopes')), 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')
|
||||
@@ -1728,7 +1729,7 @@ App::post('/v1/projects/:projectId/jwts')
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->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('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('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')
|
||||
|
||||
@@ -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,22 +86,24 @@ App::post('/v1/teams')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
|
||||
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isAppUser = User::isApp(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
$isAppUser = User::isApp($authorization->getRoles());
|
||||
|
||||
$teamId = $teamId == 'unique()' ? ID::unique() : $teamId;
|
||||
|
||||
try {
|
||||
$team = Authorization::skip(fn () => $dbForProject->createDocument('teams', new Document([
|
||||
$team = $authorization->skip(fn () => $dbForProject->createDocument('teams', new Document([
|
||||
'$id' => $teamId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team($teamId)),
|
||||
Permission::update(Role::team($teamId, 'owner')),
|
||||
Permission::delete(Role::team($teamId, 'owner')),
|
||||
],
|
||||
'labels' => [],
|
||||
'name' => $name,
|
||||
'total' => ($isPrivilegedUser || $isAppUser) ? 0 : 1,
|
||||
'prefs' => new \stdClass(),
|
||||
@@ -431,15 +433,22 @@ 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);
|
||||
->setDocument($team)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($team);
|
||||
|
||||
$queueForEvents
|
||||
->setParam('teamId', $team->getId())
|
||||
->setPayload($response->output($team, Response::MODEL_TEAM))
|
||||
@@ -491,6 +500,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('locale')
|
||||
->inject('queueForMails')
|
||||
->inject('queueForMessaging')
|
||||
@@ -500,9 +510,9 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->inject('plan')
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForToken')
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Password $proofForPassword, Token $proofForToken) {
|
||||
$isAppUser = User::isApp(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Authorization $authorization, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Password $proofForPassword, Token $proofForToken) {
|
||||
$isAppUser = User::isApp($authorization->getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
|
||||
$url = htmlentities($url);
|
||||
if (empty($url)) {
|
||||
@@ -619,13 +629,13 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
]);
|
||||
|
||||
try {
|
||||
$invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', $userDocument));
|
||||
$invitee = $authorization->skip(fn () => $dbForProject->createDocument('users', $userDocument));
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
|
||||
$isOwner = $authorization->hasRole('team:' . $team->getId() . '/owner');
|
||||
|
||||
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to send invitations for this team');
|
||||
@@ -661,11 +671,11 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
]);
|
||||
|
||||
$membership = ($isPrivilegedUser || $isAppUser) ?
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('memberships', $membership)) :
|
||||
$authorization->skip(fn () => $dbForProject->createDocument('memberships', $membership)) :
|
||||
$dbForProject->createDocument('memberships', $membership);
|
||||
|
||||
if ($isPrivilegedUser || $isAppUser) {
|
||||
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
}
|
||||
} elseif ($membership->getAttribute('confirm') === false) {
|
||||
$membership->setAttribute('secret', $proofForToken->hash($secret));
|
||||
@@ -677,7 +687,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);
|
||||
@@ -799,26 +809,21 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->setRecipients([$phone])
|
||||
->setProviderType('SMS');
|
||||
|
||||
if (isset($plan['authPhone'])) {
|
||||
$timelimit = $timelimit('organization:{organizationId}', $plan['authPhone'], 30 * 24 * 60 * 60); // 30 days
|
||||
$timelimit
|
||||
->setParam('{organizationId}', $project->getAttribute('teamId'));
|
||||
$helper = PhoneNumberUtil::getInstance();
|
||||
try {
|
||||
$countryCode = $helper->parse($phone)->getCountryCode();
|
||||
|
||||
$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);
|
||||
}
|
||||
if (!empty($countryCode)) {
|
||||
$queueForStatsUsage
|
||||
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
|
||||
}
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
} catch (NumberParseException $e) {
|
||||
// Ignore invalid phone number for country code stats
|
||||
}
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -863,7 +868,8 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $teamId, array $queries, string $search, bool $includeTotal, Response $response, Document $project, Database $dbForProject) {
|
||||
->inject('authorization')
|
||||
->action(function (string $teamId, array $queries, string $search, bool $includeTotal, Response $response, Document $project, Database $dbForProject, Authorization $authorization) {
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
@@ -933,7 +939,7 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true,
|
||||
];
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$roles = $authorization->getRoles();
|
||||
$isPrivilegedUser = User::isPrivileged($roles);
|
||||
$isAppUser = User::isApp($roles);
|
||||
|
||||
@@ -1004,7 +1010,8 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $teamId, string $membershipId, Response $response, Document $project, Database $dbForProject) {
|
||||
->inject('authorization')
|
||||
->action(function (string $teamId, string $membershipId, Response $response, Document $project, Database $dbForProject, Authorization $authorization) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
@@ -1024,7 +1031,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
|
||||
'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true,
|
||||
];
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$roles = $authorization->getRoles();
|
||||
$isPrivilegedUser = User::isPrivileged($roles);
|
||||
$isAppUser = User::isApp($roles);
|
||||
|
||||
@@ -1103,8 +1110,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
if ($team->isEmpty()) {
|
||||
@@ -1121,9 +1129,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isAppUser = User::isApp(Authorization::getRoles());
|
||||
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
$isAppUser = User::isApp($authorization->getRoles());
|
||||
$isOwner = $authorization->hasRole('team:' . $team->getId() . '/owner');
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
// Quick check: fetch up to 2 owners to determine if only one exists
|
||||
@@ -1204,12 +1212,13 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('project')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
|
||||
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Authorization $authorization, $project, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
$membership = $dbForProject->getDocument('memberships', $membershipId);
|
||||
@@ -1218,7 +1227,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
throw new Exception(Exception::MEMBERSHIP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$team = Authorization::skip(fn () => $dbForProject->getDocument('teams', $teamId));
|
||||
$team = $authorization->skip(fn () => $dbForProject->getDocument('teams', $teamId));
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
@@ -1254,11 +1263,11 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
->setAttribute('confirm', true)
|
||||
;
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
|
||||
|
||||
// Create session for the user if not logged in
|
||||
if (!$hasSession) {
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
@@ -1286,7 +1295,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session);
|
||||
|
||||
Authorization::setRole(Role::user($userId)->toString());
|
||||
$authorization->addRole(Role::user($userId)->toString());
|
||||
|
||||
$encoded = $store
|
||||
->setProperty('id', $user->getId())
|
||||
@@ -1324,7 +1333,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
@@ -1368,8 +1377,9 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $teamId, string $membershipId, Document $user, Document $project, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $teamId, string $membershipId, Document $user, Document $project, Response $response, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
|
||||
|
||||
$membership = $dbForProject->getDocument('memberships', $membershipId);
|
||||
|
||||
@@ -1427,7 +1437,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
|
||||
|
||||
@@ -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('register')
|
||||
->action(function (string $range, Response $response, Database $dbForProject) {
|
||||
->inject('authorization')
|
||||
->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) {
|
||||
|
||||
$periods = Config::getParam('usage', []);
|
||||
$stats = $usage = [];
|
||||
@@ -2689,7 +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]),
|
||||
|
||||
+32
-29
@@ -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, Build $queueForBuilds, callable $getProjectDB, array $platform) {
|
||||
$createGitDeployments = function (GitHub $github, string $providerInstallationId, array $repositories, string $providerBranch, string $providerBranchUrl, string $providerRepositoryName, string $providerRepositoryUrl, string $providerRepositoryOwner, string $providerCommitHash, string $providerCommitAuthor, string $providerCommitAuthorUrl, string $providerCommitMessage, string $providerCommitUrl, string $providerPullRequestId, bool $external, Database $dbForPlatform, Authorization $authorization, Build $queueForBuilds, callable $getProjectDB, Request $request, array $platform) {
|
||||
$errors = [];
|
||||
foreach ($repositories as $repository) {
|
||||
try {
|
||||
@@ -87,12 +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,6 +306,7 @@ $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', ''),
|
||||
@@ -334,7 +335,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
|
||||
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
|
||||
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource));
|
||||
|
||||
if ($resource->getCollection() === 'sites') {
|
||||
$projectId = $project->getId();
|
||||
@@ -344,7 +345,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
$domain = ID::unique() . "." . $sitesDomain;
|
||||
$ruleId = md5($domain);
|
||||
$previewRuleId = $ruleId;
|
||||
Authorization::skip(
|
||||
$authorization->skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
@@ -377,7 +378,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
$domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}";
|
||||
$ruleId = md5($domain);
|
||||
try {
|
||||
Authorization::skip(
|
||||
$authorization->skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
@@ -408,7 +409,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
$domain = "commit-" . substr($providerCommitHash, 0, 16) . ".{$sitesDomain}";
|
||||
$ruleId = md5($domain);
|
||||
try {
|
||||
Authorization::skip(
|
||||
$authorization->skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
@@ -460,7 +461,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
if ($lockAcquired) {
|
||||
// Wrap in try/finally to ensure lock file gets deleted
|
||||
try {
|
||||
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', $previewRuleId));
|
||||
$rule = $authorization->skip(fn () => $dbForPlatform->getDocument('rules', $previewRuleId));
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$previewUrl = !empty($rule) ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : '';
|
||||
@@ -472,7 +473,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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1476,11 +1477,12 @@ App::post('/v1/vcs/github/events')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForBuilds')
|
||||
->inject('platform')
|
||||
->action(
|
||||
function (GitHub $github, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds, array $platform) use ($createGitDeployments) {
|
||||
function (GitHub $github, Request $request, Response $response, Database $dbForPlatform, Authorization $authorization, callable $getProjectDB, Build $queueForBuilds, array $platform) use ($createGitDeployments) {
|
||||
$payload = $request->getRawPayload();
|
||||
$signatureRemote = $request->getHeader('x-hub-signature-256', '');
|
||||
$signatureLocal = System::getEnv('_APP_VCS_GITHUB_WEBHOOK_SECRET', '');
|
||||
@@ -1516,14 +1518,14 @@ App::post('/v1/vcs/github/events')
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
|
||||
//find resourceId from relevant resources table
|
||||
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
|
||||
$repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::limit(100),
|
||||
]));
|
||||
|
||||
// create new deployment only on push (not committed by us) and not when branch is created or deleted
|
||||
if ($providerCommitAuthorEmail !== APP_VCS_GITHUB_EMAIL && !$providerBranchCreated && !$providerBranchDeleted) {
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthorName, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $queueForBuilds, $getProjectDB, $platform);
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthorName, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $request, $platform);
|
||||
}
|
||||
} elseif ($event == $github::EVENT_INSTALLATION) {
|
||||
if ($parsedPayload["action"] == "deleted") {
|
||||
@@ -1536,16 +1538,16 @@ App::post('/v1/vcs/github/events')
|
||||
]);
|
||||
|
||||
foreach ($installations as $installation) {
|
||||
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
|
||||
$repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
|
||||
Query::equal('installationInternalId', [$installation->getSequence()]),
|
||||
Query::limit(1000)
|
||||
]));
|
||||
|
||||
foreach ($repositories as $repository) {
|
||||
Authorization::skip(fn () => $dbForPlatform->deleteDocument('repositories', $repository->getId()));
|
||||
$authorization->skip(fn () => $dbForPlatform->deleteDocument('repositories', $repository->getId()));
|
||||
}
|
||||
|
||||
Authorization::skip(fn () => $dbForPlatform->deleteDocument('installations', $installation->getId()));
|
||||
$authorization->skip(fn () => $dbForPlatform->deleteDocument('installations', $installation->getId()));
|
||||
}
|
||||
}
|
||||
} elseif ($event == $github::EVENT_PULL_REQUEST) {
|
||||
@@ -1574,12 +1576,12 @@ App::post('/v1/vcs/github/events')
|
||||
$providerCommitAuthor = $commitDetails["commitAuthor"] ?? '';
|
||||
$providerCommitMessage = $commitDetails["commitMessage"] ?? '';
|
||||
|
||||
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
|
||||
$repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::orderDesc('$createdAt')
|
||||
]));
|
||||
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForPlatform, $queueForBuilds, $getProjectDB, $platform);
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $request, $platform);
|
||||
} elseif ($parsedPayload["action"] == "closed") {
|
||||
// Allowed external contributions cleanup
|
||||
|
||||
@@ -1588,7 +1590,7 @@ App::post('/v1/vcs/github/events')
|
||||
$external = $parsedPayload["external"] ?? true;
|
||||
|
||||
if ($external) {
|
||||
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
|
||||
$repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::orderDesc('$createdAt')
|
||||
]));
|
||||
@@ -1599,7 +1601,7 @@ App::post('/v1/vcs/github/events')
|
||||
if (\in_array($providerPullRequestId, $providerPullRequestIds)) {
|
||||
$providerPullRequestIds = \array_diff($providerPullRequestIds, [$providerPullRequestId]);
|
||||
$repository = $repository->setAttribute('providerPullRequestIds', $providerPullRequestIds);
|
||||
$repository = Authorization::skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
|
||||
$repository = $authorization->skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1786,17 +1788,18 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForBuilds')
|
||||
->inject('platform')
|
||||
->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Response $response, Document $project, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds, array $platform) use ($createGitDeployments) {
|
||||
->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForPlatform, Authorization $authorization, callable $getProjectDB, Build $queueForBuilds, array $platform) use ($createGitDeployments) {
|
||||
$installation = $dbForPlatform->getDocument('installations', $installationId);
|
||||
|
||||
if ($installation->isEmpty()) {
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$repository = Authorization::skip(fn () => $dbForPlatform->findOne('repositories', [
|
||||
$repository = $authorization->skip(fn () => $dbForPlatform->findOne('repositories', [
|
||||
Query::equal('$id', [$repositoryId]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()])
|
||||
]));
|
||||
@@ -1814,7 +1817,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
|
||||
|
||||
// TODO: Delete from array when PR is closed
|
||||
|
||||
$repository = Authorization::skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
|
||||
$repository = $authorization->skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
|
||||
|
||||
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
@@ -1846,7 +1849,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
|
||||
$providerCommitMessage = $pullRequestResponse['title'] ?? '';
|
||||
$providerCommitUrl = $pullRequestResponse['html_url'] ?? '';
|
||||
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, true, $dbForPlatform, $queueForBuilds, $getProjectDB, $platform);
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, '', '', '', '', $providerCommitHash, '', '', '', '', $providerPullRequestId, true, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $request, $platform);
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
+202
-94
@@ -6,6 +6,7 @@ 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;
|
||||
@@ -59,7 +60,7 @@ Config::setParam('domainVerification', false);
|
||||
Config::setParam('cookieDomain', 'localhost');
|
||||
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
|
||||
|
||||
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey)
|
||||
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey, DeleteEvent $queueForDeletes, int $executionsRetentionCount)
|
||||
{
|
||||
$host = $request->getHostname() ?? '';
|
||||
if (!empty($previewHostname)) {
|
||||
@@ -67,16 +68,16 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
}
|
||||
|
||||
// TODO: (@Meldiron) Remove after 1.7.x migration
|
||||
$isMd5 = System::getEnv('_APP_RULES_FORMAT') === 'md5';
|
||||
$rule = Authorization::skip(function () use ($dbForPlatform, $host, $isMd5) {
|
||||
if ($isMd5) {
|
||||
return $dbForPlatform->getDocument('rules', md5($host));
|
||||
}
|
||||
|
||||
return $dbForPlatform->findOne('rules', [
|
||||
Query::equal('domain', [$host]),
|
||||
]) ?? new Document();
|
||||
});
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$rule = $authorization->skip(fn () => $dbForPlatform->getDocument('rules', md5($host)));
|
||||
} else {
|
||||
$rule = $authorization->skip(
|
||||
fn () => $dbForPlatform->find('rules', [
|
||||
Query::equal('domain', [$host]),
|
||||
Query::limit(1)
|
||||
])
|
||||
)[0] ?? new Document();
|
||||
}
|
||||
|
||||
$errorView = __DIR__ . '/../views/general/error.phtml';
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
@@ -111,7 +112,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
}
|
||||
|
||||
$projectId = $rule->getAttribute('projectId');
|
||||
$project = Authorization::skip(
|
||||
$project = $authorization->skip(
|
||||
fn () => $dbForPlatform->getDocument('projects', $projectId)
|
||||
);
|
||||
|
||||
@@ -119,7 +120,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
$accessedAt = $project->getAttribute('accessedAt', 0);
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
|
||||
$project->setAttribute('accessedAt', DateTime::now());
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,7 +159,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
|
||||
/** @var Document $deployment */
|
||||
if (!empty($rule->getAttribute('deploymentId', ''))) {
|
||||
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('deploymentId')));
|
||||
$deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('deploymentId')));
|
||||
} else {
|
||||
// 1.6.x DB schema compatibility
|
||||
// TODO: Make sure deploymentId is never empty, and remove this code
|
||||
@@ -172,15 +173,15 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
|
||||
// Document of site or function
|
||||
$resource = $resourceType === 'function' ?
|
||||
Authorization::skip(fn () => $dbForProject->getDocument('functions', $resourceId)) :
|
||||
Authorization::skip(fn () => $dbForProject->getDocument('sites', $resourceId));
|
||||
$authorization->skip(fn () => $dbForProject->getDocument('functions', $resourceId)) :
|
||||
$authorization->skip(fn () => $dbForProject->getDocument('sites', $resourceId));
|
||||
|
||||
// ID of active deployments
|
||||
// Attempts to use attribute from both schemas (1.6 and 1.7)
|
||||
$activeDeploymentId = $resource->getAttribute('deploymentId', $resource->getAttribute('deployment', ''));
|
||||
|
||||
// Get deployment document, as intended originally
|
||||
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $activeDeploymentId));
|
||||
$deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $activeDeploymentId));
|
||||
}
|
||||
|
||||
if ($deployment->getAttribute('resourceType', '') === 'functions') {
|
||||
@@ -199,8 +200,8 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
}
|
||||
|
||||
$resource = $type === 'function' ?
|
||||
Authorization::skip(fn () => $dbForProject->getDocument('functions', $deployment->getAttribute('resourceId', ''))) :
|
||||
Authorization::skip(fn () => $dbForProject->getDocument('sites', $deployment->getAttribute('resourceId', '')));
|
||||
$authorization->skip(fn () => $dbForProject->getDocument('functions', $deployment->getAttribute('resourceId', ''))) :
|
||||
$authorization->skip(fn () => $dbForProject->getDocument('sites', $deployment->getAttribute('resourceId', '')));
|
||||
|
||||
$isPreview = $type === 'function' ? false : ($rule->getAttribute('trigger', '') !== 'manual');
|
||||
|
||||
@@ -242,7 +243,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
$userExists = false;
|
||||
$userId = $payload['userId'] ?? '';
|
||||
if (!empty($userId)) {
|
||||
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
if (!$user->isEmpty() && $user->getAttribute('status', false)) {
|
||||
$userExists = true;
|
||||
}
|
||||
@@ -255,7 +256,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');
|
||||
@@ -802,6 +803,20 @@ 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;
|
||||
@@ -812,8 +827,6 @@ 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()
|
||||
@@ -862,15 +875,18 @@ App::init()
|
||||
->inject('devKey')
|
||||
->inject('apiKey')
|
||||
->inject('cors')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Func $queueForFunctions, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors) {
|
||||
->inject('authorization')
|
||||
->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) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
$hostname = $request->getHostname() ?? '';
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
// Only run Router when external domain
|
||||
if (!in_array($hostname, $platformHostnames) || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
|
||||
if (!\in_array($hostname, $platformHostnames) || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
@@ -1033,7 +1049,8 @@ App::init()
|
||||
->inject('dbForPlatform')
|
||||
->inject('queueForCertificates')
|
||||
->inject('platform')
|
||||
->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform) {
|
||||
->inject('authorization')
|
||||
->action(function (Request $request, Document $console, Database $dbForPlatform, Certificate $queueForCertificates, array $platform, Authorization $authorization) {
|
||||
$hostname = $request->getHostname();
|
||||
$cache = Config::getParam('hostnames', []);
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
@@ -1061,64 +1078,64 @@ App::init()
|
||||
}
|
||||
|
||||
// 4. Check/create rule (requires DB access)
|
||||
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()]),
|
||||
$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')
|
||||
]);
|
||||
|
||||
if (!$document->isEmpty()) {
|
||||
return;
|
||||
$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);
|
||||
}
|
||||
|
||||
// 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()
|
||||
@@ -1141,14 +1158,17 @@ App::options()
|
||||
->inject('devKey')
|
||||
->inject('apiKey')
|
||||
->inject('cors')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors) {
|
||||
->inject('authorization')
|
||||
->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) {
|
||||
/*
|
||||
* 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, $apiKey)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
@@ -1182,7 +1202,8 @@ App::error()
|
||||
->inject('log')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('devKey')
|
||||
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, StatsUsage $queueForStatsUsage) {
|
||||
->inject('authorization')
|
||||
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, StatsUsage $queueForStatsUsage, Document $devKey, Authorization $authorization) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
$route = $utopia->getRoute();
|
||||
$class = \get_class($error);
|
||||
@@ -1264,7 +1285,7 @@ App::error()
|
||||
* If not a publishable error, track usage stats. Publishable errors are >= 500 or those explicitly marked as publish=true in errors.php
|
||||
*/
|
||||
if (!$publish && $project->getId() !== 'console') {
|
||||
if (!DBUser::isPrivileged(Authorization::getRoles())) {
|
||||
if (!DBUser::isPrivileged($authorization->getRoles())) {
|
||||
$fileSize = 0;
|
||||
$file = $request->getFiles('file');
|
||||
if (!empty($file)) {
|
||||
@@ -1326,7 +1347,87 @@ App::error()
|
||||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
$log->addExtra('roles', Authorization::getRoles());
|
||||
$log->addExtra('roles', $authorization->getRoles());
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
$action = 'UNKNOWN_NAMESPACE.UNKNOWN.METHOD';
|
||||
if (!empty($sdk)) {
|
||||
@@ -1402,7 +1503,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 (!\str_starts_with($route->getPath(), '/v1')) {
|
||||
if (empty($route) || !\str_starts_with($route->getPath(), '/v1')) {
|
||||
$template = __DIR__ . '/../views/general/error.phtml';
|
||||
}
|
||||
|
||||
@@ -1450,13 +1551,16 @@ App::get('/robots.txt')
|
||||
->inject('platform')
|
||||
->inject('previewHostname')
|
||||
->inject('apiKey')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey) {
|
||||
->inject('authorization')
|
||||
->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) {
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
|
||||
$template = new View(__DIR__ . '/../views/general/robots.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
@@ -1482,13 +1586,16 @@ App::get('/humans.txt')
|
||||
->inject('platform')
|
||||
->inject('previewHostname')
|
||||
->inject('apiKey')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey) {
|
||||
->inject('authorization')
|
||||
->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) {
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
|
||||
$template = new View(__DIR__ . '/../views/general/humans.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $apiKey)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
@@ -1572,7 +1679,8 @@ App::get('/v1/ping')
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
->inject('queueForEvents')
|
||||
->action(function (Response $response, Document $project, Database $dbForPlatform, Event $queueForEvents) {
|
||||
->inject('authorization')
|
||||
->action(function (Response $response, Document $project, Database $dbForPlatform, Event $queueForEvents, Authorization $authorization) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND);
|
||||
}
|
||||
@@ -1584,7 +1692,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);
|
||||
});
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ App::post('/v1/mock/api-key-unprefixed')
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$scopes = array_keys(Config::getParam('scopes'));
|
||||
$scopes = array_keys(Config::getParam('projectScopes'));
|
||||
|
||||
$key = new Document([
|
||||
'$id' => ID::unique(),
|
||||
|
||||
+99
-228
@@ -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\Queue\Publisher;
|
||||
use Utopia\Database\Validator\Authorization\Input;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter as Telemetry;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -74,151 +74,6 @@ $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')
|
||||
@@ -233,7 +88,8 @@ App::init()
|
||||
->inject('mode')
|
||||
->inject('team')
|
||||
->inject('apiKey')
|
||||
->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, User $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey) {
|
||||
->inject('authorization')
|
||||
->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization) {
|
||||
$route = $utopia->getRoute();
|
||||
|
||||
/**
|
||||
@@ -301,10 +157,6 @@ 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);
|
||||
@@ -318,7 +170,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' => '',
|
||||
@@ -333,23 +185,38 @@ App::init()
|
||||
}
|
||||
|
||||
// For standard keys, update last accessed time
|
||||
if ($apiKey->getType() === API_KEY_STANDARD) {
|
||||
$dbKey = $project->find(
|
||||
key: 'secret',
|
||||
find: $request->getHeader('x-appwrite-key', ''),
|
||||
subject: 'keys'
|
||||
);
|
||||
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 (!$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) {
|
||||
$dbKey->setAttribute('accessedAt', DateTime::now());
|
||||
$dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey);
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
$updates->setAttribute('accessedAt', DateTime::now());
|
||||
}
|
||||
|
||||
$sdkValidator = new WhiteList($servers, true);
|
||||
@@ -360,12 +227,21 @@ App::init()
|
||||
|
||||
if (!in_array($sdk, $sdks)) {
|
||||
$sdks[] = $sdk;
|
||||
$dbKey->setAttribute('sdks', $sdks);
|
||||
|
||||
/** Update access time as well */
|
||||
$dbKey->setAttribute('accessedAt', Datetime::now());
|
||||
$dbForPlatform->updateDocument('keys', $dbKey->getId(), $dbKey);
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
$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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,14 +268,14 @@ App::init()
|
||||
$scopes = \array_merge($scopes, $roles[$role]['scopes']);
|
||||
}
|
||||
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
|
||||
$authorization->setDefaultStatus(false); // Cancel security segmentation for admin users.
|
||||
}
|
||||
|
||||
$scopes = \array_unique($scopes);
|
||||
|
||||
Authorization::setRole($role);
|
||||
foreach ($user->getRoles() as $authRole) {
|
||||
Authorization::setRole($authRole);
|
||||
$authorization->addRole($role);
|
||||
foreach ($user->getRoles($authorization) as $authRole) {
|
||||
$authorization->addRole($authRole);
|
||||
}
|
||||
|
||||
// Step 6: Update project and user last activity
|
||||
@@ -407,7 +283,7 @@ App::init()
|
||||
$accessedAt = $project->getAttribute('accessedAt', 0);
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
|
||||
$project->setAttribute('accessedAt', DateTime::now());
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,7 +318,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);
|
||||
}
|
||||
@@ -487,9 +363,6 @@ App::init()
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('publisher')
|
||||
->inject('publisherFunctions')
|
||||
->inject('publisherWebhooks')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMessaging')
|
||||
->inject('queueForAudits')
|
||||
@@ -499,7 +372,6 @@ App::init()
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForMails')
|
||||
->inject('queueForMigrations')
|
||||
->inject('dbForProject')
|
||||
->inject('timelimit')
|
||||
->inject('resourceToken')
|
||||
@@ -509,14 +381,15 @@ App::init()
|
||||
->inject('devKey')
|
||||
->inject('telemetry')
|
||||
->inject('platform')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Mail $queueForMails, Migration $queueForMigrations, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, array $platform) use ($usageDatabaseListener, $eventDatabaseListener) {
|
||||
->inject('authorization')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, 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) {
|
||||
|
||||
$route = $utopia->getRoute();
|
||||
|
||||
if (
|
||||
array_key_exists('rest', $project->getAttribute('apis', []))
|
||||
&& !$project->getAttribute('apis', [])['rest']
|
||||
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles()))
|
||||
&& !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
@@ -546,7 +419,7 @@ App::init()
|
||||
|
||||
$closestLimit = null;
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$roles = $authorization->getRoles();
|
||||
$isPrivilegedUser = User::isPrivileged($roles);
|
||||
$isAppUser = User::isApp($roles);
|
||||
|
||||
@@ -629,38 +502,16 @@ 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())
|
||||
);
|
||||
@@ -677,10 +528,10 @@ App::init()
|
||||
|
||||
if ($type === 'bucket' && (!$isImageTransformation || !$isDisabled)) {
|
||||
$bucketId = $parts[1] ?? null;
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAppUser && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
@@ -691,8 +542,7 @@ App::init()
|
||||
}
|
||||
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
$valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
|
||||
if (!$fileSecurity && !$valid && !$isToken) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
@@ -703,7 +553,7 @@ App::init()
|
||||
if ($fileSecurity && !$valid && !$isToken) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
|
||||
} else {
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
}
|
||||
|
||||
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) {
|
||||
@@ -714,11 +564,11 @@ App::init()
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
//Do not update transformedAt if it's a console user
|
||||
if (!User::isPrivileged(Authorization::getRoles())) {
|
||||
if (!User::isPrivileged($authorization->getRoles())) {
|
||||
$transformedAt = $file->getAttribute('transformedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
|
||||
$file->setAttribute('transformedAt', DateTime::now());
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -814,8 +664,10 @@ App::shutdown()
|
||||
->inject('queueForWebhooks')
|
||||
->inject('queueForRealtime')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('timelimit')
|
||||
->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) {
|
||||
->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) {
|
||||
|
||||
$responsePayload = $response->getPayload();
|
||||
|
||||
@@ -824,9 +676,15 @@ App::shutdown()
|
||||
$queueForEvents->setPayload($responsePayload);
|
||||
}
|
||||
|
||||
$queueForFunctions
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
// 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()
|
||||
);
|
||||
|
||||
if ($project->getId() !== 'console') {
|
||||
$queueForRealtime
|
||||
@@ -834,15 +692,28 @@ App::shutdown()
|
||||
->trigger();
|
||||
}
|
||||
|
||||
/** 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();
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -976,11 +847,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,
|
||||
@@ -990,7 +861,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']);
|
||||
}
|
||||
@@ -1002,7 +873,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)) {
|
||||
|
||||
@@ -36,7 +36,8 @@ App::init()
|
||||
->inject('request')
|
||||
->inject('project')
|
||||
->inject('geodb')
|
||||
->action(function (App $utopia, Request $request, Document $project, Reader $geodb) {
|
||||
->inject('authorization')
|
||||
->action(function (App $utopia, Request $request, Document $project, Reader $geodb, Authorization $authorization) {
|
||||
$denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', '');
|
||||
if (!empty($denylist && $project->getId() === 'console')) {
|
||||
$countries = explode(',', $denylist);
|
||||
@@ -49,8 +50,8 @@ App::init()
|
||||
|
||||
$route = $utopia->match($request);
|
||||
|
||||
$isPrivilegedUser = User::isPrivileged(Authorization::getRoles());
|
||||
$isAppUser = User::isApp(Authorization::getRoles());
|
||||
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
|
||||
$isAppUser = User::isApp($authorization->getRoles());
|
||||
|
||||
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
|
||||
return;
|
||||
|
||||
+17
-11
@@ -27,7 +27,6 @@ use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Log\User;
|
||||
use Utopia\Pools\Group;
|
||||
@@ -261,7 +260,9 @@ $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) {
|
||||
createDatabase($app, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections, $app) {
|
||||
$authorization = $app->getResource('authorization');
|
||||
|
||||
if ($dbForPlatform->getCollection(AuditAdapterSQL::COLLECTION)->isEmpty()) {
|
||||
$adapter = new AdapterDatabase($dbForPlatform);
|
||||
$audit = new Audit($adapter);
|
||||
@@ -321,9 +322,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
$dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes);
|
||||
}
|
||||
|
||||
if (Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')->isEmpty())) {
|
||||
if ($authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')->isEmpty())) {
|
||||
Console::info(" └── Creating screenshots bucket...");
|
||||
Authorization::skip(fn () => $dbForPlatform->createDocument('buckets', new Document([
|
||||
$authorization->skip(fn () => $dbForPlatform->createDocument('buckets', new Document([
|
||||
'$id' => ID::custom('screenshots'),
|
||||
'$collection' => ID::custom('buckets'),
|
||||
'name' => 'Screenshots',
|
||||
@@ -338,7 +339,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
'search' => 'buckets Screenshots',
|
||||
])));
|
||||
|
||||
$bucket = Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots'));
|
||||
$bucket = $authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots'));
|
||||
|
||||
Console::info(" └── Creating files collection for screenshots bucket...");
|
||||
$files = $collections['buckets']['files'] ?? [];
|
||||
@@ -366,7 +367,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));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -458,8 +459,12 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
App::setResource('pools', fn () => $pools);
|
||||
|
||||
try {
|
||||
Authorization::cleanRoles();
|
||||
Authorization::setRole(Role::any()->toString());
|
||||
$authorization = $app->getResource('authorization');
|
||||
|
||||
$request->setAuthorization($authorization);
|
||||
$response->setAuthorization($authorization);
|
||||
$authorization->cleanRoles();
|
||||
$authorization->addRole(Role::any()->toString());
|
||||
|
||||
$app->run($request, $response);
|
||||
} catch (\Throwable $th) {
|
||||
@@ -501,7 +506,7 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
$log->addExtra('file', $th->getFile());
|
||||
$log->addExtra('line', $th->getLine());
|
||||
$log->addExtra('trace', $th->getTraceAsString());
|
||||
$log->addExtra('roles', Authorization::getRoles());
|
||||
$log->addExtra('roles', isset($authorization) ? $authorization->getRoles() : []);
|
||||
|
||||
$sdk = $route->getLabel("sdk", false);
|
||||
|
||||
@@ -560,7 +565,7 @@ $http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
$dbForPlatform = $app->getResource('dbForPlatform');
|
||||
|
||||
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $domains, &$lastSyncUpdate) {
|
||||
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $domains, &$lastSyncUpdate, $app) {
|
||||
try {
|
||||
$time = DateTime::now();
|
||||
$limit = 1000;
|
||||
@@ -577,7 +582,8 @@ $http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
|
||||
}
|
||||
$results = [];
|
||||
try {
|
||||
$results = Authorization::skip(fn () => $dbForPlatform->find('rules', $queries));
|
||||
$authorization = $app->getResource('authorization');
|
||||
$results = $authorization->skip(fn () => $dbForPlatform->find('rules', $queries));
|
||||
} catch (Throwable $th) {
|
||||
Console::error($th->getMessage());
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@ 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('scopes', __DIR__ . '/../config/scopes.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('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
|
||||
|
||||
+10
-2
@@ -161,6 +161,9 @@ 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;
|
||||
@@ -178,8 +181,10 @@ const BUILD_TYPE_DEPLOYMENT = 'deployment';
|
||||
const BUILD_TYPE_RETRY = 'retry';
|
||||
|
||||
// Deletion Types
|
||||
const DELETE_TYPE_DATABASES = 'databases';
|
||||
|
||||
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';
|
||||
@@ -189,8 +194,10 @@ 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';
|
||||
@@ -243,6 +250,8 @@ 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';
|
||||
@@ -360,7 +369,6 @@ 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';
|
||||
|
||||
@@ -4,7 +4,6 @@ use Appwrite\OpenSSL\OpenSSL;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\System\System;
|
||||
|
||||
Database::addFilter(
|
||||
@@ -70,11 +69,11 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
$attributes = $database->find('attributes', [
|
||||
$attributes = $database->getAuthorization()->skip(fn () => $database->find('attributes', [
|
||||
Query::equal('collectionInternalId', [$document->getSequence()]),
|
||||
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
|
||||
Query::limit($database->getLimitForAttributes()),
|
||||
]);
|
||||
]));
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
$attributeType = $attribute->getAttribute('type');
|
||||
@@ -105,12 +104,12 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('indexes', [
|
||||
Query::equal('collectionInternalId', [$document->getSequence()]),
|
||||
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
|
||||
Query::limit($database->getLimitForIndexes()),
|
||||
]);
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -120,11 +119,11 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('platforms', [
|
||||
Query::equal('projectInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -134,12 +133,12 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('keys', [
|
||||
Query::equal('resourceType', ['projects']),
|
||||
Query::equal('resourceInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -149,11 +148,11 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('devKeys', [
|
||||
Query::equal('projectInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -163,11 +162,11 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('webhooks', [
|
||||
Query::equal('projectInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -177,7 +176,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database->find('sessions', [
|
||||
return $database->getAuthorization()->skip(fn () => $database->find('sessions', [
|
||||
Query::equal('userInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
@@ -190,7 +189,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('tokens', [
|
||||
Query::equal('userInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
@@ -204,7 +203,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('challenges', [
|
||||
Query::equal('userInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
@@ -218,7 +217,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('authenticators', [
|
||||
Query::equal('userInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
@@ -232,7 +231,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('memberships', [
|
||||
Query::equal('userInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
@@ -252,14 +251,14 @@ Database::addFilter(
|
||||
default => ['function', 'site']
|
||||
};
|
||||
|
||||
return $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('variables', [
|
||||
Query::equal('resourceInternalId', [$document->getSequence()]),
|
||||
Query::equal('resourceType', $resourceType),
|
||||
Query::orderAsc('resourceType'),
|
||||
Query::orderAsc(),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]);
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -295,11 +294,11 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('variables', [
|
||||
Query::equal('resourceType', ['project']),
|
||||
Query::limit(APP_LIMIT_SUBQUERY)
|
||||
]);
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
@@ -332,7 +331,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('targets', [
|
||||
Query::equal('userInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY)
|
||||
@@ -346,7 +345,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
$targetIds = Authorization::skip(fn () => \array_map(
|
||||
$targetIds = $database->getAuthorization()->skip(fn () => \array_map(
|
||||
fn ($document) => $document->getAttribute('targetInternalId'),
|
||||
$database->find('subscribers', [
|
||||
Query::equal('topicInternalId', [$document->getSequence()]),
|
||||
@@ -434,3 +433,34 @@ 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),
|
||||
]));
|
||||
}
|
||||
);
|
||||
|
||||
+1
-23
@@ -94,13 +94,6 @@ 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;
|
||||
@@ -184,7 +177,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, false));
|
||||
Response::setModel(new BaseList('API Keys List', Response::MODEL_KEY_LIST, 'keys', Response::MODEL_KEY, true, true));
|
||||
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));
|
||||
@@ -347,20 +340,5 @@ 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());
|
||||
|
||||
@@ -324,6 +324,12 @@ $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;
|
||||
});
|
||||
|
||||
@@ -388,8 +394,3 @@ $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;
|
||||
});
|
||||
|
||||
+341
-55
@@ -15,10 +15,12 @@ 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;
|
||||
@@ -82,9 +84,6 @@ 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'));
|
||||
@@ -132,6 +131,9 @@ 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']);
|
||||
@@ -156,6 +158,9 @@ 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']);
|
||||
@@ -229,7 +234,7 @@ App::setResource('allowedSchemes', function (Document $project) {
|
||||
/**
|
||||
* Rule associated with a request origin.
|
||||
*/
|
||||
App::setResource('rule', function (Request $request, Database $dbForPlatform, Document $project) {
|
||||
App::setResource('rule', function (Request $request, Database $dbForPlatform, Document $project, Authorization $authorization) {
|
||||
$domain = \parse_url($request->getOrigin(), PHP_URL_HOST);
|
||||
if (empty($domain)) {
|
||||
return new Document();
|
||||
@@ -237,7 +242,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));
|
||||
}
|
||||
@@ -252,7 +257,7 @@ App::setResource('rule', function (Request $request, Database $dbForPlatform, Do
|
||||
}
|
||||
|
||||
return $rule;
|
||||
}, ['request', 'dbForPlatform', 'project']);
|
||||
}, ['request', 'dbForPlatform', 'project', 'authorization']);
|
||||
|
||||
/**
|
||||
* CORS service
|
||||
@@ -320,7 +325,7 @@ App::setResource('redirectValidator', function (Document $devKey, array $allowed
|
||||
return new Redirect($allowedHostnames, $allowedSchemes);
|
||||
}, ['devKey', 'allowedHostnames', 'allowedSchemes']);
|
||||
|
||||
App::setResource('user', function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken) {
|
||||
App::setResource('user', function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken, $authorization) {
|
||||
/**
|
||||
* Handles user authentication and session validation.
|
||||
*
|
||||
@@ -338,9 +343,10 @@ 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());
|
||||
|
||||
@@ -407,19 +413,24 @@ 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) {
|
||||
@@ -435,13 +446,41 @@ 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']);
|
||||
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken', 'authorization']);
|
||||
|
||||
App::setResource('project', function ($dbForPlatform, $request, $console) {
|
||||
App::setResource('project', function ($dbForPlatform, $request, $console, $authorization) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
/** @var Utopia\Database\Document $console */
|
||||
@@ -452,10 +491,10 @@ App::setResource('project', function ($dbForPlatform, $request, $console) {
|
||||
return $console;
|
||||
}
|
||||
|
||||
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
|
||||
|
||||
return $project;
|
||||
}, ['dbForPlatform', 'request', 'console']);
|
||||
}, ['dbForPlatform', 'request', 'console', 'authorization']);
|
||||
|
||||
App::setResource('session', function (User $user, Store $store, Token $proofForToken) {
|
||||
if ($user->isEmpty()) {
|
||||
@@ -478,10 +517,6 @@ App::setResource('session', function (User $user, Store $store, Token $proofForT
|
||||
return;
|
||||
}, ['user', 'store', 'proofForToken']);
|
||||
|
||||
App::setResource('console', function () {
|
||||
return new Document(Config::getParam('console'));
|
||||
}, []);
|
||||
|
||||
App::setResource('store', function (): Store {
|
||||
return new Store();
|
||||
});
|
||||
@@ -512,7 +547,15 @@ App::setResource('proofForCode', function (): Code {
|
||||
return $code;
|
||||
});
|
||||
|
||||
App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project) {
|
||||
App::setResource('console', function () {
|
||||
return new Document(Config::getParam('console'));
|
||||
}, []);
|
||||
|
||||
App::setResource('authorization', function () {
|
||||
return new Authorization();
|
||||
}, []);
|
||||
|
||||
App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project, Response $response, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Func $queueForFunctions, Webhook $queueForWebhooks, Realtime $queueForRealtime, StatsUsage $queueForStatsUsage, Authorization $authorization) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForPlatform;
|
||||
}
|
||||
@@ -528,6 +571,8 @@ 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)
|
||||
@@ -548,14 +593,218 @@ 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']);
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'project', 'response', 'publisher', 'publisherFunctions', 'publisherWebhooks', 'queueForEvents', 'queueForFunctions', 'queueForWebhooks', 'queueForRealtime', 'queueForStatsUsage', 'authorization']);
|
||||
|
||||
App::setResource('dbForPlatform', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
|
||||
App::setResource('dbForPlatform', function (Group $pools, Cache $cache) {
|
||||
$adapter = new DatabasePool($pools->get('console'));
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', 'console')
|
||||
@@ -565,12 +814,12 @@ App::setResource('dbForPlatform', function (Group $pools, Cache $cache) {
|
||||
$database->setDocumentType('users', User::class);
|
||||
|
||||
return $database;
|
||||
}, ['pools', 'cache']);
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
|
||||
App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, Authorization $authorization) {
|
||||
$databases = [];
|
||||
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) {
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForPlatform;
|
||||
}
|
||||
@@ -582,13 +831,16 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
|
||||
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
|
||||
}
|
||||
|
||||
$configure = (function (Database $database) use ($project, $dsn) {
|
||||
$configure = (function (Database $database) use ($project, $dsn, $authorization) {
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId())
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
|
||||
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
|
||||
$database->setDocumentType('users', User::class);
|
||||
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES)
|
||||
->setDocumentType('users', User::class)
|
||||
;
|
||||
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
@@ -618,12 +870,12 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'dbForPlatform', 'cache']);
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
|
||||
|
||||
App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
App::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$database = null;
|
||||
|
||||
return function (?Document $project = null) use ($pools, $cache, &$database) {
|
||||
return function (?Document $project = null) use ($pools, $cache, $authorization, &$database) {
|
||||
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$database->setTenant((int) $project->getSequence());
|
||||
return $database;
|
||||
@@ -633,6 +885,8 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setSharedTables(true)
|
||||
->setNamespace('logsV1')
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
|
||||
@@ -645,7 +899,7 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'cache']);
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
App::setResource('audit', function ($dbForProject) {
|
||||
$adapter = new AdapterDatabase($dbForProject);
|
||||
@@ -844,7 +1098,7 @@ App::setResource('promiseAdapter', function ($register) {
|
||||
return $register->get('promiseAdapter');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('schema', function ($utopia, $dbForProject) {
|
||||
App::setResource('schema', function ($utopia, $dbForProject, $authorization) {
|
||||
|
||||
$complexity = function (int $complexity, array $args) {
|
||||
$queries = Query::parseQueries($args['queries'] ?? []);
|
||||
@@ -854,8 +1108,8 @@ App::setResource('schema', function ($utopia, $dbForProject) {
|
||||
return $complexity * $limit;
|
||||
};
|
||||
|
||||
$attributes = function (int $limit, int $offset) use ($dbForProject) {
|
||||
$attrs = Authorization::skip(fn () => $dbForProject->find('attributes', [
|
||||
$attributes = function (int $limit, int $offset) use ($dbForProject, $authorization) {
|
||||
$attrs = $authorization->skip(fn () => $dbForProject->find('attributes', [
|
||||
Query::limit($limit),
|
||||
Query::offset($offset),
|
||||
]));
|
||||
@@ -929,7 +1183,7 @@ App::setResource('schema', function ($utopia, $dbForProject) {
|
||||
$urls,
|
||||
$params,
|
||||
);
|
||||
}, ['utopia', 'dbForProject']);
|
||||
}, ['utopia', 'dbForProject', 'authorization']);
|
||||
|
||||
App::setResource('gitHub', function (Cache $cache) {
|
||||
return new VcsGitHub($cache);
|
||||
@@ -957,7 +1211,7 @@ App::setResource('smsRates', function () {
|
||||
return [];
|
||||
});
|
||||
|
||||
App::setResource('devKey', function (Request $request, Document $project, array $servers, Database $dbForPlatform) {
|
||||
App::setResource('devKey', function (Request $request, Document $project, array $servers, Database $dbForPlatform, Authorization $authorization) {
|
||||
$devKey = $request->getHeader('x-appwrite-dev-key', $request->getParam('devKey', ''));
|
||||
|
||||
// Check if given key match project's development keys
|
||||
@@ -976,7 +1230,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());
|
||||
}
|
||||
|
||||
@@ -993,25 +1247,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']);
|
||||
}, ['request', 'project', 'servers', 'dbForPlatform', 'authorization']);
|
||||
|
||||
App::setResource('team', function (Document $project, Database $dbForPlatform, App $utopia, Request $request) {
|
||||
App::setResource('team', function (Document $project, Database $dbForPlatform, App $utopia, Request $request, Authorization $authorization) {
|
||||
$teamInternalId = '';
|
||||
if ($project->getId() !== 'console') {
|
||||
$teamInternalId = $project->getAttribute('teamInternalId', '');
|
||||
} else {
|
||||
$route = $utopia->match($request);
|
||||
$path = $route->getPath();
|
||||
$path = !empty($route) ? $route->getPath() : $request->getURI();
|
||||
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', '');
|
||||
@@ -1020,7 +1274,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;
|
||||
}
|
||||
}
|
||||
@@ -1029,14 +1283,14 @@ App::setResource('team', function (Document $project, Database $dbForPlatform, A
|
||||
return new Document([]);
|
||||
}
|
||||
|
||||
$team = Authorization::skip(function () use ($dbForPlatform, $teamInternalId) {
|
||||
$team = $authorization->skip(function () use ($dbForPlatform, $teamInternalId) {
|
||||
return $dbForPlatform->findOne('teams', [
|
||||
Query::equal('$sequence', [$teamInternalId]),
|
||||
]);
|
||||
});
|
||||
|
||||
return $team;
|
||||
}, ['project', 'dbForPlatform', 'utopia', 'request']);
|
||||
}, ['project', 'dbForPlatform', 'utopia', 'request', 'authorization']);
|
||||
|
||||
App::setResource(
|
||||
'isResourceBlocked',
|
||||
@@ -1062,19 +1316,43 @@ App::setResource('previewHostname', function (Request $request, ?Key $apiKey) {
|
||||
return '';
|
||||
}, ['request', 'apiKey']);
|
||||
|
||||
App::setResource('apiKey', function (Request $request, Document $project): ?Key {
|
||||
App::setResource('apiKey', function (Request $request, Document $project, Document $team, Document $user): ?Key {
|
||||
$key = $request->getHeader('x-appwrite-key');
|
||||
|
||||
if (empty($key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Key::decode($project, $key);
|
||||
}, ['request', 'project']);
|
||||
$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']);
|
||||
|
||||
App::setResource('executor', fn () => new Executor());
|
||||
|
||||
App::setResource('resourceToken', function ($project, $dbForProject, $request) {
|
||||
App::setResource('resourceToken', function ($project, $dbForProject, $request, Authorization $authorization) {
|
||||
$tokenJWT = $request->getParam('token');
|
||||
|
||||
if (!empty($tokenJWT) && !$project->isEmpty()) { // JWT authentication
|
||||
@@ -1092,7 +1370,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
|
||||
return new Document([]);
|
||||
}
|
||||
|
||||
$token = Authorization::skip(fn () => $dbForProject->getDocument('resourceTokens', $tokenId));
|
||||
$token = $authorization->skip(fn () => $dbForProject->getDocument('resourceTokens', $tokenId));
|
||||
|
||||
if ($token->isEmpty()) {
|
||||
return new Document([]);
|
||||
@@ -1110,7 +1388,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
|
||||
}
|
||||
|
||||
return match ($token->getAttribute('resourceType')) {
|
||||
TOKENS_RESOURCE_TYPE_FILES => (function () use ($token, $dbForProject) {
|
||||
TOKENS_RESOURCE_TYPE_FILES => (function () use ($token, $dbForProject, $authorization) {
|
||||
$sequences = explode(':', $token->getAttribute('resourceInternalId'));
|
||||
$ids = explode(':', $token->getAttribute('resourceId'));
|
||||
|
||||
@@ -1121,7 +1399,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
|
||||
$accessedAt = $token->getAttribute('accessedAt', 0);
|
||||
if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), -APP_RESOURCE_TOKEN_ACCESS)) > $accessedAt) {
|
||||
$token->setAttribute('accessedAt', DatabaseDateTime::now());
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('resourceTokens', $token->getId(), $token));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('resourceTokens', $token->getId(), $token));
|
||||
}
|
||||
|
||||
return new Document([
|
||||
@@ -1136,8 +1414,16 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
|
||||
};
|
||||
}
|
||||
return new Document([]);
|
||||
}, ['project', 'dbForProject', 'request']);
|
||||
}, ['project', 'dbForProject', 'request', 'authorization']);
|
||||
|
||||
App::setResource('transactionState', function (Database $dbForProject) {
|
||||
return new TransactionState($dbForProject);
|
||||
}, ['dbForProject']);
|
||||
App::setResource('transactionState', function (Database $dbForProject, Authorization $authorization) {
|
||||
return new TransactionState($dbForProject, $authorization);
|
||||
}, ['dbForProject', 'authorization']);
|
||||
|
||||
App::setResource('executionsRetentionCount', function (Document $project, array $plan) {
|
||||
if ($project->getId() === 'console' || empty($plan)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int) ($plan['executionsRetentionCount'] ?? 100);
|
||||
}, ['project', 'plan']);
|
||||
|
||||
+44
-14
@@ -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,6 +67,7 @@ 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');
|
||||
@@ -123,6 +124,7 @@ if (!function_exists('getProjectDB')) {
|
||||
}
|
||||
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId());
|
||||
|
||||
@@ -309,7 +311,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
||||
'value' => '{}'
|
||||
]);
|
||||
|
||||
$statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document));
|
||||
$statsDocument = $database->getAuthorization()->skip(fn () => $database->createDocument('realtime', $document));
|
||||
break;
|
||||
} catch (Throwable) {
|
||||
Console::warning("Collection not ready. Retrying connection ({$attempts})...");
|
||||
@@ -339,7 +341,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
||||
->setAttribute('timestamp', DateTime::now())
|
||||
->setAttribute('value', json_encode($payload));
|
||||
|
||||
Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
|
||||
$database->getAuthorization()->skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
|
||||
} catch (Throwable $th) {
|
||||
$logError($th, "updateWorkerDocument");
|
||||
}
|
||||
@@ -370,7 +372,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
|
||||
$payload = [];
|
||||
|
||||
$list = Authorization::skip(fn () => $database->find('realtime', [
|
||||
$list = $database->getAuthorization()->skip(fn () => $database->find('realtime', [
|
||||
Query::greaterThan('timestamp', DateTime::addSeconds(new \DateTime(), -15)),
|
||||
]));
|
||||
|
||||
@@ -464,17 +466,18 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
if ($realtime->hasSubscriber($projectId, 'user:' . $userId)) {
|
||||
$connection = array_key_first(reset($realtime->subscriptions[$projectId]['user:' . $userId]));
|
||||
$consoleDatabase = getConsoleDB();
|
||||
$project = Authorization::skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
|
||||
$project = $consoleDatabase->getAuthorization()->skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
|
||||
$database = getProjectDB($project);
|
||||
|
||||
/** @var Appwrite\Utopia\Database\Documents\User $user */
|
||||
$user = $database->getDocument('users', $userId);
|
||||
|
||||
$roles = $user->getRoles();
|
||||
$roles = $user->getRoles($database->getAuthorization());
|
||||
$channels = $realtime->connections[$connection]['channels'];
|
||||
$queries = $realtime->connections[$connection]['queries'] ?? [];
|
||||
|
||||
$realtime->unsubscribe($connection);
|
||||
$realtime->subscribe($projectId, $connection, $roles, $channels);
|
||||
$realtime->subscribe($projectId, $connection, $roles, $channels, $queries);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,6 +529,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
try {
|
||||
/** @var Document $project */
|
||||
$project = $app->getResource('project');
|
||||
$authorization = $app->getResource('authorization');
|
||||
|
||||
/*
|
||||
* Project Check
|
||||
@@ -537,7 +541,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
if (
|
||||
array_key_exists('realtime', $project->getAttribute('apis', []))
|
||||
&& !$project->getAttribute('apis', [])['realtime']
|
||||
&& !(User::isPrivileged(Authorization::getRoles()) || User::isApp(Authorization::getRoles()))
|
||||
&& !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
@@ -573,9 +577,14 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
|
||||
}
|
||||
|
||||
$roles = $user->getRoles();
|
||||
$roles = $user->getRoles($authorization);
|
||||
|
||||
$channels = Realtime::convertChannels($request->getQuery('channels', []), $user->getId());
|
||||
try {
|
||||
$queries = Realtime::convertQueries($request->getQuery('queries', []));
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $e->getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Channels Check
|
||||
@@ -584,7 +593,9 @@ $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);
|
||||
$realtime->subscribe($project->getId(), $connection, $roles, $channels, $queries);
|
||||
|
||||
$realtime->connections[$connection]['authorization'] = $authorization;
|
||||
|
||||
$user = empty($user->getId()) ? null : $response->output($user, Response::MODEL_ACCOUNT);
|
||||
|
||||
@@ -592,6 +603,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
'type' => 'connected',
|
||||
'data' => [
|
||||
'channels' => array_keys($channels),
|
||||
'queries' => $queries,
|
||||
'user' => $user
|
||||
]
|
||||
]));
|
||||
@@ -614,6 +626,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
$code = 500;
|
||||
}
|
||||
|
||||
|
||||
$message = $th->getMessage();
|
||||
|
||||
// sanitize 0 && 5xx errors
|
||||
@@ -643,12 +656,19 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) {
|
||||
try {
|
||||
$response = new Response(new SwooleResponse());
|
||||
$projectId = $realtime->connections[$connection]['projectId'];
|
||||
$projectId = $realtime->connections[$connection]['projectId'] ?? null;
|
||||
|
||||
// Get authorization from connection (stored during onOpen)
|
||||
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
|
||||
|
||||
$database = getConsoleDB();
|
||||
$database->setAuthorization($authorization);
|
||||
|
||||
if ($projectId !== 'console') {
|
||||
$project = Authorization::skip(fn () => $database->getDocument('projects', $projectId));
|
||||
$project = $authorization->skip(fn () => $database->getDocument('projects', $projectId));
|
||||
|
||||
$database = getProjectDB($project);
|
||||
$database->setAuthorization($authorization);
|
||||
} else {
|
||||
$project = null;
|
||||
}
|
||||
@@ -712,9 +732,19 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.');
|
||||
}
|
||||
|
||||
$roles = $user->getRoles();
|
||||
$roles = $user->getRoles($database->getAuthorization());
|
||||
$channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId());
|
||||
$realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
$user = $response->output($user, Response::MODEL_ACCOUNT);
|
||||
$server->send([$connection], json_encode([
|
||||
|
||||
@@ -165,6 +165,7 @@ $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
|
||||
|
||||
+52
-27
@@ -14,6 +14,7 @@ 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;
|
||||
@@ -48,19 +49,31 @@ use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter as Telemetry;
|
||||
use Utopia\Telemetry\Adapter\None as NoTelemetry;
|
||||
|
||||
Authorization::disable();
|
||||
Runtime::enableCoroutine();
|
||||
|
||||
Server::setResource('register', fn () => $register);
|
||||
|
||||
Server::setResource('dbForPlatform', function (Cache $cache, Registry $register) {
|
||||
Server::setResource('authorization', function () {
|
||||
$authorization = new Authorization();
|
||||
$authorization->disable();
|
||||
return $authorization;
|
||||
}, []);
|
||||
|
||||
Server::setResource('dbForPlatform', function (Cache $cache, Registry $register, Authorization $authorization) {
|
||||
$pools = $register->get('pools');
|
||||
$adapter = new DatabasePool($pools->get('console'));
|
||||
$dbForPlatform = new Database($adapter, $cache);
|
||||
$dbForPlatform->setNamespace('_console');
|
||||
$dbForPlatform->setDocumentType('users', User::class);
|
||||
|
||||
$dbForPlatform
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setNamespace('_console')
|
||||
->setDocumentType('users', User::class)
|
||||
;
|
||||
|
||||
|
||||
return $dbForPlatform;
|
||||
}, ['cache', 'register']);
|
||||
}, ['cache', 'register', 'authorization']);
|
||||
|
||||
Server::setResource('project', function (Message $message, Database $dbForPlatform) {
|
||||
$payload = $message->getPayload() ?? [];
|
||||
@@ -73,7 +86,7 @@ Server::setResource('project', function (Message $message, Database $dbForPlatfo
|
||||
return $dbForPlatform->getDocument('projects', $project->getId());
|
||||
}, ['message', 'dbForPlatform']);
|
||||
|
||||
Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForPlatform) {
|
||||
Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForPlatform, Authorization $authorization) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForPlatform;
|
||||
}
|
||||
@@ -105,15 +118,18 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
|
||||
->setNamespace('_' . $project->getSequence());
|
||||
}
|
||||
|
||||
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
|
||||
|
||||
return $database;
|
||||
}, ['cache', 'register', 'message', 'project', 'dbForPlatform']);
|
||||
}, ['cache', 'register', 'message', 'project', 'dbForPlatform', 'authorization']);
|
||||
|
||||
Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
|
||||
Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, Authorization $authorization) {
|
||||
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
|
||||
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases): Database {
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases): Database {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForPlatform;
|
||||
}
|
||||
@@ -127,7 +143,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
|
||||
|
||||
if (isset($databases[$dsn->getHost()])) {
|
||||
$database = $databases[$dsn->getHost()];
|
||||
|
||||
$database->setAuthorization($authorization);
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
@@ -164,15 +180,18 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
|
||||
->setNamespace('_' . $project->getSequence());
|
||||
}
|
||||
|
||||
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'dbForPlatform', 'cache']);
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
|
||||
|
||||
Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$database = null;
|
||||
return function (?Document $project = null) use ($pools, $cache, $database) {
|
||||
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
|
||||
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$database->setTenant((int)$project->getSequence());
|
||||
return $database;
|
||||
@@ -182,6 +201,8 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setSharedTables(true)
|
||||
->setNamespace('logsV1')
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER)
|
||||
@@ -194,7 +215,7 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'cache']);
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
Server::setResource('abuseRetention', function () {
|
||||
return time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400); // 1 day
|
||||
@@ -307,6 +328,10 @@ 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']);
|
||||
@@ -382,10 +407,6 @@ 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)) {
|
||||
@@ -469,6 +490,14 @@ 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');
|
||||
@@ -513,7 +542,8 @@ $worker
|
||||
->inject('log')
|
||||
->inject('pools')
|
||||
->inject('project')
|
||||
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project) use ($worker, $queueName) {
|
||||
->inject('authorization')
|
||||
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project, Authorization $authorization) use ($worker, $queueName) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
if ($logger) {
|
||||
@@ -529,7 +559,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);
|
||||
@@ -548,9 +578,4 @@ $worker
|
||||
Console::error('[Error] Line: ' . $error->getLine());
|
||||
});
|
||||
|
||||
$worker->workerStart()
|
||||
->action(function () use ($workerName) {
|
||||
Console::info("Worker $workerName started");
|
||||
});
|
||||
|
||||
$worker->start();
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php schedule-payments-usage $@
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/worker.php payments-usage-sync $@
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
exec php /usr/src/code/app/worker.php screenshots "$@"
|
||||
+10
-10
@@ -45,18 +45,18 @@
|
||||
"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.0.2-rc3",
|
||||
"utopia-php/audit": "2.*",
|
||||
"utopia-php/auth": "0.5.*",
|
||||
"utopia-php/cache": "0.13.*",
|
||||
"utopia-php/cli": "0.15.*",
|
||||
"utopia-php/config": "1.*.*",
|
||||
"utopia-php/database": "3.*.*",
|
||||
"utopia-php/config": "1.*",
|
||||
"utopia-php/database": "4.*",
|
||||
"utopia-php/detector": "0.2.*",
|
||||
"utopia-php/domains": "0.9.*",
|
||||
"utopia-php/domains": "0.11.*",
|
||||
"utopia-php/emails": "0.6.*",
|
||||
"utopia-php/dns": "1.4.*",
|
||||
"utopia-php/dns": "1.5.*",
|
||||
"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": "0.8.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
"utopia-php/queue": "0.11.*",
|
||||
"utopia-php/queue": "0.15.*",
|
||||
"utopia-php/registry": "0.5.*",
|
||||
"utopia-php/storage": "0.18.*",
|
||||
"utopia-php/swoole": "0.8.*",
|
||||
"utopia-php/swoole": "1.*",
|
||||
"utopia-php/system": "0.9.*",
|
||||
"utopia-php/telemetry": "0.1.*",
|
||||
"utopia-php/vcs": "0.13.*",
|
||||
@@ -109,4 +109,4 @@
|
||||
"tbachert/spi": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+9064
-8148
File diff suppressed because it is too large
Load Diff
+75
-64
@@ -98,6 +98,7 @@ 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,6 +113,7 @@ services:
|
||||
- _APP_EDITION
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_LOCALE
|
||||
- _APP_COMPRESSION_ENABLED
|
||||
- _APP_COMPRESSION_MIN_SIZE_BYTES
|
||||
- _APP_CONSOLE_WHITELIST_ROOT
|
||||
- _APP_CONSOLE_WHITELIST_EMAILS
|
||||
@@ -200,6 +202,7 @@ 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
|
||||
@@ -466,14 +469,12 @@ 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_OPENSSL_KEY_V1
|
||||
@@ -529,6 +530,65 @@ 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_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
|
||||
@@ -580,9 +640,12 @@ services:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
depends_on:
|
||||
- redis
|
||||
- mariadb
|
||||
- openruntimes-executor
|
||||
redis:
|
||||
condition: service_started
|
||||
mariadb:
|
||||
condition: service_started
|
||||
openruntimes-executor:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
@@ -823,6 +886,7 @@ 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
|
||||
@@ -917,65 +981,6 @@ 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
|
||||
@@ -1125,6 +1130,12 @@ 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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
appwrite health get-queue-audits
|
||||
@@ -0,0 +1,3 @@
|
||||
appwrite projects update-labels \
|
||||
--project-id <PROJECT_ID> \
|
||||
--labels one two three
|
||||
@@ -0,0 +1 @@
|
||||
Get the number of audit logs that are waiting to be processed in the Appwrite internal queue server.
|
||||
@@ -1,5 +1,63 @@
|
||||
# Change Log
|
||||
|
||||
## 13.1.0
|
||||
|
||||
- Mark `appwrite generate` command as stable
|
||||
- Improve permissions param to be a typesafe callback
|
||||
- Fix relationship handling in generated code
|
||||
- Fix `appwrite client` properly hanlding `--key` parameter
|
||||
- Fix `init site` not working on Windows
|
||||
|
||||
## 13.1.0-rc.3
|
||||
|
||||
- Allow generation of server side CRUD operations on databases and tables
|
||||
- Fix npm distribution failing due to missing template files in bundle
|
||||
|
||||
## 13.1.0-rc.2
|
||||
|
||||
- Update generated `databases` services to automatically initialize a client instance
|
||||
- Update generator to use handlebars templates
|
||||
|
||||
## 13.1.0-rc.1
|
||||
|
||||
- Feat: `appwrite generate` command to create a fully typesafe SDK for your Appwrite project
|
||||
- Chore: improve creation of columns during table creation by passing them directly instead of creating them one by one
|
||||
- Improved config validation by adding extra rules in zod schema
|
||||
|
||||
## 13.0.1
|
||||
|
||||
- Fix `project init` command leading to Cannot convert to BigInt error
|
||||
- Fix filter out unwanted attributes being pulled in the config file
|
||||
|
||||
## 13.0.0
|
||||
|
||||
- Mark release as stable
|
||||
- Feat: add pull sync on destruction of remote resources (+ confirmation)
|
||||
- Fix: refine zod schema to check string size
|
||||
- Validate using zod schema during push cli command
|
||||
- Maintain order of keys in local config
|
||||
|
||||
## 13.0.0-rc.5
|
||||
|
||||
- Fix push all command not working correctly
|
||||
|
||||
## 13.0.0-rc.4
|
||||
|
||||
- Fix CLI ES module import issues
|
||||
|
||||
## 13.0.0-rc.3
|
||||
|
||||
- Add `Schema` class for programmatically pushing and pulling appwrite config
|
||||
- Add client side db generation using `schema.db.generate()` command
|
||||
|
||||
## 13.0.0-rc.2
|
||||
|
||||
- Fixes a lot of typescript errors throughout the codebase
|
||||
|
||||
## 13.0.0-rc.1
|
||||
|
||||
- Migrates codebase from JavaScript to TypeScript
|
||||
|
||||
## 12.0.1
|
||||
|
||||
Fix type generation for `point`, `lineString` and `polygon` columns
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Change Log
|
||||
|
||||
## 20.1.1
|
||||
|
||||
* Fix boolean parameter not handled correctly in Client requests
|
||||
|
||||
## 20.1.0
|
||||
|
||||
* Added ability to create columns and indexes synchronously while creating a table
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Change Log
|
||||
|
||||
## 20.3.3
|
||||
|
||||
* Fix boolean parameter not handled correctly in Client requests
|
||||
|
||||
## 20.3.2
|
||||
|
||||
* Fix OAuth2 browser infinite redirect issue
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Change Log
|
||||
|
||||
## 0.1.0
|
||||
|
||||
* Initial release
|
||||
@@ -15,6 +15,8 @@ class Key
|
||||
{
|
||||
public function __construct(
|
||||
protected string $projectId,
|
||||
protected string $teamId,
|
||||
protected string $userId,
|
||||
protected string $type,
|
||||
protected string $role,
|
||||
protected array $scopes,
|
||||
@@ -34,6 +36,16 @@ class Key
|
||||
return $this->projectId;
|
||||
}
|
||||
|
||||
public function getUserId(): string
|
||||
{
|
||||
return $this->userId;
|
||||
}
|
||||
|
||||
public function getTeamId(): string
|
||||
{
|
||||
return $this->teamId;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
@@ -95,13 +107,12 @@ class Key
|
||||
* Decode the given secret key into a Key object, containing the project ID, type, role, scopes, and name.
|
||||
* Can be a stored API key or a dynamic key (JWT).
|
||||
*
|
||||
* @param Document $project
|
||||
* @param string $key
|
||||
* @return Key
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function decode(
|
||||
Document $project,
|
||||
Document $team,
|
||||
Document $user,
|
||||
string $key
|
||||
): Key {
|
||||
if (\str_contains($key, '_')) {
|
||||
@@ -118,6 +129,8 @@ class Key
|
||||
|
||||
$guestKey = new Key(
|
||||
$project->getId(),
|
||||
'',
|
||||
'',
|
||||
$type,
|
||||
User::ROLE_GUESTS,
|
||||
$roles[User::ROLE_GUESTS]['scopes'] ?? [],
|
||||
@@ -133,6 +146,7 @@ class Key
|
||||
leeway: 0
|
||||
);
|
||||
|
||||
$payload = [];
|
||||
try {
|
||||
$payload = $jwtObj->decode($secret);
|
||||
} catch (JWTException) {
|
||||
@@ -155,6 +169,8 @@ class Key
|
||||
|
||||
return new Key(
|
||||
$projectId,
|
||||
'',
|
||||
'',
|
||||
$type,
|
||||
$role,
|
||||
$scopes,
|
||||
@@ -188,12 +204,86 @@ class Key
|
||||
|
||||
return new Key(
|
||||
$project->getId(),
|
||||
'',
|
||||
'',
|
||||
$type,
|
||||
$role,
|
||||
$scopes,
|
||||
$name,
|
||||
$expired
|
||||
);
|
||||
case API_KEY_ACCOUNT:
|
||||
$key = $user->find(
|
||||
key: 'secret',
|
||||
find: $key,
|
||||
subject: 'keys'
|
||||
);
|
||||
|
||||
// Invalid key
|
||||
if (!$key) {
|
||||
return $guestKey;
|
||||
}
|
||||
|
||||
$expire = $key->getAttribute('expire');
|
||||
$expired = false;
|
||||
if (!empty($expire) && $expire < DateTime::formatTz(DateTime::now())) {
|
||||
$expired = true;
|
||||
}
|
||||
|
||||
$name = $key->getAttribute('name', 'UNKNOWN');
|
||||
|
||||
$role = User::ROLE_USERS;
|
||||
|
||||
$scopes = $key->getAttribute('scopes', []);
|
||||
|
||||
$key = new Key(
|
||||
'',
|
||||
'',
|
||||
$user->getId(),
|
||||
$type,
|
||||
$role,
|
||||
$scopes,
|
||||
$name,
|
||||
$expired
|
||||
);
|
||||
|
||||
return $key;
|
||||
case API_KEY_ORGANIZATION:
|
||||
$key = $team->find(
|
||||
key: 'secret',
|
||||
find: $key,
|
||||
subject: 'keys'
|
||||
);
|
||||
|
||||
// Invalid key
|
||||
if (!$key) {
|
||||
return $guestKey;
|
||||
}
|
||||
|
||||
$expire = $key->getAttribute('expire');
|
||||
$expired = false;
|
||||
if (!empty($expire) && $expire < DateTime::formatTz(DateTime::now())) {
|
||||
$expired = true;
|
||||
}
|
||||
|
||||
$name = $key->getAttribute('name', 'UNKNOWN');
|
||||
|
||||
$role = User::ROLE_APPS;
|
||||
|
||||
$scopes = $key->getAttribute('scopes', []);
|
||||
|
||||
$key = new Key(
|
||||
'',
|
||||
$team->getId(),
|
||||
'',
|
||||
$type,
|
||||
$role,
|
||||
$scopes,
|
||||
$name,
|
||||
$expired
|
||||
);
|
||||
|
||||
return $key;
|
||||
default:
|
||||
return $guestKey;
|
||||
}
|
||||
|
||||
@@ -20,10 +20,12 @@ use Utopia\Database\Validator\Authorization;
|
||||
class TransactionState
|
||||
{
|
||||
private Database $dbForProject;
|
||||
|
||||
public function __construct(Database $dbForProject)
|
||||
private Authorization $authorization;
|
||||
/** @var Authorization $authorization */
|
||||
public function __construct(Database $dbForProject, Authorization $authorization)
|
||||
{
|
||||
$this->dbForProject = $dbForProject;
|
||||
$this->authorization = $authorization;
|
||||
}
|
||||
|
||||
|
||||
@@ -342,12 +344,12 @@ class TransactionState
|
||||
*/
|
||||
private function getTransactionState(string $transactionId): array
|
||||
{
|
||||
$transaction = Authorization::skip(fn () => $this->dbForProject->getDocument('transactions', $transactionId));
|
||||
$transaction = $this->authorization->skip(fn () => $this->dbForProject->getDocument('transactions', $transactionId));
|
||||
if ($transaction->isEmpty() || $transaction->getAttribute('status') !== 'pending') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$operations = Authorization::skip(fn () => $this->dbForProject->find('transactionLogs', [
|
||||
$operations = $this->authorization->skip(fn () => $this->dbForProject->find('transactionLogs', [
|
||||
Query::equal('transactionInternalId', [$transaction->getSequence()]),
|
||||
Query::orderAsc(),
|
||||
Query::limit(PHP_INT_MAX)
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
namespace Appwrite\Deletes;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Utopia\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Limit as LimitException;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class Targets
|
||||
@@ -42,12 +44,17 @@ class Targets
|
||||
MESSAGE_TYPE_PUSH => 'pushTotal',
|
||||
default => throw new Exception('Invalid target provider type'),
|
||||
};
|
||||
$database->decreaseDocumentAttribute(
|
||||
'topics',
|
||||
$topicId,
|
||||
$totalAttribute,
|
||||
min: 0
|
||||
);
|
||||
|
||||
try {
|
||||
$database->decreaseDocumentAttribute(
|
||||
'topics',
|
||||
$topicId,
|
||||
$totalAttribute,
|
||||
min: 0
|
||||
);
|
||||
} catch (LimitException $e) {
|
||||
Console::error("Delete subscribers decreaseDocumentAttribute (topicId={$topicId}): {$e->getMessage()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -39,15 +39,15 @@ class Event
|
||||
public const BUILDS_QUEUE_NAME = 'v1-builds';
|
||||
public const BUILDS_CLASS_NAME = 'BuildsV1';
|
||||
|
||||
public const SCREENSHOTS_QUEUE_NAME = 'v1-screenshots';
|
||||
public const SCREENSHOTS_CLASS_NAME = 'ScreenshotsV1';
|
||||
|
||||
public const MESSAGING_QUEUE_NAME = 'v1-messaging';
|
||||
public const MESSAGING_CLASS_NAME = 'MessagingV1';
|
||||
|
||||
public const MIGRATIONS_QUEUE_NAME = 'v1-migrations';
|
||||
public const MIGRATIONS_CLASS_NAME = 'MigrationsV1';
|
||||
|
||||
public const PAYMENTS_USAGE_QUEUE_NAME = 'v1-payments-usage-sync';
|
||||
public const PAYMENTS_USAGE_CLASS_NAME = 'PaymentsUsageSyncV1';
|
||||
|
||||
protected string $queue = '';
|
||||
protected string $class = '';
|
||||
protected string $event = '';
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Queue\Publisher;
|
||||
|
||||
class PaymentsUsage extends Event
|
||||
{
|
||||
public function __construct(protected Publisher $publisher)
|
||||
{
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(Event::PAYMENTS_USAGE_QUEUE_NAME)
|
||||
->setClass(Event::PAYMENTS_USAGE_CLASS_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the payload for the event
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function preparePayload(): array
|
||||
{
|
||||
return [
|
||||
'project' => $this->getProject(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Screenshot extends Event
|
||||
{
|
||||
protected string $deploymentId = '';
|
||||
|
||||
public function __construct(protected Publisher $publisher)
|
||||
{
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(System::getEnv('_APP_SCREENSHOTS_QUEUE_NAME', Event::SCREENSHOTS_QUEUE_NAME))
|
||||
->setClass(System::getEnv('_APP_SCREENSHOTS_CLASS_NAME', Event::SCREENSHOTS_CLASS_NAME));
|
||||
}
|
||||
|
||||
public function setDeploymentId(string $deploymentId): self
|
||||
{
|
||||
$this->deploymentId = $deploymentId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function preparePayload(): array
|
||||
{
|
||||
$platform = $this->platform;
|
||||
if (empty($platform)) {
|
||||
$platform = Config::getParam('platform', []);
|
||||
}
|
||||
|
||||
return [
|
||||
'project' => $this->project,
|
||||
'deploymentId' => $this->deploymentId,
|
||||
'platform' => $platform,
|
||||
];
|
||||
}
|
||||
|
||||
public function reset(): self
|
||||
{
|
||||
$this->deploymentId = '';
|
||||
parent::reset();
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,6 @@ class Exception extends \Exception
|
||||
public const string GENERAL_INVALID_PHONE = 'general_invalid_phone';
|
||||
public const string GENERAL_REGION_ACCESS_DENIED = 'general_region_access_denied';
|
||||
public const string GENERAL_BAD_REQUEST = 'general_bad_request';
|
||||
public const string GENERAL_NOT_FOUND = 'general_not_found';
|
||||
|
||||
/** Users */
|
||||
public const string USER_COUNT_EXCEEDED = 'user_count_exceeded';
|
||||
@@ -108,7 +107,9 @@ class Exception extends \Exception
|
||||
public const string USER_DELETION_PROHIBITED = 'user_deletion_prohibited';
|
||||
public const string USER_TARGET_NOT_FOUND = 'user_target_not_found';
|
||||
public const string USER_TARGET_ALREADY_EXISTS = 'user_target_already_exists';
|
||||
public const string USER_API_KEY_AND_SESSION_SET = 'user_key_and_session_set';
|
||||
public const string USER_API_KEY_AND_SESSION_SET = 'user_api_key_and_session_set';
|
||||
public const string USER_JWT_AND_COOKIE_SET = 'user_jwt_and_cookie_set';
|
||||
public const string USER_ID_MISSING = 'user_id_missing';
|
||||
|
||||
public const string API_KEY_EXPIRED = 'api_key_expired';
|
||||
|
||||
@@ -120,6 +121,8 @@ class Exception extends \Exception
|
||||
public const string TEAM_INVITE_MISMATCH = 'team_invite_mismatch';
|
||||
public const string TEAM_ALREADY_EXISTS = 'team_already_exists';
|
||||
|
||||
public const string ORGANIZATION_ID_MISSING = 'organization_id_missing';
|
||||
|
||||
/** Console */
|
||||
public const string RESOURCE_ALREADY_EXISTS = 'resource_already_exists';
|
||||
|
||||
@@ -284,6 +287,7 @@ class Exception extends \Exception
|
||||
|
||||
/** Projects */
|
||||
public const string PROJECT_NOT_FOUND = 'project_not_found';
|
||||
public const string PROJECT_ID_MISSING = 'project_id_missing';
|
||||
public const string PROJECT_PROVIDER_DISABLED = 'project_provider_disabled';
|
||||
public const string PROJECT_PROVIDER_UNSUPPORTED = 'project_provider_unsupported';
|
||||
public const string PROJECT_ALREADY_EXISTS = 'project_already_exists';
|
||||
@@ -291,6 +295,7 @@ class Exception extends \Exception
|
||||
public const string PROJECT_INVALID_FAILURE_URL = 'project_invalid_failure_url';
|
||||
public const string PROJECT_RESERVED_PROJECT = 'project_reserved_project';
|
||||
public const string PROJECT_KEY_EXPIRED = 'project_key_expired';
|
||||
public const string ACCOUNT_KEY_EXPIRED = 'account_key_expired';
|
||||
|
||||
public const string PROJECT_SMTP_CONFIG_INVALID = 'project_smtp_config_invalid';
|
||||
|
||||
@@ -379,16 +384,6 @@ class Exception extends \Exception
|
||||
public const string TOKEN_EXPIRED = 'token_expired';
|
||||
public const string TOKEN_RESOURCE_TYPE_INVALID = 'token_resource_type_invalid';
|
||||
|
||||
/** Payments */
|
||||
public const string PAYMENT_PLAN_NOT_FOUND = 'payment_plan_not_found';
|
||||
public const string PAYMENT_PLAN_ALREADY_EXISTS = 'payment_plan_already_exists';
|
||||
public const string PAYMENT_SUBSCRIPTION_NOT_FOUND = 'payment_subscription_not_found';
|
||||
public const string PAYMENT_SUBSCRIPTION_ALREADY_EXISTS = 'payment_subscription_already_exists';
|
||||
public const string PAYMENT_PROVIDER_NOT_CONFIGURED = 'payment_provider_not_configured';
|
||||
public const string PAYMENT_PROVIDER_ALREADY_CONFIGURED = 'payment_provider_already_configured';
|
||||
public const string PAYMENT_WEBHOOK_FAILED = 'payment_webhook_failed';
|
||||
public const string PAYMENT_FEATURE_NOT_FOUND = 'payment_feature_not_found';
|
||||
|
||||
protected string $type = '';
|
||||
protected array $errors = [];
|
||||
protected bool $publish;
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Functions;
|
||||
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
|
||||
class EventProcessor
|
||||
{
|
||||
/**
|
||||
* Get function events for a project, using Redis cache
|
||||
* @param Document|null $project
|
||||
* @param Database $dbForProject
|
||||
* @return array<string, bool>
|
||||
*/
|
||||
public function getFunctionsEvents(?Document $project, Database $dbForProject): array
|
||||
{
|
||||
if ($project === null ||
|
||||
$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()
|
||||
);
|
||||
|
||||
$ttl = 3600; // 1 hour cache TTL
|
||||
$cachedFunctionEvents = $dbForProject->getCache()->load($cacheKey, $ttl);
|
||||
|
||||
if ($cachedFunctionEvents !== false) {
|
||||
return \json_decode($cachedFunctionEvents, true) ?? [];
|
||||
}
|
||||
|
||||
try {
|
||||
$events = [];
|
||||
$limit = 100;
|
||||
$sum = 100;
|
||||
$offset = 0;
|
||||
|
||||
while ($sum >= $limit) {
|
||||
$functions = $dbForProject->find('functions', [
|
||||
Query::select(['$id', 'events']),
|
||||
Query::limit($limit),
|
||||
Query::offset($offset),
|
||||
Query::orderAsc('$sequence'),
|
||||
]);
|
||||
|
||||
$sum = \count($functions);
|
||||
$offset = $offset + $limit;
|
||||
|
||||
foreach ($functions as $function) {
|
||||
$functionEvents = $function->getAttribute('events', []);
|
||||
if (!empty($functionEvents)) {
|
||||
$events = array_merge($events, $functionEvents);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$uniqueEvents = \array_flip(\array_unique($events));
|
||||
$dbForProject->getCache()->save($cacheKey, \json_encode($uniqueEvents));
|
||||
|
||||
return $uniqueEvents;
|
||||
} catch (\Throwable $e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get webhook events for a project from the project's webhooks attribute
|
||||
* @param Document|null $project
|
||||
* @return array<string, bool>
|
||||
*/
|
||||
public function getWebhooksEvents(?Document $project): array
|
||||
{
|
||||
if ($project === null || $project->isEmpty() || $project->getId() === 'console') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$webhooks = $project->getAttribute('webhooks', []);
|
||||
if (empty($webhooks)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$events = [];
|
||||
foreach ($webhooks as $webhook) {
|
||||
if ($webhook->getAttribute('enabled', false) !== true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$webhookEvents = $webhook->getAttribute('events', []);
|
||||
if (!empty($webhookEvents)) {
|
||||
$events = array_merge($events, $webhookEvents);
|
||||
}
|
||||
}
|
||||
|
||||
return \array_flip(\array_unique($events));
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,13 @@ namespace Appwrite\Messaging\Adapter;
|
||||
|
||||
use Appwrite\Messaging\Adapter as MessagingAdapter;
|
||||
use Appwrite\PubSub\Adapter\Pool as PubSubPool;
|
||||
use Appwrite\Utopia\Database\RuntimeQuery;
|
||||
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;
|
||||
|
||||
class Realtime extends MessagingAdapter
|
||||
{
|
||||
@@ -51,9 +54,10 @@ class Realtime extends MessagingAdapter
|
||||
* @param mixed $identifier
|
||||
* @param array $roles
|
||||
* @param array $channels
|
||||
* @param array $queries
|
||||
* @return void
|
||||
*/
|
||||
public function subscribe(string $projectId, mixed $identifier, array $roles, array $channels): void
|
||||
public function subscribe(string $projectId, mixed $identifier, array $roles, array $channels, array $queries = []): void
|
||||
{
|
||||
if (!isset($this->subscriptions[$projectId])) { // Init Project
|
||||
$this->subscriptions[$projectId] = [];
|
||||
@@ -72,7 +76,8 @@ class Realtime extends MessagingAdapter
|
||||
$this->connections[$identifier] = [
|
||||
'projectId' => $projectId,
|
||||
'roles' => $roles,
|
||||
'channels' => $channels
|
||||
'channels' => $channels,
|
||||
'queries' => $queries
|
||||
];
|
||||
}
|
||||
|
||||
@@ -206,7 +211,14 @@ class Realtime extends MessagingAdapter
|
||||
/**
|
||||
* To prevent duplicates, we save the connections as array keys.
|
||||
*/
|
||||
$receivers[$id] = 0;
|
||||
$queries = $this->connections[$id]['queries'] ?? [];
|
||||
$payload = $event['data']['payload'] ?? [];
|
||||
if (
|
||||
empty($queries) ||
|
||||
!empty(RuntimeQuery::filter($queries, $payload))
|
||||
) {
|
||||
$receivers[$id] = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -245,6 +257,34 @@ class Realtime extends MessagingAdapter
|
||||
return $channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the queries from the Query Params into an array.
|
||||
* @param array $queries
|
||||
* @return array
|
||||
*/
|
||||
public static function convertQueries(array $queries): array
|
||||
{
|
||||
$queries = Query::parseQueries($queries);
|
||||
$stack = $queries;
|
||||
$allowedMethods = implode(', ', RuntimeQuery::ALLOWED_QUERIES);
|
||||
while (!empty($stack)) {
|
||||
/** `@var` Query $query */
|
||||
$query = array_pop($stack);
|
||||
$method = $query->getMethod();
|
||||
if (!in_array($method, RuntimeQuery::ALLOWED_QUERIES, true)) {
|
||||
$unsupportedMethod = $method;
|
||||
throw new QueryException(
|
||||
"Query method '{$unsupportedMethod}' is not supported in Realtime queries. Allowed query methods are: {$allowedMethods}"
|
||||
);
|
||||
}
|
||||
if (in_array($method, [Query::TYPE_AND, Query::TYPE_OR], true)) {
|
||||
$stack = array_merge($stack, $query->getValues());
|
||||
}
|
||||
}
|
||||
|
||||
return $queries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create channels array based on the event name and payload.
|
||||
*
|
||||
|
||||
@@ -100,8 +100,6 @@ abstract class Migration
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
Authorization::disable();
|
||||
Authorization::setDefaultStatus(false);
|
||||
|
||||
$this->collections = Config::getParam('collections', []);
|
||||
|
||||
@@ -129,6 +127,7 @@ abstract class Migration
|
||||
Document $project,
|
||||
Database $dbForProject,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
?callable $getProjectDB = null
|
||||
): self {
|
||||
$this->project = $project;
|
||||
@@ -136,6 +135,9 @@ abstract class Migration
|
||||
$this->dbForPlatform = $dbForPlatform;
|
||||
$this->getProjectDB = $getProjectDB;
|
||||
|
||||
$authorization->disable();
|
||||
$authorization->setDefaultStatus(false);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Payments\Provider;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
|
||||
interface Adapter
|
||||
{
|
||||
public function getIdentifier(): string;
|
||||
|
||||
public function configure(array $config, Document $project): ProviderState;
|
||||
|
||||
public function ensurePlan(array $planData, ProviderState $state): ProviderPlanRef;
|
||||
|
||||
public function updatePlan(array $planData, ProviderPlanRef $reference, ProviderState $state): ProviderPlanRef;
|
||||
|
||||
public function deletePlan(ProviderPlanRef $reference, ProviderState $state): void;
|
||||
|
||||
public function ensureFeature(array $featureData, ProviderPlanRef $plan, ProviderState $state): ProviderFeatureRef;
|
||||
|
||||
public function deleteFeature(ProviderFeatureRef $feature, ProviderPlanRef $plan, ProviderState $state): void;
|
||||
|
||||
public function updateSubscription(ProviderSubscriptionRef $subscription, array $changes, ProviderState $state): ProviderSubscriptionRef;
|
||||
|
||||
public function cancelSubscription(ProviderSubscriptionRef $subscription, bool $atPeriodEnd, ProviderState $state): ProviderSubscriptionRef;
|
||||
|
||||
public function resumeSubscription(ProviderSubscriptionRef $subscription, ProviderState $state): ProviderSubscriptionRef;
|
||||
|
||||
public function createCheckoutSession(Document $actor, array $planContext, ProviderState $state, array $options = []): ProviderCheckoutSession;
|
||||
|
||||
public function createPortalSession(Document $actor, ProviderState $state, array $options = []): ProviderPortalSession;
|
||||
|
||||
/**
|
||||
* @return ProviderInvoice[]
|
||||
*/
|
||||
public function listInvoices(ProviderSubscriptionRef $subscription, ProviderState $state, int $limit = 25, int $offset = 0): array;
|
||||
|
||||
public function previewProration(ProviderSubscriptionRef $subscription, string $newPriceId, ProviderState $state): ProviderProrationPreview;
|
||||
|
||||
public function reportUsage(ProviderSubscriptionRef $subscription, string $featureId, int $quantity, \DateTimeInterface $timestamp, ProviderState $state): void;
|
||||
|
||||
public function syncUsage(ProviderSubscriptionRef $subscription, ProviderState $state): ProviderUsageReport;
|
||||
|
||||
public function handleWebhook(array $payload, ProviderState $state): ProviderWebhookResult;
|
||||
|
||||
public function testConnection(array $config): ProviderTestResult;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Payments\Provider;
|
||||
|
||||
class ProviderCheckoutSession
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $url,
|
||||
public readonly array $metadata = [],
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Payments\Provider;
|
||||
|
||||
class ProviderFeatureRef
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $externalFeatureId,
|
||||
public readonly array $metadata = [],
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Payments\Provider;
|
||||
|
||||
class ProviderInvoice
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $invoiceId,
|
||||
public readonly string $subscriptionId,
|
||||
public readonly int $amount,
|
||||
public readonly string $currency,
|
||||
public readonly string $status,
|
||||
public readonly ?int $createdAt = null,
|
||||
public readonly ?int $paidAt = null,
|
||||
public readonly ?string $invoiceUrl = null,
|
||||
public readonly array $metadata = [],
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Payments\Provider;
|
||||
|
||||
class ProviderPlanRef
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $externalPlanId,
|
||||
public readonly array $metadata = [],
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Payments\Provider;
|
||||
|
||||
class ProviderPortalSession
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $url,
|
||||
public readonly array $metadata = [],
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Payments\Provider;
|
||||
|
||||
class ProviderProrationPreview
|
||||
{
|
||||
public function __construct(
|
||||
public readonly int $amountDue,
|
||||
public readonly int $prorationAmount,
|
||||
public readonly string $currency,
|
||||
public readonly ?int $nextBillingDate = null,
|
||||
public readonly array $metadata = [],
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Payments\Provider;
|
||||
|
||||
class ProviderState
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $providerId,
|
||||
public readonly array $config = [],
|
||||
public readonly array $metadata = [],
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Payments\Provider;
|
||||
|
||||
class ProviderSubscriptionRef
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $externalSubscriptionId,
|
||||
public readonly array $metadata = [],
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Payments\Provider;
|
||||
|
||||
class ProviderTestResult
|
||||
{
|
||||
public function __construct(
|
||||
public readonly bool $success,
|
||||
public readonly string $message = '',
|
||||
public readonly array $metadata = [],
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Payments\Provider;
|
||||
|
||||
class ProviderUsageReport
|
||||
{
|
||||
public function __construct(
|
||||
public readonly array $totals = [],
|
||||
public readonly array $details = [],
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Payments\Provider;
|
||||
|
||||
class ProviderWebhookResult
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $status,
|
||||
public readonly array $changes = [],
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Payments\Provider;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class Registry
|
||||
{
|
||||
/**
|
||||
* @var array<string, class-string<Adapter>>
|
||||
*/
|
||||
private array $map = [];
|
||||
|
||||
/**
|
||||
* @var array<string, Adapter>
|
||||
*/
|
||||
private array $cache = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Map provider identifiers to adapter classes
|
||||
// e.g., $this->map['stripe'] = \Appwrite\Payments\Provider\StripeAdapter::class;
|
||||
}
|
||||
|
||||
public function register(string $identifier, string $adapterClass): void
|
||||
{
|
||||
$this->map[$identifier] = $adapterClass;
|
||||
}
|
||||
|
||||
public function get(string $identifier, array $config, Document $project, \Utopia\Database\Database $dbForPlatform, \Utopia\Database\Database $dbForProject): Adapter
|
||||
{
|
||||
$cacheKey = $identifier . ':' . $project->getId();
|
||||
if (isset($this->cache[$cacheKey])) {
|
||||
return $this->cache[$cacheKey];
|
||||
}
|
||||
if (!isset($this->map[$identifier])) {
|
||||
throw new \RuntimeException('Unknown payments provider: ' . $identifier);
|
||||
}
|
||||
$class = $this->map[$identifier];
|
||||
/** @var Adapter $adapter */
|
||||
$adapter = new $class($config, $project, $dbForProject, $dbForPlatform);
|
||||
$this->cache[$cacheKey] = $adapter;
|
||||
return $adapter;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,11 +3,12 @@
|
||||
namespace Appwrite\Platform;
|
||||
|
||||
use Appwrite\Platform\Modules\Account;
|
||||
use Appwrite\Platform\Modules\Avatars;
|
||||
use Appwrite\Platform\Modules\Console;
|
||||
use Appwrite\Platform\Modules\Core;
|
||||
use Appwrite\Platform\Modules\Databases;
|
||||
use Appwrite\Platform\Modules\Functions;
|
||||
use Appwrite\Platform\Modules\Payments;
|
||||
use Appwrite\Platform\Modules\Health;
|
||||
use Appwrite\Platform\Modules\Projects;
|
||||
use Appwrite\Platform\Modules\Proxy;
|
||||
use Appwrite\Platform\Modules\Sites;
|
||||
@@ -21,14 +22,15 @@ class Appwrite extends Platform
|
||||
{
|
||||
parent::__construct(new Core());
|
||||
$this->addModule(new Account\Module());
|
||||
$this->addModule(new Avatars\Module());
|
||||
$this->addModule(new Databases\Module());
|
||||
$this->addModule(new Projects\Module());
|
||||
$this->addModule(new Functions\Module());
|
||||
$this->addModule(new Health\Module());
|
||||
$this->addModule(new Sites\Module());
|
||||
$this->addModule(new Console\Module());
|
||||
$this->addModule(new Proxy\Module());
|
||||
$this->addModule(new Tokens\Module());
|
||||
$this->addModule(new Storage\Module());
|
||||
$this->addModule(new Payments\Module());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use libphonenumber\NumberParseException;
|
||||
use libphonenumber\PhoneNumberUtil;
|
||||
use Utopia\Abuse\Abuse;
|
||||
use Utopia\Auth\Proofs\Code as ProofsCode;
|
||||
use Utopia\Auth\Proofs\Token as ProofsToken;
|
||||
use Utopia\Database\Database;
|
||||
@@ -196,26 +196,21 @@ class Create extends Action
|
||||
->setRecipients([$phone])
|
||||
->setProviderType(MESSAGE_TYPE_SMS);
|
||||
|
||||
if (isset($plan['authPhone'])) {
|
||||
$timelimit = $timelimit('organization:{organizationId}', $plan['authPhone'], 30 * 24 * 60 * 60); // 30 days
|
||||
$timelimit
|
||||
->setParam('{organizationId}', $project->getAttribute('teamId'));
|
||||
$helper = PhoneNumberUtil::getInstance();
|
||||
try {
|
||||
$countryCode = $helper->parse($phone)->getCountryCode();
|
||||
|
||||
$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);
|
||||
}
|
||||
if (!empty($countryCode)) {
|
||||
$queueForStatsUsage
|
||||
->addMetric(str_replace('{countryCode}', $countryCode, METRIC_AUTH_METHOD_PHONE_COUNTRY_CODE), 1);
|
||||
}
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
} catch (NumberParseException $e) {
|
||||
// Ignore invalid phone number for country code stats
|
||||
}
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_AUTH_METHOD_PHONE, 1)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
break;
|
||||
case Type::EMAIL:
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Action as PlatformAction;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Throwable;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Image\Image;
|
||||
use Utopia\Logger\Logger;
|
||||
|
||||
class Action extends PlatformAction
|
||||
{
|
||||
protected function getAppRoot(): string
|
||||
{
|
||||
return \dirname(__DIR__, 6);
|
||||
}
|
||||
|
||||
protected function avatar(string $type, string $code, int $width, int $height, int $quality, Response $response): void
|
||||
{
|
||||
$code = \strtolower($code);
|
||||
$type = \strtolower($type);
|
||||
$set = Config::getParam('avatar-' . $type, []);
|
||||
|
||||
if (empty($set)) {
|
||||
throw new Exception(Exception::AVATAR_SET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!\array_key_exists($code, $set)) {
|
||||
throw new Exception(Exception::AVATAR_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!\extension_loaded('imagick')) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
|
||||
}
|
||||
|
||||
$output = 'png';
|
||||
$path = $set[$code]['path'];
|
||||
$type = 'png';
|
||||
|
||||
if (!\is_readable($path)) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'File not readable in ' . $path);
|
||||
}
|
||||
|
||||
$image = new Image(\file_get_contents($path));
|
||||
$image->crop((int) $width, (int) $height);
|
||||
$output = (empty($output)) ? $type : $output;
|
||||
$data = $image->output($output, $quality);
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
->setContentType('image/png')
|
||||
->file($data);
|
||||
unset($image);
|
||||
}
|
||||
|
||||
protected function getUserGitHub(string $userId, Document $project, Database $dbForProject, Database $dbForPlatform, ?Logger $logger, Authorization $authorization): array
|
||||
{
|
||||
try {
|
||||
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
|
||||
$gitHubSession = null;
|
||||
foreach ($sessions as $session) {
|
||||
if ($session->getAttribute('provider', '') === 'github') {
|
||||
$gitHubSession = $session;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($gitHubSession)) {
|
||||
throw new Exception(Exception::USER_SESSION_NOT_FOUND, 'GitHub session not found.');
|
||||
}
|
||||
|
||||
$provider = $gitHubSession->getAttribute('provider', '');
|
||||
$accessToken = $gitHubSession->getAttribute('providerAccessToken');
|
||||
$accessTokenExpiry = $gitHubSession->getAttribute('providerAccessTokenExpiry');
|
||||
$refreshToken = $gitHubSession->getAttribute('providerRefreshToken');
|
||||
|
||||
$appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? '';
|
||||
$appSecret = $project->getAttribute('oAuthProviders', [])[$provider . 'Secret'] ?? '{}';
|
||||
|
||||
$oAuthProviders = Config::getParam('oAuthProviders');
|
||||
$className = $oAuthProviders[$provider]['class'];
|
||||
if (!\class_exists($className)) {
|
||||
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
|
||||
}
|
||||
|
||||
$oauth2 = new $className($appId, $appSecret, '', [], []);
|
||||
|
||||
$isExpired = new \DateTime($accessTokenExpiry) < new \DateTime('now');
|
||||
if ($isExpired) {
|
||||
try {
|
||||
$oauth2->refreshTokens($refreshToken);
|
||||
|
||||
$accessToken = $oauth2->getAccessToken('');
|
||||
$refreshToken = $oauth2->getRefreshToken('');
|
||||
|
||||
$verificationId = $oauth2->getUserID($accessToken);
|
||||
|
||||
if (empty($verificationId)) {
|
||||
throw new \Exception("Locked tokens."); // Race codition, handeled in catch
|
||||
}
|
||||
|
||||
$gitHubSession
|
||||
->setAttribute('providerAccessToken', $accessToken)
|
||||
->setAttribute('providerRefreshToken', $refreshToken)
|
||||
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry('')));
|
||||
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession));
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
} catch (Throwable $err) {
|
||||
$index = 0;
|
||||
do {
|
||||
$previousAccessToken = $gitHubSession->getAttribute('providerAccessToken');
|
||||
|
||||
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
|
||||
$gitHubSession = new Document();
|
||||
foreach ($sessions as $session) {
|
||||
if ($session->getAttribute('provider', '') === 'github') {
|
||||
$gitHubSession = $session;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$accessToken = $gitHubSession->getAttribute('providerAccessToken');
|
||||
|
||||
if ($accessToken !== $previousAccessToken) {
|
||||
break;
|
||||
}
|
||||
|
||||
$index++;
|
||||
\usleep(500000);
|
||||
} while ($index < 10);
|
||||
}
|
||||
}
|
||||
|
||||
$oauth2 = new $className($appId, $appSecret, '', [], []);
|
||||
$githubUser = $oauth2->getUserSlug($accessToken);
|
||||
$githubId = $oauth2->getUserID($accessToken);
|
||||
|
||||
return [
|
||||
'name' => $githubUser,
|
||||
'id' => $githubId
|
||||
];
|
||||
} catch (Exception $error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http\Browsers;
|
||||
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getBrowser';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(UtopiaAction::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/avatars/browsers/:code')
|
||||
->desc('Get browser icon')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resource', 'avatar/browser')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getBrowser',
|
||||
description: '/docs/references/avatars/get-browser.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE_PNG
|
||||
))
|
||||
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-browsers'))), 'Browser Code.')
|
||||
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('quality', -1, new Range(-1, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true)
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $code, int $width, int $height, int $quality, Response $response)
|
||||
{
|
||||
$this->avatar('browsers', $code, $width, $height, $quality, $response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http\Cards\Cloud\Back;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Action;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Imagick;
|
||||
use ImagickDraw;
|
||||
use ImagickPixel;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getCloudCardBack';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(UtopiaAction::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/cards/cloud-back')
|
||||
->desc('Get back Of Cloud Card')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resourceType', 'cards/cloud-back')
|
||||
->label('cache.resource', 'card-back/{request.userId}')
|
||||
->label('docs', false)
|
||||
->label('origin', '*')
|
||||
->param('userId', '', new UID(), 'User ID.', true)
|
||||
->param('mock', '', new WhiteList(['golden', 'normal', 'platinum']), 'Mocking behaviour.', true)
|
||||
->param('width', 0, new Range(0, 512), 'Resize image width, Pass an integer between 0 to 512.', true)
|
||||
->param('height', 0, new Range(0, 320), 'Resize image height, Pass an integer between 0 to 320.', true)
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('response')
|
||||
->inject('heroes')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization)
|
||||
{
|
||||
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$mock) {
|
||||
$userId = $user->getId();
|
||||
$email = $user->getAttribute('email', '');
|
||||
|
||||
$gitHub = $this->getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger, $authorization);
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
$isHero = \array_key_exists($email, $heroes);
|
||||
$isContributor = \in_array($githubId, $contributors);
|
||||
$isEmployee = \array_key_exists($email, $employees);
|
||||
|
||||
$isGolden = $isEmployee || $isHero || $isContributor;
|
||||
$isPlatinum = $user->getSequence() % 100 === 0;
|
||||
} else {
|
||||
$userId = '63e0bcf3c3eb803ba530';
|
||||
|
||||
$isGolden = $mock === 'golden';
|
||||
$isPlatinum = $mock === 'platinum';
|
||||
}
|
||||
|
||||
$userId = 'UID ' . $userId;
|
||||
|
||||
$isPlatinum = $isGolden ? false : $isPlatinum;
|
||||
|
||||
$imagePath = $isGolden ? 'back-golden.png' : ($isPlatinum ? 'back-platinum.png' : 'back.png');
|
||||
|
||||
$baseImage = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/' . $imagePath);
|
||||
|
||||
setlocale(LC_ALL, "en_US.utf8");
|
||||
// $userId = \iconv("utf-8", "ascii//TRANSLIT", $userId);
|
||||
|
||||
$text = new ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$text->setFont($this->getAppRoot() . '/public/fonts/SourceCodePro-Regular.ttf');
|
||||
$text->setFillColor(new ImagickPixel($isGolden ? '#664A1E' : ($isPlatinum ? '#555555' : '#E8E9F0')));
|
||||
$text->setFontSize(28);
|
||||
$text->setFontWeight(400);
|
||||
$baseImage->annotateImage($text, 512, 596, 0, $userId);
|
||||
|
||||
if (!empty($width) || !empty($height)) {
|
||||
$baseImage->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1);
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->setContentType('image/png')
|
||||
->file($baseImage->getImageBlob());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http\Cards\Cloud\Front;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Action;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Imagick;
|
||||
use ImagickDraw;
|
||||
use ImagickPixel;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getCloudCard';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(UtopiaAction::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/cards/cloud')
|
||||
->desc('Get front Of Cloud Card')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resourceType', 'cards/cloud')
|
||||
->label('cache.resource', 'card/{request.userId}')
|
||||
->label('docs', false)
|
||||
->label('origin', '*')
|
||||
->param('userId', '', new UID(), 'User ID.', true)
|
||||
->param('mock', '', new WhiteList(['employee', 'employee-2digit', 'hero', 'contributor', 'normal', 'platinum', 'normal-no-github', 'normal-long']), 'Mocking behaviour.', true)
|
||||
->param('width', 0, new Range(0, 512), 'Resize image width, Pass an integer between 0 to 512.', true)
|
||||
->param('height', 0, new Range(0, 320), 'Resize image height, Pass an integer between 0 to 320.', true)
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('response')
|
||||
->inject('heroes')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization)
|
||||
{
|
||||
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$mock) {
|
||||
$name = $user->getAttribute('name', 'Anonymous');
|
||||
$email = $user->getAttribute('email', '');
|
||||
$createdAt = new \DateTime($user->getCreatedAt());
|
||||
|
||||
$gitHub = $this->getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger, $authorization);
|
||||
$githubName = $gitHub['name'] ?? '';
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
$isHero = \array_key_exists($email, $heroes);
|
||||
$isContributor = \in_array($githubId, $contributors);
|
||||
$isEmployee = \array_key_exists($email, $employees);
|
||||
$employeeNumber = $isEmployee ? $employees[$email]['spot'] : '';
|
||||
|
||||
if ($isHero) {
|
||||
$createdAt = new \DateTime($heroes[$email]['memberSince'] ?? '');
|
||||
} elseif ($isEmployee) {
|
||||
$createdAt = new \DateTime($employees[$email]['memberSince'] ?? '');
|
||||
}
|
||||
|
||||
if (!$isEmployee && !empty($githubName)) {
|
||||
$employeeGitHub = \array_search(\strtolower($githubName), \array_map(fn ($employee) => \strtolower($employee['gitHub']) ?? '', $employees));
|
||||
if (!empty($employeeGitHub)) {
|
||||
$isEmployee = true;
|
||||
$employeeNumber = $isEmployee ? $employees[$employeeGitHub]['spot'] : '';
|
||||
$createdAt = new \DateTime($employees[$employeeGitHub]['memberSince'] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
$isPlatinum = $user->getSequence() % 100 === 0;
|
||||
} else {
|
||||
$name = $mock === 'normal-long' ? 'Sir First Walter O\'Brian Junior' : 'Walter O\'Brian';
|
||||
$createdAt = new \DateTime('now');
|
||||
$githubName = $mock === 'normal-no-github' ? '' : ($mock === 'normal-long' ? 'sir-first-walterobrian-junior' : 'walterobrian');
|
||||
$isHero = $mock === 'hero';
|
||||
$isContributor = $mock === 'contributor';
|
||||
$isEmployee = \str_starts_with($mock, 'employee');
|
||||
$employeeNumber = match ($mock) {
|
||||
'employee' => '1',
|
||||
'employee-2digit' => '18',
|
||||
default => ''
|
||||
};
|
||||
|
||||
$isPlatinum = $mock === 'platinum';
|
||||
}
|
||||
|
||||
if ($isEmployee) {
|
||||
$isContributor = false;
|
||||
$isHero = false;
|
||||
}
|
||||
|
||||
if ($isHero) {
|
||||
$isContributor = false;
|
||||
$isEmployee = false;
|
||||
}
|
||||
|
||||
if ($isContributor) {
|
||||
$isHero = false;
|
||||
$isEmployee = false;
|
||||
}
|
||||
|
||||
$isGolden = $isEmployee || $isHero || $isContributor;
|
||||
$isPlatinum = $isGolden ? false : $isPlatinum;
|
||||
$memberSince = \strtoupper('Member since ' . $createdAt->format('M') . ' ' . $createdAt->format('d') . ', ' . $createdAt->format('o'));
|
||||
|
||||
$imagePath = $isGolden ? 'front-golden.png' : ($isPlatinum ? 'front-platinum.png' : 'front.png');
|
||||
|
||||
$baseImage = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/' . $imagePath);
|
||||
|
||||
if ($isEmployee) {
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/employee.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 793, 35);
|
||||
|
||||
$text = new ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$text->setFont($this->getAppRoot() . '/public/fonts/Inter-Bold.ttf');
|
||||
$text->setFillColor(new ImagickPixel('#FFFADF'));
|
||||
$text->setFontSize(\strlen($employeeNumber) <= 2 ? 54 : 48);
|
||||
$text->setFontWeight(700);
|
||||
$metricsText = $baseImage->queryFontMetrics($text, $employeeNumber);
|
||||
|
||||
$hashtag = new ImagickDraw();
|
||||
$hashtag->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$hashtag->setFont($this->getAppRoot() . '/public/fonts/Inter-Bold.ttf');
|
||||
$hashtag->setFillColor(new ImagickPixel('#FFFADF'));
|
||||
$hashtag->setFontSize(28);
|
||||
$hashtag->setFontWeight(700);
|
||||
$metricsHashtag = $baseImage->queryFontMetrics($hashtag, '#');
|
||||
|
||||
$startX = 898;
|
||||
$totalWidth = $metricsHashtag['textWidth'] + 12 + $metricsText['textWidth'];
|
||||
|
||||
$hashtagX = ($metricsHashtag['textWidth'] / 2);
|
||||
$textX = $hashtagX + 12 + ($metricsText['textWidth'] / 2);
|
||||
|
||||
$hashtagX -= $totalWidth / 2;
|
||||
$textX -= $totalWidth / 2;
|
||||
|
||||
$hashtagX += $startX;
|
||||
$textX += $startX;
|
||||
|
||||
$baseImage->annotateImage($hashtag, $hashtagX, 150, 0, '#');
|
||||
$baseImage->annotateImage($text, $textX, 150, 0, $employeeNumber);
|
||||
}
|
||||
|
||||
if ($isContributor) {
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/contributor.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 793, 34);
|
||||
}
|
||||
|
||||
if ($isHero) {
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/hero.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 793, 34);
|
||||
}
|
||||
|
||||
setlocale(LC_ALL, "en_US.utf8");
|
||||
// $name = \iconv("utf-8", "ascii//TRANSLIT", $name);
|
||||
// $memberSince = \iconv("utf-8", "ascii//TRANSLIT", $memberSince);
|
||||
// $githubName = \iconv("utf-8", "ascii//TRANSLIT", $githubName);
|
||||
|
||||
$text = new ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$text->setFont($this->getAppRoot() . '/public/fonts/Inter-Bold.ttf');
|
||||
$text->setFillColor(new ImagickPixel('#FFFFFF'));
|
||||
|
||||
if (\strlen($name) > 32) {
|
||||
$name = \substr($name, 0, 32);
|
||||
}
|
||||
|
||||
if (\strlen($name) <= 23) {
|
||||
$text->setFontSize(80);
|
||||
$scalingDown = false;
|
||||
} else {
|
||||
$text->setFontSize(54);
|
||||
$scalingDown = true;
|
||||
}
|
||||
$text->setFontWeight(700);
|
||||
$baseImage->annotateImage($text, 512, 477, 0, $name);
|
||||
|
||||
$text = new ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$text->setFont($this->getAppRoot() . '/public/fonts/Inter-SemiBold.ttf');
|
||||
$text->setFillColor(new ImagickPixel($isGolden || $isPlatinum ? '#FFFFFF' : '#FFB9CC'));
|
||||
$text->setFontSize(27);
|
||||
$text->setFontWeight(600);
|
||||
$text->setTextKerning(1.08);
|
||||
$baseImage->annotateImage($text, 512, 541, 0, \strtoupper($memberSince));
|
||||
|
||||
if (!empty($githubName)) {
|
||||
$text = new ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$text->setFont($this->getAppRoot() . '/public/fonts/Inter-Regular.ttf');
|
||||
$text->setFillColor(new ImagickPixel('#FFFFFF'));
|
||||
$text->setFontSize($scalingDown ? 28 : 32);
|
||||
$text->setFontWeight(400);
|
||||
$metrics = $baseImage->queryFontMetrics($text, $githubName);
|
||||
|
||||
$baseImage->annotateImage($text, 512 + 20 + 4, 373 + ($scalingDown ? 2 : 0), 0, $githubName);
|
||||
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/github.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$precisionFix = 5;
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 512 - ($metrics['textWidth'] / 2) - 20 - 4, 373 - ($metrics['textHeight'] - $precisionFix));
|
||||
}
|
||||
|
||||
if (!empty($width) || !empty($height)) {
|
||||
$baseImage->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1);
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->setContentType('image/png')
|
||||
->file($baseImage->getImageBlob());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http\Cards\Cloud\OG;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Action;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Imagick;
|
||||
use ImagickDraw;
|
||||
use ImagickPixel;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getCloudCardOG';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(UtopiaAction::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/cards/cloud-og')
|
||||
->desc('Get OG image From Cloud Card')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resourceType', 'cards/cloud-og')
|
||||
->label('cache.resource', 'card-og/{request.userId}')
|
||||
->label('docs', false)
|
||||
->label('origin', '*')
|
||||
->param('userId', '', new UID(), 'User ID.', true)
|
||||
->param('mock', '', new WhiteList(['employee', 'employee-2digit', 'hero', 'contributor', 'normal', 'platinum', 'normal-no-github', 'normal-long', 'normal-long-right', 'normal-long-middle', 'normal-bg2', 'normal-bg3', 'normal-right', 'normal-middle', 'platinum-right', 'platinum-middle', 'hero-middle', 'hero-right', 'contributor-right', 'employee-right', 'contributor-middle', 'employee-middle', 'employee-2digit-middle', 'employee-2digit-right']), 'Mocking behaviour.', true)
|
||||
->param('width', 0, new Range(0, 1024), 'Resize image card width, Pass an integer between 0 to 1024.', true)
|
||||
->param('height', 0, new Range(0, 1024), 'Resize image card height, Pass an integer between 0 to 1024.', true)
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('response')
|
||||
->inject('heroes')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization)
|
||||
{
|
||||
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$mock) {
|
||||
$sequence = $user->getSequence();
|
||||
$bgVariation = $sequence % 3 === 0 ? '1' : ($sequence % 3 === 1 ? '2' : '3');
|
||||
$cardVariation = $sequence % 3 === 0 ? '1' : ($sequence % 3 === 1 ? '2' : '3');
|
||||
|
||||
$name = $user->getAttribute('name', 'Anonymous');
|
||||
$email = $user->getAttribute('email', '');
|
||||
$createdAt = new \DateTime($user->getCreatedAt());
|
||||
|
||||
$gitHub = $this->getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger, $authorization);
|
||||
$githubName = $gitHub['name'] ?? '';
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
$isHero = \array_key_exists($email, $heroes);
|
||||
$isContributor = \in_array($githubId, $contributors);
|
||||
$isEmployee = \array_key_exists($email, $employees);
|
||||
$employeeNumber = $isEmployee ? $employees[$email]['spot'] : '';
|
||||
|
||||
if ($isHero) {
|
||||
$createdAt = new \DateTime($heroes[$email]['memberSince'] ?? '');
|
||||
} elseif ($isEmployee) {
|
||||
$createdAt = new \DateTime($employees[$email]['memberSince'] ?? '');
|
||||
}
|
||||
|
||||
if (!$isEmployee && !empty($githubName)) {
|
||||
$employeeGitHub = \array_search(\strtolower($githubName), \array_map(fn ($employee) => \strtolower($employee['gitHub']) ?? '', $employees));
|
||||
if (!empty($employeeGitHub)) {
|
||||
$isEmployee = true;
|
||||
$employeeNumber = $isEmployee ? $employees[$employeeGitHub]['spot'] : '';
|
||||
$createdAt = new \DateTime($employees[$employeeGitHub]['memberSince'] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
$isPlatinum = $user->getSequence() % 100 === 0;
|
||||
} else {
|
||||
$bgVariation = \str_ends_with($mock, '-bg2') ? '2' : (\str_ends_with($mock, '-bg3') ? '3' : '1');
|
||||
$cardVariation = \str_ends_with($mock, '-right') ? '2' : (\str_ends_with($mock, '-middle') ? '3' : '1');
|
||||
$name = \str_starts_with($mock, 'normal-long') ? 'Sir First Walter O\'Brian Junior' : 'Walter O\'Brian';
|
||||
$createdAt = new \DateTime('now');
|
||||
$githubName = $mock === 'normal-no-github' ? '' : (\str_starts_with($mock, 'normal-long') ? 'sir-first-walterobrian-junior' : 'walterobrian');
|
||||
$isHero = \str_starts_with($mock, 'hero');
|
||||
$isContributor = \str_starts_with($mock, 'contributor');
|
||||
$isEmployee = \str_starts_with($mock, 'employee');
|
||||
$employeeNumber = match ($mock) {
|
||||
'employee' => '1',
|
||||
'employee-right' => '1',
|
||||
'employee-middle' => '1',
|
||||
'employee-2digit' => '18',
|
||||
'employee-2digit-right' => '18',
|
||||
'employee-2digit-middle' => '18',
|
||||
default => ''
|
||||
};
|
||||
|
||||
$isPlatinum = \str_starts_with($mock, 'platinum');
|
||||
}
|
||||
|
||||
if ($isEmployee) {
|
||||
$isContributor = false;
|
||||
$isHero = false;
|
||||
}
|
||||
|
||||
if ($isHero) {
|
||||
$isContributor = false;
|
||||
$isEmployee = false;
|
||||
}
|
||||
|
||||
if ($isContributor) {
|
||||
$isHero = false;
|
||||
$isEmployee = false;
|
||||
}
|
||||
|
||||
$isGolden = $isEmployee || $isHero || $isContributor;
|
||||
$isPlatinum = $isGolden ? false : $isPlatinum;
|
||||
$memberSince = \strtoupper('Member since ' . $createdAt->format('M') . ' ' . $createdAt->format('d') . ', ' . $createdAt->format('o'));
|
||||
|
||||
$baseImage = new Imagick($this->getAppRoot() . "/public/images/cards/cloud/og-background{$bgVariation}.png");
|
||||
|
||||
$cardType = $isGolden ? '-golden' : ($isPlatinum ? '-platinum' : '');
|
||||
|
||||
$image = new Imagick($this->getAppRoot() . "/public/images/cards/cloud/og-card{$cardType}{$cardVariation}.png");
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 1008 / 2 - $image->getImageWidth() / 2, 1008 / 2 - $image->getImageHeight() / 2);
|
||||
|
||||
$imageLogo = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/og-background-logo.png');
|
||||
$imageShadow = new Imagick($this->getAppRoot() . "/public/images/cards/cloud/og-shadow{$cardType}.png");
|
||||
if ($cardVariation === '1') {
|
||||
$baseImage->compositeImage($imageLogo, Imagick::COMPOSITE_OVER, 32, 1008 - $imageLogo->getImageHeight() - 32);
|
||||
$baseImage->compositeImage($imageShadow, Imagick::COMPOSITE_OVER, -450, 700);
|
||||
} elseif ($cardVariation === '2') {
|
||||
$baseImage->compositeImage($imageLogo, Imagick::COMPOSITE_OVER, 1008 - $imageLogo->getImageWidth() - 32, 1008 - $imageLogo->getImageHeight() - 32);
|
||||
$baseImage->compositeImage($imageShadow, Imagick::COMPOSITE_OVER, -20, 710);
|
||||
} else {
|
||||
$baseImage->compositeImage($imageLogo, Imagick::COMPOSITE_OVER, 1008 - $imageLogo->getImageWidth() - 32, 1008 - $imageLogo->getImageHeight() - 32);
|
||||
$baseImage->compositeImage($imageShadow, Imagick::COMPOSITE_OVER, -135, 710);
|
||||
}
|
||||
|
||||
if ($isEmployee) {
|
||||
$file = $cardVariation === '3' ? 'employee-skew.png' : 'employee.png';
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/' . $file);
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
|
||||
$hashtag = new ImagickDraw();
|
||||
$hashtag->setTextAlignment(Imagick::ALIGN_LEFT);
|
||||
$hashtag->setFont($this->getAppRoot() . '/public/fonts/Inter-Bold.ttf');
|
||||
$hashtag->setFillColor(new ImagickPixel('#FFFADF'));
|
||||
$hashtag->setFontSize(20);
|
||||
$hashtag->setFontWeight(700);
|
||||
|
||||
$text = new ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_LEFT);
|
||||
$text->setFont($this->getAppRoot() . '/public/fonts/Inter-Bold.ttf');
|
||||
$text->setFillColor(new ImagickPixel('#FFFADF'));
|
||||
$text->setFontSize(\strlen($employeeNumber) <= 1 ? 36 : 28);
|
||||
$text->setFontWeight(700);
|
||||
|
||||
if ($cardVariation === '3') {
|
||||
$hashtag->setFontSize(16);
|
||||
$text->setFontSize(\strlen($employeeNumber) <= 1 ? 30 : 26);
|
||||
|
||||
$hashtag->skewY(20);
|
||||
$hashtag->skewX(20);
|
||||
$text->skewY(20);
|
||||
$text->skewX(20);
|
||||
}
|
||||
|
||||
$metricsHashtag = $baseImage->queryFontMetrics($hashtag, '#');
|
||||
$metricsText = $baseImage->queryFontMetrics($text, $employeeNumber);
|
||||
|
||||
$group = new Imagick();
|
||||
$groupWidth = $metricsHashtag['textWidth'] + 6 + $metricsText['textWidth'];
|
||||
|
||||
if ($cardVariation === '1') {
|
||||
$group->newImage($groupWidth, $metricsText['textHeight'], '#00000000');
|
||||
$group->annotateImage($hashtag, 0, $metricsText['textHeight'], 0, '#');
|
||||
$group->annotateImage($text, $metricsHashtag['textWidth'] + 6, $metricsText['textHeight'], 0, $employeeNumber);
|
||||
|
||||
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
|
||||
$image->rotateImage(new ImagickPixel('#00000000'), -20);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 612, 203);
|
||||
|
||||
$group->rotateImage(new ImagickPixel('#00000000'), -22);
|
||||
|
||||
if (\strlen($employeeNumber) <= 1) {
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 660, 245);
|
||||
} else {
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 655, 247);
|
||||
}
|
||||
} elseif ($cardVariation === '2') {
|
||||
$group->newImage($groupWidth, $metricsText['textHeight'], '#00000000');
|
||||
$group->annotateImage($hashtag, 0, $metricsText['textHeight'], 0, '#');
|
||||
$group->annotateImage($text, $metricsHashtag['textWidth'] + 6, $metricsText['textHeight'], 0, $employeeNumber);
|
||||
|
||||
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
|
||||
$image->rotateImage(new ImagickPixel('#00000000'), 30);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 715, 425);
|
||||
|
||||
$group->rotateImage(new ImagickPixel('#00000000'), 32);
|
||||
|
||||
if (\strlen($employeeNumber) <= 1) {
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 775, 465);
|
||||
} else {
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 767, 470);
|
||||
}
|
||||
} else {
|
||||
$group->newImage(300, 300, '#00000000');
|
||||
|
||||
$hashtag->annotation(0, $metricsText['textHeight'], '#');
|
||||
$text->annotation($metricsHashtag['textWidth'] + 2, $metricsText['textHeight'], $employeeNumber);
|
||||
|
||||
$group->drawImage($hashtag);
|
||||
$group->drawImage($text);
|
||||
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 640, 293);
|
||||
|
||||
if (\strlen($employeeNumber) <= 1) {
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 670, 317);
|
||||
} else {
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 663, 322);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($isContributor) {
|
||||
$file = $cardVariation === '3' ? 'contributor-skew.png' : 'contributor.png';
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/' . $file);
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
|
||||
if ($cardVariation === '1') {
|
||||
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
|
||||
$image->rotateImage(new ImagickPixel('#00000000'), -20);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 612, 203);
|
||||
} elseif ($cardVariation === '2') {
|
||||
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
|
||||
$image->rotateImage(new ImagickPixel('#00000000'), 30);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 715, 425);
|
||||
} else {
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 640, 293);
|
||||
}
|
||||
}
|
||||
|
||||
if ($isHero) {
|
||||
$file = $cardVariation === '3' ? 'hero-skew.png' : 'hero.png';
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/' . $file);
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
|
||||
if ($cardVariation === '1') {
|
||||
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
|
||||
$image->rotateImage(new ImagickPixel('#00000000'), -20);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 612, 203);
|
||||
} elseif ($cardVariation === '2') {
|
||||
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
|
||||
$image->rotateImage(new ImagickPixel('#00000000'), 30);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 715, 425);
|
||||
} else {
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 640, 293);
|
||||
}
|
||||
}
|
||||
|
||||
setlocale(LC_ALL, "en_US.utf8");
|
||||
// $name = \iconv("utf-8", "ascii//TRANSLIT", $name);
|
||||
// $memberSince = \iconv("utf-8", "ascii//TRANSLIT", $memberSince);
|
||||
// $githubName = \iconv("utf-8", "ascii//TRANSLIT", $githubName);
|
||||
|
||||
$textName = new ImagickDraw();
|
||||
$textName->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$textName->setFont($this->getAppRoot() . '/public/fonts/Inter-Bold.ttf');
|
||||
$textName->setFillColor(new ImagickPixel('#FFFFFF'));
|
||||
|
||||
if (\strlen($name) > 32) {
|
||||
$name = \substr($name, 0, 32);
|
||||
}
|
||||
|
||||
if ($cardVariation === '1') {
|
||||
if (\strlen($name) <= 23) {
|
||||
$scalingDown = false;
|
||||
$textName->setFontSize(54);
|
||||
} else {
|
||||
$scalingDown = true;
|
||||
$textName->setFontSize(36);
|
||||
}
|
||||
} elseif ($cardVariation === '2') {
|
||||
if (\strlen($name) <= 23) {
|
||||
$scalingDown = false;
|
||||
$textName->setFontSize(50);
|
||||
} else {
|
||||
$scalingDown = true;
|
||||
$textName->setFontSize(34);
|
||||
}
|
||||
} else {
|
||||
if (\strlen($name) <= 23) {
|
||||
$scalingDown = false;
|
||||
$textName->setFontSize(44);
|
||||
} else {
|
||||
$scalingDown = true;
|
||||
$textName->setFontSize(32);
|
||||
}
|
||||
}
|
||||
|
||||
$textName->setFontWeight(700);
|
||||
|
||||
$textMember = new ImagickDraw();
|
||||
$textMember->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
$textMember->setFont($this->getAppRoot() . '/public/fonts/Inter-Medium.ttf');
|
||||
$textMember->setFillColor(new ImagickPixel($isGolden || $isPlatinum ? '#FFFFFF' : '#FFB9CC'));
|
||||
$textMember->setFontWeight(500);
|
||||
$textMember->setTextKerning(1.12);
|
||||
|
||||
if ($cardVariation === '1') {
|
||||
$textMember->setFontSize(21);
|
||||
|
||||
$baseImage->annotateImage($textName, 550, 600, -22, $name);
|
||||
$baseImage->annotateImage($textMember, 585, 635, -22, $memberSince);
|
||||
} elseif ($cardVariation === '2') {
|
||||
$textMember->setFontSize(20);
|
||||
|
||||
$baseImage->annotateImage($textName, 435, 590, 31.37, $name);
|
||||
$baseImage->annotateImage($textMember, 412, 628, 31.37, $memberSince);
|
||||
} else {
|
||||
$textMember->setFontSize(16);
|
||||
|
||||
$textName->skewY(20);
|
||||
$textName->skewX(20);
|
||||
$textName->annotation(320, 700, $name);
|
||||
|
||||
$textMember->skewY(20);
|
||||
$textMember->skewX(20);
|
||||
$textMember->annotation(330, 735, $memberSince);
|
||||
|
||||
$baseImage->drawImage($textName);
|
||||
$baseImage->drawImage($textMember);
|
||||
}
|
||||
|
||||
if (!empty($githubName)) {
|
||||
$text = new ImagickDraw();
|
||||
$text->setTextAlignment(Imagick::ALIGN_LEFT);
|
||||
$text->setFont($this->getAppRoot() . '/public/fonts/Inter-Regular.ttf');
|
||||
$text->setFillColor(new ImagickPixel('#FFFFFF'));
|
||||
$text->setFontSize($scalingDown ? 16 : 20);
|
||||
$text->setFontWeight(400);
|
||||
|
||||
if ($cardVariation === '1') {
|
||||
$metrics = $baseImage->queryFontMetrics($text, $githubName);
|
||||
|
||||
$group = new Imagick();
|
||||
$groupWidth = $metrics['textWidth'] + 32 + 4;
|
||||
$group->newImage($groupWidth, $metrics['textHeight'] + 10, '#00000000');
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/github.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$image->resizeImage(32, 32, Imagick::FILTER_LANCZOS, 1);
|
||||
$precisionFix = -1;
|
||||
|
||||
$group->compositeImage($image, Imagick::COMPOSITE_OVER, 0, 0);
|
||||
$group->annotateImage($text, 32 + 4, $metrics['textHeight'] - $precisionFix, 0, $githubName);
|
||||
|
||||
$group->rotateImage(new ImagickPixel('#00000000'), -22);
|
||||
$x = 510 - $group->getImageWidth() / 2;
|
||||
$y = 530 - $group->getImageHeight() / 2;
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, $x, $y);
|
||||
} elseif ($cardVariation === '2') {
|
||||
$metrics = $baseImage->queryFontMetrics($text, $githubName);
|
||||
|
||||
$group = new Imagick();
|
||||
$groupWidth = $metrics['textWidth'] + 32 + 4;
|
||||
$group->newImage($groupWidth, $metrics['textHeight'] + 10, '#00000000');
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/github.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$image->resizeImage(32, 32, Imagick::FILTER_LANCZOS, 1);
|
||||
$precisionFix = -1;
|
||||
|
||||
$group->compositeImage($image, Imagick::COMPOSITE_OVER, 0, 0);
|
||||
$group->annotateImage($text, 32 + 4, $metrics['textHeight'] - $precisionFix, 0, $githubName);
|
||||
|
||||
$group->rotateImage(new ImagickPixel('#00000000'), 31.11);
|
||||
$x = 485 - $group->getImageWidth() / 2;
|
||||
$y = 530 - $group->getImageHeight() / 2;
|
||||
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, $x, $y);
|
||||
} else {
|
||||
$text->skewY(20);
|
||||
$text->skewX(20);
|
||||
$text->setTextAlignment(Imagick::ALIGN_CENTER);
|
||||
|
||||
$text->annotation(320 + 15 + 2, 640, $githubName);
|
||||
$metrics = $baseImage->queryFontMetrics($text, $githubName);
|
||||
|
||||
$image = new Imagick($this->getAppRoot() . '/public/images/cards/cloud/github-skew.png');
|
||||
$image->setGravity(Imagick::GRAVITY_CENTER);
|
||||
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 512 - ($metrics['textWidth'] / 2), 518 + \strlen($githubName) * 1.3);
|
||||
|
||||
$baseImage->drawImage($text);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($width) || !empty($height)) {
|
||||
$baseImage->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1);
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
->setContentType('image/png')
|
||||
->file($baseImage->getImageBlob());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http\CreditCards;
|
||||
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getCreditCard';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(UtopiaAction::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/avatars/credit-cards/:code')
|
||||
->desc('Get credit card icon')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resource', 'avatar/credit-card')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getCreditCard',
|
||||
description: '/docs/references/avatars/get-credit-card.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE_PNG
|
||||
))
|
||||
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-credit-cards'))), 'Credit Card Code. Possible values: ' . \implode(', ', \array_keys(Config::getParam('avatar-credit-cards'))) . '.')
|
||||
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('quality', -1, new Range(-1, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true)
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $code, int $width, int $height, int $quality, Response $response)
|
||||
{
|
||||
$this->avatar('credit-cards', $code, $width, $height, $quality, $response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http\Favicon;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\URL\URL as URLParse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use enshrined\svgSanitize\Sanitizer as SvgSanitizer;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Fetch\Client;
|
||||
use Utopia\Image\Image;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\URL;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getFavicon';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(UtopiaAction::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/avatars/favicon')
|
||||
->desc('Get favicon')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resource', 'avatar/favicon')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getFavicon',
|
||||
description: '/docs/references/avatars/get-favicon.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE
|
||||
))
|
||||
->param('url', '', new URL(['http', 'https']), 'Website URL which you want to fetch the favicon from.')
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $url, Response $response)
|
||||
{
|
||||
$width = 56;
|
||||
$height = 56;
|
||||
$quality = 80;
|
||||
$output = 'png';
|
||||
$type = 'png';
|
||||
|
||||
if (!\extension_loaded('imagick')) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
|
||||
}
|
||||
|
||||
$domain = new Domain(\parse_url($url, PHP_URL_HOST));
|
||||
|
||||
if (!$domain->isKnown()) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
$client = new Client();
|
||||
try {
|
||||
$res = $client
|
||||
->setAllowRedirects(true)
|
||||
->setMaxRedirects(5)
|
||||
->setUserAgent(\sprintf(
|
||||
APP_USERAGENT,
|
||||
System::getEnv('_APP_VERSION', 'UNKNOWN'),
|
||||
System::getEnv('_APP_EMAIL_SECURITY', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY))
|
||||
))
|
||||
->fetch($url);
|
||||
} catch (\Throwable) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
$doc = new DOMDocument();
|
||||
$doc->strictErrorChecking = false;
|
||||
@$doc->loadHTML($res->getBody());
|
||||
|
||||
$links = $doc->getElementsByTagName('link') ?? [];
|
||||
$outputHref = '';
|
||||
$outputExt = '';
|
||||
$space = 0;
|
||||
|
||||
foreach ($links as $link) { /* @var $link DOMElement */
|
||||
$href = $link->getAttribute('href');
|
||||
$rel = $link->getAttribute('rel');
|
||||
$sizes = $link->getAttribute('sizes');
|
||||
$absolute = URLParse::unparse(\array_merge(\parse_url($url), \parse_url($href)));
|
||||
|
||||
switch (\strtolower($rel)) {
|
||||
case 'icon':
|
||||
case 'shortcut icon':
|
||||
//case 'apple-touch-icon':
|
||||
$ext = \pathinfo(\parse_url($absolute, PHP_URL_PATH), PATHINFO_EXTENSION);
|
||||
|
||||
switch ($ext) {
|
||||
case 'svg':
|
||||
// SVG icons are prioritized by assigning the maximum possible value.
|
||||
$space = PHP_INT_MAX;
|
||||
$outputHref = $absolute;
|
||||
$outputExt = $ext;
|
||||
break;
|
||||
case 'ico':
|
||||
case 'png':
|
||||
case 'jpg':
|
||||
case 'jpeg':
|
||||
$size = \explode('x', \strtolower($sizes));
|
||||
|
||||
$sizeWidth = (int) ($size[0] ?? 0);
|
||||
$sizeHeight = (int) ($size[1] ?? 0);
|
||||
|
||||
if (($sizeWidth * $sizeHeight) >= $space) {
|
||||
$space = $sizeWidth * $sizeHeight;
|
||||
$outputHref = $absolute;
|
||||
$outputExt = $ext;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($outputHref) || empty($outputExt)) {
|
||||
$default = \parse_url($url);
|
||||
|
||||
$outputHref = $default['scheme'] . '://' . $default['host'] . '/favicon.ico';
|
||||
$outputExt = 'ico';
|
||||
}
|
||||
|
||||
$domain = new Domain(\parse_url($outputHref, PHP_URL_HOST));
|
||||
|
||||
if (!$domain->isKnown()) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
$client = new Client();
|
||||
try {
|
||||
$res = $client
|
||||
->setAllowRedirects(true)
|
||||
->setMaxRedirects(5)
|
||||
->fetch($outputHref);
|
||||
} catch (\Throwable) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
if ($res->getStatusCode() !== 200) {
|
||||
throw new Exception(Exception::AVATAR_ICON_NOT_FOUND);
|
||||
}
|
||||
|
||||
$data = $res->getBody();
|
||||
|
||||
if ('ico' === $outputExt) { // Skip crop, Imagick isn\'t supporting icon files
|
||||
if (
|
||||
empty($data) ||
|
||||
stripos($data, '<html') === 0 ||
|
||||
stripos($data, '<!doc') === 0
|
||||
) {
|
||||
throw new Exception(Exception::AVATAR_ICON_NOT_FOUND, 'Favicon not found');
|
||||
}
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
->setContentType('image/x-icon')
|
||||
->file($data);
|
||||
return;
|
||||
}
|
||||
|
||||
if ('svg' === $outputExt) { // Skip crop, Imagick isn\'t supporting svg files
|
||||
$sanitizer = new SvgSanitizer();
|
||||
$sanitizer->minify(true);
|
||||
$cleanSvg = $sanitizer->sanitize($data);
|
||||
if ($cleanSvg === false) {
|
||||
throw new Exception(Exception::AVATAR_SVG_SANITIZATION_FAILED);
|
||||
}
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
->setContentType('image/svg+xml')
|
||||
->file($cleanSvg);
|
||||
return;
|
||||
}
|
||||
|
||||
$image = new Image($data);
|
||||
$image->crop((int) $width, (int) $height);
|
||||
$output = (empty($output)) ? $type : $output;
|
||||
$data = $image->output($output, $quality);
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
->setContentType('image/png')
|
||||
->file($data);
|
||||
unset($image);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http\Flags;
|
||||
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getFlag';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(UtopiaAction::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/avatars/flags/:code')
|
||||
->desc('Get country flag')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resource', 'avatar/flag')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getFlag',
|
||||
description: '/docs/references/avatars/get-flag.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE_PNG
|
||||
))
|
||||
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-flags'))), 'Country Code. ISO Alpha-2 country code format.')
|
||||
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
|
||||
->param('quality', -1, new Range(-1, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true)
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $code, int $width, int $height, int $quality, Response $response)
|
||||
{
|
||||
$this->avatar('flags', $code, $width, $height, $quality, $response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Avatars\Http\Image;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Avatars\Http\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\MethodType;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Fetch\Client;
|
||||
use Utopia\Image\Image;
|
||||
use Utopia\Platform\Action as UtopiaAction;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\URL;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'getImage';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(UtopiaAction::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/avatars/image')
|
||||
->desc('Get image from URL')
|
||||
->groups(['api', 'avatars'])
|
||||
->label('scope', 'avatars.read')
|
||||
->label('cache', true)
|
||||
->label('cache.resource', 'avatar/image')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'avatars',
|
||||
group: null,
|
||||
name: 'getImage',
|
||||
description: '/docs/references/avatars/get-image.md',
|
||||
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::KEY, AuthType::JWT],
|
||||
type: MethodType::LOCATION,
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::IMAGE
|
||||
))
|
||||
->param('url', '', new URL(['http', 'https']), 'Image URL which you want to crop.')
|
||||
->param('width', 400, new Range(0, 2000), 'Resize preview image width, Pass an integer between 0 to 2000. Defaults to 400.', true)
|
||||
->param('height', 400, new Range(0, 2000), 'Resize preview image height, Pass an integer between 0 to 2000. Defaults to 400.', true)
|
||||
->inject('response')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(string $url, int $width, int $height, Response $response)
|
||||
{
|
||||
$quality = 80;
|
||||
$output = 'png';
|
||||
$type = 'png';
|
||||
|
||||
if (!\extension_loaded('imagick')) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
|
||||
}
|
||||
|
||||
$domain = new Domain(\parse_url($url, PHP_URL_HOST));
|
||||
|
||||
if (!$domain->isKnown()) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
$client = new Client();
|
||||
try {
|
||||
$res = $client
|
||||
->setAllowRedirects(false)
|
||||
->fetch($url);
|
||||
} catch (\Throwable) {
|
||||
throw new Exception(Exception::AVATAR_REMOTE_URL_FAILED);
|
||||
}
|
||||
|
||||
if ($res->getStatusCode() !== 200) {
|
||||
throw new Exception(Exception::AVATAR_IMAGE_NOT_FOUND);
|
||||
}
|
||||
|
||||
try {
|
||||
$image = new Image($res->getBody());
|
||||
} catch (\Throwable $exception) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unable to parse image');
|
||||
}
|
||||
|
||||
$image->crop((int) $width, (int) $height);
|
||||
$output = (empty($output)) ? $type : $output;
|
||||
$data = $image->output($output, $quality);
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
->setContentType('image/png')
|
||||
->file($data);
|
||||
unset($image);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user