mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
160
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cea242c66f | ||
|
|
ce4eb563b3 | ||
|
|
2307d637fb | ||
|
|
6e1f0d57af | ||
|
|
62b6ef06e6 | ||
|
|
f5ab593261 | ||
|
|
dd4a43b78c | ||
|
|
f71a98a527 | ||
|
|
6dba407aed | ||
|
|
7eef845556 | ||
|
|
7781d377ae | ||
|
|
59bf4663ca | ||
|
|
9ea0b2bc2f | ||
|
|
715727853b | ||
|
|
43d4f709d5 | ||
|
|
f40050fe6f | ||
|
|
34dfcba45c | ||
|
|
cb1e31bcd4 | ||
|
|
3e4e7fc0cd | ||
|
|
9403e4d65d | ||
|
|
07a49835a6 | ||
|
|
d32de6f217 | ||
|
|
9b00ce4f1d | ||
|
|
8f6c8f9d8d | ||
|
|
23fcb284a1 | ||
|
|
d66813d3cf | ||
|
|
0ce92da41f | ||
|
|
e8ef4e40d7 | ||
|
|
d200b07466 | ||
|
|
92abfb31aa | ||
|
|
6c56eee0f4 | ||
|
|
399c37d943 | ||
|
|
f3acadd53c | ||
|
|
331fcee710 | ||
|
|
951a96ae01 | ||
|
|
7864a5b9d1 | ||
|
|
f11bd7ce0e | ||
|
|
ed91549681 | ||
|
|
04173600ae | ||
|
|
b74d4d45f9 | ||
|
|
aa9a58b44d | ||
|
|
59a773e9a0 | ||
|
|
e3053bb83d | ||
|
|
1f7fc4bd40 | ||
|
|
d1b59ff3f3 | ||
|
|
221b52bac0 | ||
|
|
b8ed30db55 | ||
|
|
be56317bf2 | ||
|
|
d1549b225b | ||
|
|
a06ba57384 | ||
|
|
dffcfe8ee4 | ||
|
|
452440f3c0 | ||
|
|
b236e2546b | ||
|
|
412d09b801 | ||
|
|
f3b9121a14 | ||
|
|
3018b478ba | ||
|
|
094fe90499 | ||
|
|
4df5f4a18f | ||
|
|
e8bcc94187 | ||
|
|
e576cd5082 | ||
|
|
30befc6a60 | ||
|
|
d13644a47a | ||
|
|
4a905a6ac9 | ||
|
|
b554243447 | ||
|
|
15b2ab321e | ||
|
|
789870b545 | ||
|
|
c9f7b7f0d9 | ||
|
|
cba7e53898 | ||
|
|
fb26da5df1 | ||
|
|
eb8455bd76 | ||
|
|
a76a03d988 | ||
|
|
cc04c682b0 | ||
|
|
66ba483b6a | ||
|
|
908e408480 | ||
|
|
ff903e7cbb | ||
|
|
10ebf942b1 | ||
|
|
d9a75c5938 | ||
|
|
bb80e50d01 | ||
|
|
644840ec66 | ||
|
|
0d6d6a0a35 | ||
|
|
e8d4065af5 | ||
|
|
008110b3f7 | ||
|
|
ae99d59aba | ||
|
|
a06aaaf9ca | ||
|
|
4970d1b6f2 | ||
|
|
094cd180b5 | ||
|
|
a8f43f3486 | ||
|
|
038f4b5992 | ||
|
|
c903fb87ac | ||
|
|
e5841c4cc0 | ||
|
|
bb7815bd57 | ||
|
|
0c33d981a7 | ||
|
|
36d8ad6e7c | ||
|
|
fbce66d500 | ||
|
|
89db65299d | ||
|
|
ea6a05be4f | ||
|
|
355acf6824 | ||
|
|
c2988efa08 | ||
|
|
8ccf5094f8 | ||
|
|
39f2d24907 | ||
|
|
d3c5a425e7 | ||
|
|
4641596a6d | ||
|
|
d932527561 | ||
|
|
89c072e223 | ||
|
|
6421bc8689 | ||
|
|
d008d9bff0 | ||
|
|
f6dc6359bb | ||
|
|
032638e896 | ||
|
|
10cc6a8040 | ||
|
|
9ecdbf5950 | ||
|
|
55b436c67b | ||
|
|
05defcc6e0 | ||
|
|
6700340ef3 | ||
|
|
4224f6ea5a | ||
|
|
8d925f3670 | ||
|
|
d2875c9bf6 | ||
|
|
c002f4afe3 | ||
|
|
ecb8104340 | ||
|
|
e6090800e2 | ||
|
|
0237cced6b | ||
|
|
625ec4ce91 | ||
|
|
5339592b3f | ||
|
|
9f1d092717 | ||
|
|
e38a4e4347 | ||
|
|
fdbc5b6737 | ||
|
|
fa2b7955d0 | ||
|
|
ea11af1e15 | ||
|
|
6cbae88131 | ||
|
|
03b2755aaf | ||
|
|
2095a94bc5 | ||
|
|
df7796d5cb | ||
|
|
d177e3adfc | ||
|
|
d04ac5c3e6 | ||
|
|
60939da801 | ||
|
|
cdb301a293 | ||
|
|
14a9aa890f | ||
|
|
d3833d06d5 | ||
|
|
5ba19ec763 | ||
|
|
0564936c4f | ||
|
|
c2795376d8 | ||
|
|
2b898dfe50 | ||
|
|
fa1404be52 | ||
|
|
be87675e1c | ||
|
|
b1edf75c4e | ||
|
|
8a0f092342 | ||
|
|
87b02de612 | ||
|
|
f8e9f71de3 | ||
|
|
c055c32371 | ||
|
|
d60858cd8f | ||
|
|
22bd655ad1 | ||
|
|
c900b22dc0 | ||
|
|
27e5ade92b | ||
|
|
d9c1b9db2a | ||
|
|
eb399c7bd0 | ||
|
|
b8e51366d7 | ||
|
|
b75cd993be | ||
|
|
a1bc503ce4 | ||
|
|
e475a7ac5a | ||
|
|
2dc24dabfd | ||
|
|
aaa2a0525f |
@@ -161,6 +161,27 @@ jobs:
|
||||
- name: Run PHPStan
|
||||
run: composer analyze -- --no-progress
|
||||
|
||||
specs:
|
||||
name: Checks / Specs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.3'
|
||||
extensions: swoole
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --prefer-dist --no-progress --ignore-platform-reqs
|
||||
|
||||
- name: Generate specs
|
||||
run: _APP_STORAGE_LIMIT=5368709120 php app/cli.php specs --version=latest --git=no
|
||||
|
||||
locale:
|
||||
name: Checks / Locale
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -44,7 +44,7 @@ Table of Contents:
|
||||
|
||||
## Products
|
||||
|
||||
- **[Appwrite Auth](https://appwrite.io/docs/products/authentication)** - Secure user authentication with multiple login methods including email/password, SMS, OAuth, anonymous sessions, and magic links. Includes session management, multi-factor authentication, and user verification flows.
|
||||
- **[Appwrite Auth](https://appwrite.io/docs/products/auth)** - Secure user authentication with multiple login methods including email/password, SMS, OAuth, anonymous sessions, and magic links. Includes session management, multi-factor authentication, and user verification flows.
|
||||
|
||||
- **[Appwrite Databases](https://appwrite.io/docs/products/databases)** - Scalable structured data storage with support for databases, tables, and rows. Includes querying, pagination, indexing, and relationships to model complex application data.
|
||||
|
||||
|
||||
+34
-38
@@ -18,13 +18,15 @@ use Swoole\Timer;
|
||||
use Utopia\Cache\Adapter\Pool as CachePool;
|
||||
use Utopia\Cache\Adapter\Sharding;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\CLI\Adapters\Generic;
|
||||
use Utopia\CLI\CLI;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Console;
|
||||
use Utopia\Database\Adapter\Pool as DatabasePool;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\DI\Dependency;
|
||||
use Utopia\DI\Container;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Platform\Service;
|
||||
@@ -47,7 +49,7 @@ require_once __DIR__ . '/controllers/general.php';
|
||||
global $register;
|
||||
|
||||
$platform = new Appwrite();
|
||||
$args = $platform->getEnv('argv');
|
||||
$args = $_SERVER['argv'] ?? [];
|
||||
|
||||
\array_shift($args);
|
||||
if (! isset($args[0])) {
|
||||
@@ -56,21 +58,15 @@ if (! isset($args[0])) {
|
||||
}
|
||||
|
||||
$taskName = $args[0];
|
||||
$container = new Container();
|
||||
$cli = new CLI(new Generic(), $_SERVER['argv'] ?? [], $container);
|
||||
|
||||
$platform->setCli($cli);
|
||||
$platform->init(Service::TYPE_TASK);
|
||||
$cli = $platform->getCli();
|
||||
|
||||
$setResource = function (string $name, callable $callback, array $injections = []) use ($cli) {
|
||||
$dependency = new Dependency();
|
||||
$dependency->setName($name)->setCallback($callback);
|
||||
foreach ($injections as $injection) {
|
||||
$dependency->inject($injection);
|
||||
}
|
||||
$cli->setResource($dependency);
|
||||
};
|
||||
$container->set('register', fn () => $register, []);
|
||||
|
||||
$setResource('register', fn () => $register, []);
|
||||
|
||||
$setResource('cache', function ($pools) {
|
||||
$container->set('cache', function ($pools) {
|
||||
$list = Config::getParam('pools-cache', []);
|
||||
$adapters = [];
|
||||
|
||||
@@ -81,18 +77,18 @@ $setResource('cache', function ($pools) {
|
||||
return new Cache(new Sharding($adapters));
|
||||
}, ['pools']);
|
||||
|
||||
$setResource('pools', function (Registry $register) {
|
||||
$container->set('pools', function (Registry $register) {
|
||||
return $register->get('pools');
|
||||
}, ['register']);
|
||||
|
||||
$setResource('authorization', function () {
|
||||
$container->set('authorization', function () {
|
||||
$authorization = new Authorization();
|
||||
$authorization->disable();
|
||||
|
||||
return $authorization;
|
||||
}, []);
|
||||
|
||||
$setResource('dbForPlatform', function ($pools, $cache, $authorization) {
|
||||
$container->set('dbForPlatform', function ($pools, $cache, $authorization) {
|
||||
$sleep = 3;
|
||||
$maxAttempts = 5;
|
||||
$attempts = 0;
|
||||
@@ -135,17 +131,17 @@ $setResource('dbForPlatform', function ($pools, $cache, $authorization) {
|
||||
return $dbForPlatform;
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
$setResource('console', function () {
|
||||
$container->set('console', function () {
|
||||
return new Document(Config::getParam('console'));
|
||||
}, []);
|
||||
|
||||
$setResource(
|
||||
$container->set(
|
||||
'isResourceBlocked',
|
||||
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false,
|
||||
[]
|
||||
);
|
||||
|
||||
$setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, $authorization) {
|
||||
$container->set('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, $authorization, &$databases) {
|
||||
@@ -207,10 +203,10 @@ $setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $c
|
||||
};
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
|
||||
|
||||
$setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$container->set('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$database = null;
|
||||
|
||||
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
|
||||
return function (?Document $project = null) use ($pools, $cache, &$database, $authorization) {
|
||||
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$database->setTenant($project->getSequence());
|
||||
return $database;
|
||||
@@ -235,41 +231,41 @@ $setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $a
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
$setResource('publisher', function (Group $pools) {
|
||||
$container->set('publisher', function (Group $pools) {
|
||||
return new BrokerPool(publisher: $pools->get('publisher'));
|
||||
}, ['pools']);
|
||||
$setResource('publisherDatabases', function (BrokerPool $publisher) {
|
||||
$container->set('publisherDatabases', function (BrokerPool $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
$setResource('publisherFunctions', function (BrokerPool $publisher) {
|
||||
$container->set('publisherFunctions', function (BrokerPool $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
$setResource('publisherMigrations', function (BrokerPool $publisher) {
|
||||
$container->set('publisherMigrations', function (BrokerPool $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
$setResource('publisherMessaging', function (BrokerPool $publisher) {
|
||||
$container->set('publisherMessaging', function (BrokerPool $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
$setResource('usage', function () {
|
||||
$container->set('usage', function () {
|
||||
return new UsageContext();
|
||||
}, []);
|
||||
$setResource('publisherForUsage', fn (Publisher $publisher) => new UsagePublisher(
|
||||
$container->set('publisherForUsage', fn (Publisher $publisher) => new UsagePublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
$setResource('queueForStatsResources', function (Publisher $publisher) {
|
||||
$container->set('queueForStatsResources', function (Publisher $publisher) {
|
||||
return new StatsResources($publisher);
|
||||
}, ['publisher']);
|
||||
$setResource('queueForFunctions', function (Publisher $publisher) {
|
||||
$container->set('queueForFunctions', function (Publisher $publisher) {
|
||||
return new Func($publisher);
|
||||
}, ['publisher']);
|
||||
$setResource('queueForDeletes', function (Publisher $publisher) {
|
||||
$container->set('queueForDeletes', function (Publisher $publisher) {
|
||||
return new Delete($publisher);
|
||||
}, ['publisher']);
|
||||
$setResource('queueForCertificates', function (Publisher $publisher) {
|
||||
$container->set('queueForCertificates', function (Publisher $publisher) {
|
||||
return new Certificate($publisher);
|
||||
}, ['publisher']);
|
||||
$setResource('logError', function (Registry $register) {
|
||||
$container->set('logError', function (Registry $register) {
|
||||
return function (Throwable $error, string $namespace, string $action) use ($register) {
|
||||
Console::error('[Error] Timestamp: ' . date('c', time()));
|
||||
Console::error('[Error] Type: ' . get_class($error));
|
||||
@@ -321,13 +317,13 @@ $setResource('logError', function (Registry $register) {
|
||||
};
|
||||
}, ['register']);
|
||||
|
||||
$setResource('executor', fn () => new Executor(), []);
|
||||
$container->set('executor', fn () => new Executor(), []);
|
||||
|
||||
$setResource('bus', function (Registry $register) use ($cli) {
|
||||
return $register->get('bus')->setResolver(fn (string $name) => $cli->getResource($name));
|
||||
$container->set('bus', function (Registry $register) use ($container) {
|
||||
return $register->get('bus')->setResolver(fn (string $name) => $container->get($name));
|
||||
}, ['register']);
|
||||
|
||||
$setResource('telemetry', fn () => new NoTelemetry(), []);
|
||||
$container->set('telemetry', fn () => new NoTelemetry(), []);
|
||||
|
||||
$exitCode = 0;
|
||||
|
||||
|
||||
@@ -594,7 +594,7 @@ $platformCollections = [
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('key'),
|
||||
'$id' => ID::custom('key'), // For app platforms
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => Database::LENGTH_KEY,
|
||||
@@ -605,7 +605,7 @@ $platformCollections = [
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('store'),
|
||||
'$id' => ID::custom('store'), // Unused at the moment
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
@@ -616,7 +616,7 @@ $platformCollections = [
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('hostname'),
|
||||
'$id' => ID::custom('hostname'), // For web platforms
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 256,
|
||||
|
||||
@@ -1179,6 +1179,16 @@ return [
|
||||
'description' => 'Platform with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::PLATFORM_METHOD_UNSUPPORTED => [
|
||||
'name' => Exception::PLATFORM_METHOD_UNSUPPORTED,
|
||||
'description' => 'The requested platform has invalid type. Please use corresponding update method for the platform type.',
|
||||
'code' => 400,
|
||||
],
|
||||
Exception::PLATFORM_ALREADY_EXISTS => [
|
||||
'name' => Exception::PLATFORM_ALREADY_EXISTS,
|
||||
'description' => 'Platform with the same ID already exists in this project. Try again with a different ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::VARIABLE_NOT_FOUND => [
|
||||
'name' => Exception::VARIABLE_NOT_FOUND,
|
||||
'description' => 'Variable with the requested ID could not be found.',
|
||||
|
||||
@@ -207,7 +207,7 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, arr
|
||||
}
|
||||
|
||||
|
||||
$createSession = function (string $userId, string $secret, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Store $store, ProofsToken $proofForToken, ProofsCode $proofForCode, Authorization $authorization) {
|
||||
$createSession = function (string $userId, string $secret, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Store $store, ProofsToken $proofForToken, ProofsCode $proofForCode, bool $domainVerification, ?string $cookieDomain, Authorization $authorization) {
|
||||
|
||||
// Attempt to decode secret as a JWT (used by OAuth2 token flow to carry provider info)
|
||||
$oauthProvider = null;
|
||||
@@ -345,7 +345,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
|
||||
->setProperty('secret', $sessionSecret)
|
||||
->encode();
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
if (!$domainVerification) {
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded]));
|
||||
}
|
||||
|
||||
@@ -353,8 +353,8 @@ $createSession = function (string $userId, string $secret, Request $request, Res
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
$response
|
||||
->addCookie($store->getKey() . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie($store->getKey(), $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->addCookie($store->getKey() . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', $cookieDomain, ('https' == $protocol), true, null)
|
||||
->addCookie($store->getKey(), $encoded, (new \DateTime($expire))->getTimestamp(), '/', $cookieDomain, ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED);
|
||||
|
||||
$countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'));
|
||||
@@ -719,7 +719,9 @@ Http::delete('/v1/account/sessions')
|
||||
->inject('queueForDeletes')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->action(function (Request $request, Response $response, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Store $store, ProofsToken $proofForToken) {
|
||||
->inject('domainVerification')
|
||||
->inject('cookieDomain')
|
||||
->action(function (Request $request, Response $response, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Store $store, ProofsToken $proofForToken, bool $domainVerification, ?string $cookieDomain) {
|
||||
|
||||
$protocol = $request->getProtocol();
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
@@ -728,7 +730,7 @@ Http::delete('/v1/account/sessions')
|
||||
foreach ($sessions as $session) {/** @var Document $session */
|
||||
$dbForProject->deleteDocument('sessions', $session->getId());
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
if (!$domainVerification) {
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([]));
|
||||
}
|
||||
|
||||
@@ -741,8 +743,8 @@ Http::delete('/v1/account/sessions')
|
||||
|
||||
// If current session delete the cookies too
|
||||
$response
|
||||
->addCookie($store->getKey() . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie($store->getKey(), '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'));
|
||||
->addCookie($store->getKey() . '_legacy', '', \time() - 3600, '/', $cookieDomain, ('https' == $protocol), true, null)
|
||||
->addCookie($store->getKey(), '', \time() - 3600, '/', $cookieDomain, ('https' == $protocol), true, Config::getParam('cookieSamesite'));
|
||||
|
||||
// Use current session for events.
|
||||
$currentSession = $session;
|
||||
@@ -849,7 +851,9 @@ Http::delete('/v1/account/sessions/:sessionId')
|
||||
->inject('queueForDeletes')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Store $store, ProofsToken $proofForToken) {
|
||||
->inject('domainVerification')
|
||||
->inject('cookieDomain')
|
||||
->action(function (?string $sessionId, ?\DateTime $requestTimestamp, Request $request, Response $response, User $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Delete $queueForDeletes, Store $store, ProofsToken $proofForToken, bool $domainVerification, ?string $cookieDomain) {
|
||||
|
||||
$protocol = $request->getProtocol();
|
||||
$sessionId = ($sessionId === 'current')
|
||||
@@ -875,13 +879,13 @@ Http::delete('/v1/account/sessions/:sessionId')
|
||||
->setAttribute('current', true)
|
||||
->setAttribute('countryName', $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown')));
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
if (!$domainVerification) {
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([]));
|
||||
}
|
||||
|
||||
$response
|
||||
->addCookie($store->getKey() . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie($store->getKey(), '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'));
|
||||
->addCookie($store->getKey() . '_legacy', '', \time() - 3600, '/', $cookieDomain, ('https' == $protocol), true, null)
|
||||
->addCookie($store->getKey(), '', \time() - 3600, '/', $cookieDomain, ('https' == $protocol), true, Config::getParam('cookieSamesite'));
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
@@ -1035,8 +1039,10 @@ Http::post('/v1/account/sessions/email')
|
||||
->inject('store')
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForToken')
|
||||
->inject('domainVerification')
|
||||
->inject('cookieDomain')
|
||||
->inject('authorization')
|
||||
->action(function (string $email, string $password, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, Authorization $authorization) {
|
||||
->action(function (string $email, string $password, Request $request, Response $response, User $user, Database $dbForProject, Document $project, array $platform, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, bool $domainVerification, ?string $cookieDomain, Authorization $authorization) {
|
||||
$email = \strtolower($email);
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
@@ -1097,28 +1103,28 @@ Http::post('/v1/account/sessions/email')
|
||||
]));
|
||||
}
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session->setAttribute('$permissions', [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
Permission::delete(Role::user($user->getId())),
|
||||
]));
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
$encoded = $store
|
||||
->setProperty('id', $user->getId())
|
||||
->setProperty('secret', $secret)
|
||||
->encode();
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
if (!$domainVerification) {
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded]));
|
||||
}
|
||||
|
||||
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration));
|
||||
|
||||
$response
|
||||
->addCookie($store->getKey() . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie($store->getKey(), $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->addCookie($store->getKey() . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', $cookieDomain, ('https' == $protocol), true, null)
|
||||
->addCookie($store->getKey(), $encoded, (new \DateTime($expire))->getTimestamp(), '/', $cookieDomain, ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
;
|
||||
|
||||
@@ -1184,8 +1190,10 @@ Http::post('/v1/account/sessions/anonymous')
|
||||
->inject('store')
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForToken')
|
||||
->inject('domainVerification')
|
||||
->inject('cookieDomain')
|
||||
->inject('authorization')
|
||||
->action(function (Request $request, Response $response, Locale $locale, User $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, Authorization $authorization) {
|
||||
->action(function (Request $request, Response $response, Locale $locale, User $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, bool $domainVerification, ?string $cookieDomain, Authorization $authorization) {
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
if ('console' === $project->getId()) {
|
||||
@@ -1276,15 +1284,15 @@ Http::post('/v1/account/sessions/anonymous')
|
||||
->setProperty('secret', $secret)
|
||||
->encode();
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
if (!$domainVerification) {
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded]));
|
||||
}
|
||||
|
||||
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration));
|
||||
|
||||
$response
|
||||
->addCookie($store->getKey() . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie($store->getKey(), $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->addCookie($store->getKey() . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', $cookieDomain, ('https' == $protocol), true, null)
|
||||
->addCookie($store->getKey(), $encoded, (new \DateTime($expire))->getTimestamp(), '/', $cookieDomain, ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
;
|
||||
|
||||
@@ -1339,7 +1347,9 @@ Http::post('/v1/account/sessions/token')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->inject('proofForCode')
|
||||
->inject('authorization')
|
||||
->inject('domainVerification')
|
||||
->inject('cookieDomain')
|
||||
->inject('authorization')
|
||||
->action($createSession);
|
||||
|
||||
Http::get('/v1/account/sessions/oauth2/:provider')
|
||||
@@ -1538,8 +1548,10 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
->inject('proofForPassword')
|
||||
->inject('proofForToken')
|
||||
->inject('plan')
|
||||
->inject('domainVerification')
|
||||
->inject('cookieDomain')
|
||||
->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, Database $dbForPlatform, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, array $plan, Authorization $authorization) use ($oauthDefaultSuccess) {
|
||||
->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, Validator $redirectValidator, Document $devKey, User $user, Database $dbForProject, Database $dbForPlatform, Reader $geodb, Event $queueForEvents, Store $store, ProofsPassword $proofForPassword, ProofsToken $proofForToken, array $plan, bool $domainVerification, ?string $cookieDomain, Authorization $authorization) use ($oauthDefaultSuccess) {
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$port = $request->getPort();
|
||||
$callbackBase = $protocol . '://' . $request->getHostname();
|
||||
@@ -2055,7 +2067,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
->setProperty('secret', $secret)
|
||||
->encode();
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
if (!$domainVerification) {
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded]));
|
||||
}
|
||||
|
||||
@@ -2068,14 +2080,14 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
// TODO: Remove this deprecated workaround - support only token
|
||||
if ($state['success']['path'] == $oauthDefaultSuccess) {
|
||||
$query['project'] = $project->getId();
|
||||
$query['domain'] = Config::getParam('cookieDomain');
|
||||
$query['domain'] = $cookieDomain;
|
||||
$query['key'] = $store->getKey();
|
||||
$query['secret'] = $encoded;
|
||||
}
|
||||
|
||||
$response
|
||||
->addCookie($store->getKey() . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie($store->getKey(), $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'));
|
||||
->addCookie($store->getKey() . '_legacy', $encoded, (new \DateTime($expire))->getTimestamp(), '/', $cookieDomain, ('https' == $protocol), true, null)
|
||||
->addCookie($store->getKey(), $encoded, (new \DateTime($expire))->getTimestamp(), '/', $cookieDomain, ('https' == $protocol), true, Config::getParam('cookieSamesite'));
|
||||
}
|
||||
|
||||
if (isset($sessionUpgrade) && $sessionUpgrade && isset($session)) {
|
||||
@@ -2886,11 +2898,13 @@ Http::put('/v1/account/sessions/magic-url')
|
||||
->inject('queueForMails')
|
||||
->inject('store')
|
||||
->inject('proofForCode')
|
||||
->inject('domainVerification')
|
||||
->inject('cookieDomain')
|
||||
->inject('authorization')
|
||||
->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForCode, $authorization) use ($createSession) {
|
||||
->action(function ($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForCode, $domainVerification, $cookieDomain, $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, $authorization);
|
||||
$createSession($userId, $secret, $request, $response, $user, $dbForProject, $project, $platform, $locale, $geodb, $queueForEvents, $queueForMails, $store, $proofForToken, $proofForCode, $domainVerification, $cookieDomain, $authorization);
|
||||
});
|
||||
|
||||
Http::put('/v1/account/sessions/phone')
|
||||
@@ -2936,6 +2950,8 @@ Http::put('/v1/account/sessions/phone')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->inject('proofForCode')
|
||||
->inject('domainVerification')
|
||||
->inject('cookieDomain')
|
||||
->inject('authorization')
|
||||
->action($createSession);
|
||||
|
||||
@@ -3727,7 +3743,9 @@ Http::patch('/v1/account/status')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->inject('store')
|
||||
->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Store $store) {
|
||||
->inject('domainVerification')
|
||||
->inject('cookieDomain')
|
||||
->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Store $store, bool $domainVerification, ?string $cookieDomain) {
|
||||
|
||||
$user->setAttribute('status', false);
|
||||
|
||||
@@ -3737,14 +3755,14 @@ Http::patch('/v1/account/status')
|
||||
->setParam('userId', $user->getId())
|
||||
->setPayload($response->output($user, Response::MODEL_ACCOUNT));
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
if (!$domainVerification) {
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([]));
|
||||
}
|
||||
|
||||
$protocol = $request->getProtocol();
|
||||
$response
|
||||
->addCookie($store->getKey() . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||
->addCookie($store->getKey(), '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
->addCookie($store->getKey() . '_legacy', '', \time() - 3600, '/', $cookieDomain, ('https' == $protocol), true, null)
|
||||
->addCookie($store->getKey(), '', \time() - 3600, '/', $cookieDomain, ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||
;
|
||||
|
||||
$response->dynamic($user, Response::MODEL_ACCOUNT);
|
||||
|
||||
@@ -1093,272 +1093,6 @@ Http::post('/v1/projects/:projectId/jwts')
|
||||
])]), Response::MODEL_JWT);
|
||||
});
|
||||
|
||||
// Platforms
|
||||
|
||||
Http::post('/v1/projects/:projectId/platforms')
|
||||
->desc('Create platform')
|
||||
->groups(['api', 'projects'])
|
||||
->label('audits.event', 'platforms.create')
|
||||
->label('audits.resource', 'project/{request.projectId}')
|
||||
->label('scope', 'platforms.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'platforms',
|
||||
name: 'createPlatform',
|
||||
description: '/docs/references/projects/create-platform.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
model: Response::MODEL_PLATFORM,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
|
||||
->param(
|
||||
'type',
|
||||
null,
|
||||
new WhiteList([
|
||||
Platform::TYPE_WEB,
|
||||
Platform::TYPE_FLUTTER_WEB,
|
||||
Platform::TYPE_FLUTTER_IOS,
|
||||
Platform::TYPE_FLUTTER_ANDROID,
|
||||
Platform::TYPE_FLUTTER_LINUX,
|
||||
Platform::TYPE_FLUTTER_MACOS,
|
||||
Platform::TYPE_FLUTTER_WINDOWS,
|
||||
Platform::TYPE_APPLE_IOS,
|
||||
Platform::TYPE_APPLE_MACOS,
|
||||
Platform::TYPE_APPLE_WATCHOS,
|
||||
Platform::TYPE_APPLE_TVOS,
|
||||
Platform::TYPE_ANDROID,
|
||||
Platform::TYPE_UNITY,
|
||||
Platform::TYPE_REACT_NATIVE_IOS,
|
||||
Platform::TYPE_REACT_NATIVE_ANDROID,
|
||||
], true),
|
||||
'Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, flutter-linux, flutter-macos, flutter-windows, apple-ios, apple-macos, apple-watchos, apple-tvos, android, unity, react-native-ios, react-native-android.'
|
||||
)
|
||||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('key', '', new Text(256), 'Package name for Android or bundle ID for iOS or macOS. Max length: 256 chars.', true)
|
||||
->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true)
|
||||
->param('hostname', '', new Hostname(), 'Platform client hostname. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $type, string $name, string $key, string $store, string $hostname, Response $response, Database $dbForPlatform) {
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$platform = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
Permission::update(Role::any()),
|
||||
Permission::delete(Role::any()),
|
||||
],
|
||||
'projectInternalId' => $project->getSequence(),
|
||||
'projectId' => $project->getId(),
|
||||
'type' => $type,
|
||||
'name' => $name,
|
||||
'key' => $key,
|
||||
'store' => $store,
|
||||
'hostname' => $hostname
|
||||
]);
|
||||
|
||||
$platform = $dbForPlatform->createDocument('platforms', $platform);
|
||||
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($platform, Response::MODEL_PLATFORM);
|
||||
});
|
||||
|
||||
Http::get('/v1/projects/:projectId/platforms')
|
||||
->desc('List platforms')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'platforms.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'platforms',
|
||||
name: 'listPlatforms',
|
||||
description: '/docs/references/projects/list-platforms.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PLATFORM_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, bool $includeTotal, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$platforms = $dbForPlatform->find('platforms', [
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
Query::limit(5000),
|
||||
]);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'platforms' => $platforms,
|
||||
'total' => $includeTotal ? count($platforms) : 0,
|
||||
]), Response::MODEL_PLATFORM_LIST);
|
||||
});
|
||||
|
||||
Http::get('/v1/projects/:projectId/platforms/:platformId')
|
||||
->desc('Get platform')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'platforms.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'platforms',
|
||||
name: 'getPlatform',
|
||||
description: '/docs/references/projects/get-platform.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PLATFORM,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
|
||||
->param('platformId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform unique ID.', false, ['dbForPlatform'])
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $platformId, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$platform = $dbForPlatform->findOne('platforms', [
|
||||
Query::equal('$id', [$platformId]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
]);
|
||||
|
||||
if ($platform->isEmpty()) {
|
||||
throw new Exception(Exception::PLATFORM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$response->dynamic($platform, Response::MODEL_PLATFORM);
|
||||
});
|
||||
|
||||
Http::put('/v1/projects/:projectId/platforms/:platformId')
|
||||
->desc('Update platform')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'platforms.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'platforms',
|
||||
name: 'updatePlatform',
|
||||
description: '/docs/references/projects/update-platform.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PLATFORM,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
|
||||
->param('platformId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform unique ID.', false, ['dbForPlatform'])
|
||||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('key', '', new Text(256), 'Package name for android or bundle ID for iOS. Max length: 256 chars.', true)
|
||||
->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true)
|
||||
->param('hostname', '', new Hostname(), 'Platform client URL. Max length: 256 chars.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $platformId, string $name, string $key, string $store, string $hostname, Response $response, Database $dbForPlatform) {
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$platform = $dbForPlatform->findOne('platforms', [
|
||||
Query::equal('$id', [$platformId]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
]);
|
||||
|
||||
if ($platform->isEmpty()) {
|
||||
throw new Exception(Exception::PLATFORM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$platform
|
||||
->setAttribute('name', $name)
|
||||
->setAttribute('key', $key)
|
||||
->setAttribute('store', $store)
|
||||
->setAttribute('hostname', $hostname);
|
||||
|
||||
$dbForPlatform->updateDocument('platforms', $platform->getId(), $platform);
|
||||
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
|
||||
$response->dynamic($platform, Response::MODEL_PLATFORM);
|
||||
});
|
||||
|
||||
Http::delete('/v1/projects/:projectId/platforms/:platformId')
|
||||
->desc('Delete platform')
|
||||
->groups(['api', 'projects'])
|
||||
->label('audits.event', 'platforms.delete')
|
||||
->label('audits.resource', 'project/{request.projectId}/platform/${request.platformId}')
|
||||
->label('scope', 'platforms.write')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'platforms',
|
||||
name: 'deletePlatform',
|
||||
description: '/docs/references/projects/delete-platform.md',
|
||||
auth: [AuthType::ADMIN],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::NONE
|
||||
))
|
||||
->param('projectId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Project unique ID.', false, ['dbForPlatform'])
|
||||
->param('platformId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform unique ID.', false, ['dbForPlatform'])
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $platformId, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$platform = $dbForPlatform->findOne('platforms', [
|
||||
Query::equal('$id', [$platformId]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
]);
|
||||
|
||||
if ($platform->isEmpty()) {
|
||||
throw new Exception(Exception::PLATFORM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$dbForPlatform->deleteDocument('platforms', $platformId);
|
||||
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
|
||||
// CUSTOM SMTP and Templates
|
||||
Http::patch('/v1/projects/:projectId/smtp')
|
||||
->desc('Update SMTP')
|
||||
|
||||
@@ -61,8 +61,6 @@ use Utopia\System\System;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
Config::setParam('domainVerification', false);
|
||||
Config::setParam('cookieDomain', 'localhost');
|
||||
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
|
||||
|
||||
function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, Bus $bus, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey, DeleteEvent $queueForDeletes, int $executionsRetentionCount)
|
||||
@@ -754,11 +752,8 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
|
||||
}
|
||||
|
||||
if (\is_array($values)) {
|
||||
$count = 0;
|
||||
foreach ($values as $value) {
|
||||
$override = $count === 0;
|
||||
$response->addHeader($name, $value, override: $override);
|
||||
$count++;
|
||||
$response->addHeader($name, $value);
|
||||
}
|
||||
} else {
|
||||
$response->addHeader($name, $values);
|
||||
@@ -904,40 +899,16 @@ Http::init()
|
||||
$locale->setDefault($localeParam);
|
||||
}
|
||||
|
||||
$origin = \parse_url($request->getOrigin($request->getReferer('')), PHP_URL_HOST);
|
||||
$selfDomain = new Domain($request->getHostname());
|
||||
$endDomain = new Domain((string)$origin);
|
||||
Config::setParam(
|
||||
'domainVerification',
|
||||
($selfDomain->getRegisterable() === $endDomain->getRegisterable()) &&
|
||||
$endDomain->getRegisterable() !== ''
|
||||
);
|
||||
|
||||
$localHosts = ['localhost','localhost:'.$request->getPort()];
|
||||
|
||||
$migrationHost = System::getEnv('_APP_MIGRATION_HOST');
|
||||
if (!empty($migrationHost)) {
|
||||
// Treat the migration host like localhost because internal migration and
|
||||
// CI traffic may use it before a public domain is configured.
|
||||
$localHosts[] = $migrationHost;
|
||||
$localHosts[] = $migrationHost.':'.$request->getPort();
|
||||
}
|
||||
|
||||
$isLocalHost = in_array($request->getHostname(), $localHosts);
|
||||
$isIpAddress = filter_var($request->getHostname(), FILTER_VALIDATE_IP) !== false;
|
||||
|
||||
$isConsoleProject = $project->getAttribute('$id', '') === 'console';
|
||||
$isConsoleRootSession = System::getEnv('_APP_CONSOLE_ROOT_SESSION', 'disabled') === 'enabled';
|
||||
|
||||
Config::setParam(
|
||||
'cookieDomain',
|
||||
$isLocalHost || $isIpAddress
|
||||
? null
|
||||
: (
|
||||
$isConsoleProject && $isConsoleRootSession
|
||||
? '.' . $selfDomain->getRegisterable()
|
||||
: '.' . $request->getHostname()
|
||||
)
|
||||
);
|
||||
|
||||
$warnings = [];
|
||||
|
||||
/*
|
||||
@@ -1500,7 +1471,9 @@ Http::error()
|
||||
try {
|
||||
$cors = $utopia->getResource('cors');
|
||||
foreach ($cors->headers($request->getOrigin()) as $name => $value) {
|
||||
$response->addHeader($name, $value, override: true);
|
||||
$response
|
||||
->removeHeader($name)
|
||||
->addHeader($name, $value);
|
||||
}
|
||||
} catch (Throwable) {
|
||||
// Degrade gracefully - error response without CORS is no worse than before.
|
||||
|
||||
@@ -98,6 +98,9 @@ Http::init()
|
||||
->inject('authorization')
|
||||
->action(function (Http $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, User $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization) {
|
||||
$route = $utopia->getRoute();
|
||||
if ($route === null) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle user authentication and session validation.
|
||||
@@ -489,6 +492,10 @@ Http::init()
|
||||
$request->setUser($user);
|
||||
|
||||
$route = $utopia->getRoute();
|
||||
if ($route === null) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$path = $route->getMatchedPath();
|
||||
$databaseType = match (true) {
|
||||
str_contains($path, '/documentsdb') => DATABASE_TYPE_DOCUMENTSDB,
|
||||
|
||||
+57
-54
@@ -1,16 +1,13 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
require_once __DIR__ . '/init.php';
|
||||
require_once __DIR__ . '/init/span.php';
|
||||
|
||||
global $register;
|
||||
$registerRequestResources = require __DIR__ . '/init/resources/request.php';
|
||||
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Swoole\Constant;
|
||||
use Swoole\Http\Request as SwooleRequest;
|
||||
use Swoole\Http\Response as SwooleResponse;
|
||||
use Swoole\Http\Server;
|
||||
use Swoole\Process;
|
||||
use Swoole\Table;
|
||||
use Swoole\Timer;
|
||||
@@ -29,6 +26,7 @@ use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Http\Adapter\Swoole\Server;
|
||||
use Utopia\Http\Files;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Logger\Log;
|
||||
@@ -49,18 +47,35 @@ $certifiedDomains = new Table(100_000);
|
||||
$certifiedDomains->column('value', Table::TYPE_INT, 1);
|
||||
$certifiedDomains->create();
|
||||
|
||||
Http::setResource('riskyDomains', fn () => $riskyDomains);
|
||||
Http::setResource('certifiedDomains', fn () => $certifiedDomains);
|
||||
|
||||
$http = new Server(
|
||||
host: "0.0.0.0",
|
||||
port: System::getEnv('PORT', 80),
|
||||
mode: SWOOLE_PROCESS,
|
||||
);
|
||||
global $container;
|
||||
$container->set('riskyDomains', fn () => $riskyDomains);
|
||||
$container->set('certifiedDomains', fn () => $certifiedDomains);
|
||||
$container->set('pools', function ($register) {
|
||||
return $register->get('pools');
|
||||
}, ['register']);
|
||||
|
||||
$payloadSize = 12 * (1024 * 1024); // 12MB - adding slight buffer for headers and other data that might be sent with the payload - update later with valid testing
|
||||
$totalWorkers = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
|
||||
|
||||
$swooleAdapter = new Server(
|
||||
host: "0.0.0.0",
|
||||
port: System::getEnv('PORT', 80),
|
||||
settings: [
|
||||
Constant::OPTION_WORKER_NUM => $totalWorkers,
|
||||
Constant::OPTION_DISPATCH_FUNC => dispatch(...),
|
||||
Constant::OPTION_DISPATCH_MODE => SWOOLE_DISPATCH_UIDMOD,
|
||||
Constant::OPTION_HTTP_COMPRESSION => false,
|
||||
Constant::OPTION_PACKAGE_MAX_LENGTH => $payloadSize,
|
||||
Constant::OPTION_OUTPUT_BUFFER_SIZE => $payloadSize,
|
||||
Constant::OPTION_TASK_WORKER_NUM => 1, // required for the task to fetch domains background
|
||||
],
|
||||
container: $container,
|
||||
);
|
||||
|
||||
$container->set('container', fn () => fn () => $swooleAdapter->getContainer());
|
||||
|
||||
$http = $swooleAdapter->getServer();
|
||||
|
||||
/**
|
||||
* Assigns HTTP requests to worker threads by analyzing its payload/content.
|
||||
*
|
||||
@@ -69,16 +84,16 @@ $totalWorkers = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intva
|
||||
* riskier tasks to a dedicated worker subset. Prefers idle workers, with fallback to random selection if necessary.
|
||||
* doc: https://openswoole.com/docs/modules/swoole-server/configuration#dispatch_func
|
||||
*
|
||||
* @param Server $server Swoole server instance.
|
||||
* @param \Swoole\Http\Server $server Swoole server instance.
|
||||
* @param int $fd client ID
|
||||
* @param int $type the type of data and its current state
|
||||
* @param string|null $data Request content for categorization.
|
||||
* @global int $totalThreads Total number of workers.
|
||||
* @return int Chosen worker ID for the request.
|
||||
*/
|
||||
function dispatch(Server $server, int $fd, int $type, $data = null): int
|
||||
function dispatch(\Swoole\Http\Server $server, int $fd, int $type, $data = null): int
|
||||
{
|
||||
$resolveWorkerId = function (Server $server, $data = null) {
|
||||
$resolveWorkerId = function (\Swoole\Http\Server $server, $data = null) {
|
||||
global $totalWorkers, $riskyDomains;
|
||||
|
||||
// If data is not set we can send request to any worker
|
||||
@@ -161,18 +176,6 @@ function dispatch(Server $server, int $fd, int $type, $data = null): int
|
||||
return $workerId;
|
||||
}
|
||||
|
||||
|
||||
$http
|
||||
->set([
|
||||
Constant::OPTION_WORKER_NUM => $totalWorkers,
|
||||
Constant::OPTION_DISPATCH_FUNC => dispatch(...),
|
||||
Constant::OPTION_DISPATCH_MODE => SWOOLE_DISPATCH_UIDMOD,
|
||||
Constant::OPTION_HTTP_COMPRESSION => false,
|
||||
Constant::OPTION_PACKAGE_MAX_LENGTH => $payloadSize,
|
||||
Constant::OPTION_OUTPUT_BUFFER_SIZE => $payloadSize,
|
||||
Constant::OPTION_TASK_WORKER_NUM => 1, // required for the task to fetch domains background
|
||||
]);
|
||||
|
||||
$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) {
|
||||
});
|
||||
|
||||
@@ -189,16 +192,14 @@ $http->on(Constant::EVENT_AFTER_RELOAD, function ($server) {
|
||||
Console::success('Reload completed...');
|
||||
});
|
||||
|
||||
Http::setResource('bus', function ($register, $utopia) {
|
||||
return $register->get('bus')->setResolver(fn (string $name) => $utopia->getResource($name));
|
||||
}, ['register', 'utopia']);
|
||||
$container->set('bus', function ($register) use ($swooleAdapter) {
|
||||
return $register->get('bus')->setResolver(fn (string $name) => $swooleAdapter->getContainer()->get($name));
|
||||
}, ['register']);
|
||||
|
||||
include __DIR__ . '/controllers/general.php';
|
||||
|
||||
function createDatabase(Http $app, string $resourceKey, string $dbName, array $collections, mixed $pools, ?callable $extraSetup = null): void
|
||||
{
|
||||
$max = 15;
|
||||
$sleep = 2;
|
||||
$max = 15;
|
||||
$sleep = 2;
|
||||
$attempts = 0;
|
||||
@@ -289,13 +290,13 @@ function createDatabase(Http $app, string $resourceKey, string $dbName, array $c
|
||||
Span::current()?->finish();
|
||||
}
|
||||
|
||||
$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $totalWorkers, $register) {
|
||||
$app = new Http('UTC');
|
||||
$http->on(Constant::EVENT_START, function ($http) use ($payloadSize, $totalWorkers, $swooleAdapter) {
|
||||
$app = new Http($swooleAdapter, 'UTC');
|
||||
|
||||
go(function () use ($register, $app) {
|
||||
$pools = $register->get('pools');
|
||||
/** @var \Utopia\Pools\Group $pools */
|
||||
Http::setResource('pools', fn () => $pools);
|
||||
/** @var \Utopia\Pools\Group $pools */
|
||||
$pools = $app->getResource('pools');
|
||||
|
||||
go(function () use ($app, $pools) {
|
||||
|
||||
/** @var array $collections */
|
||||
$collections = Config::getParam('collections', []);
|
||||
@@ -511,14 +512,11 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $tot
|
||||
});
|
||||
});
|
||||
|
||||
$http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register, $files) {
|
||||
$swooleAdapter->onRequest(function ($utopiaRequest, $utopiaResponse) use ($files, $swooleAdapter, $registerRequestResources) {
|
||||
Span::init('http.request');
|
||||
|
||||
Http::setResource('swooleRequest', fn () => $swooleRequest);
|
||||
Http::setResource('swooleResponse', fn () => $swooleResponse);
|
||||
|
||||
$request = new Request($swooleRequest);
|
||||
$response = new Response($swooleResponse);
|
||||
$request = new Request($utopiaRequest->getSwooleRequest());
|
||||
$response = new Response($utopiaResponse->getSwooleResponse());
|
||||
|
||||
Span::add('http.method', $request->getMethod());
|
||||
|
||||
@@ -534,13 +532,18 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
return;
|
||||
}
|
||||
|
||||
$app = new Http('UTC');
|
||||
$requestContainer = $swooleAdapter->getContainer();
|
||||
$requestContainer->set('request', fn () => $request);
|
||||
$requestContainer->set('response', fn () => $response);
|
||||
|
||||
$app = new Http($swooleAdapter, 'UTC');
|
||||
$requestContainer->set('utopia', fn () => $app);
|
||||
|
||||
$registerRequestResources($requestContainer);
|
||||
|
||||
$app->setCompression(System::getEnv('_APP_COMPRESSION_ENABLED', 'enabled') === 'enabled');
|
||||
$app->setCompressionMinSize(intval(System::getEnv('_APP_COMPRESSION_MIN_SIZE_BYTES', '1024'))); // 1KB
|
||||
|
||||
$pools = $register->get('pools');
|
||||
Http::setResource('pools', fn () => $pools);
|
||||
|
||||
try {
|
||||
$authorization = $app->getResource('authorization');
|
||||
|
||||
@@ -624,6 +627,7 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
}
|
||||
}
|
||||
|
||||
$swooleResponse = $utopiaResponse->getSwooleResponse();
|
||||
$swooleResponse->setStatusCode(500);
|
||||
|
||||
$output = ((Http::isDevelopment())) ? [
|
||||
@@ -647,11 +651,10 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
});
|
||||
|
||||
// Fetch domains every `DOMAIN_SYNC_TIMER` seconds and update in the memory
|
||||
$http->on(Constant::EVENT_TASK, function () use ($register) {
|
||||
$http->on(Constant::EVENT_TASK, function () use ($swooleAdapter) {
|
||||
$lastSyncUpdate = null;
|
||||
$pools = $register->get('pools');
|
||||
Http::setResource('pools', fn () => $pools);
|
||||
$app = new Http('UTC');
|
||||
|
||||
$app = new Http($swooleAdapter, 'UTC');
|
||||
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
$dbForPlatform = $app->getResource('dbForPlatform');
|
||||
@@ -726,4 +729,4 @@ $http->on(Constant::EVENT_TASK, function () use ($register) {
|
||||
});
|
||||
});
|
||||
|
||||
$http->start();
|
||||
$swooleAdapter->start();
|
||||
|
||||
+12
-3
@@ -106,7 +106,12 @@ use Appwrite\Utopia\Response\Model\Mock;
|
||||
use Appwrite\Utopia\Response\Model\MockNumber;
|
||||
use Appwrite\Utopia\Response\Model\None;
|
||||
use Appwrite\Utopia\Response\Model\Phone;
|
||||
use Appwrite\Utopia\Response\Model\Platform;
|
||||
use Appwrite\Utopia\Response\Model\PlatformAndroid;
|
||||
use Appwrite\Utopia\Response\Model\PlatformApple;
|
||||
use Appwrite\Utopia\Response\Model\PlatformLinux;
|
||||
use Appwrite\Utopia\Response\Model\PlatformList;
|
||||
use Appwrite\Utopia\Response\Model\PlatformWeb;
|
||||
use Appwrite\Utopia\Response\Model\PlatformWindows;
|
||||
use Appwrite\Utopia\Response\Model\Preferences;
|
||||
use Appwrite\Utopia\Response\Model\Project;
|
||||
use Appwrite\Utopia\Response\Model\Provider;
|
||||
@@ -197,7 +202,6 @@ Response::setModel(new BaseList('Webhooks List', Response::MODEL_WEBHOOK_LIST, '
|
||||
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));
|
||||
Response::setModel(new BaseList('Countries List', Response::MODEL_COUNTRY_LIST, 'countries', Response::MODEL_COUNTRY));
|
||||
Response::setModel(new BaseList('Continents List', Response::MODEL_CONTINENT_LIST, 'continents', Response::MODEL_CONTINENT));
|
||||
Response::setModel(new BaseList('Languages List', Response::MODEL_LANGUAGE_LIST, 'languages', Response::MODEL_LANGUAGE));
|
||||
@@ -333,7 +337,12 @@ Response::setModel(new Key());
|
||||
Response::setModel(new DevKey());
|
||||
Response::setModel(new MockNumber());
|
||||
Response::setModel(new AuthProvider());
|
||||
Response::setModel(new Platform());
|
||||
Response::setModel(new PlatformWeb());
|
||||
Response::setModel(new PlatformApple());
|
||||
Response::setModel(new PlatformAndroid());
|
||||
Response::setModel(new PlatformWindows());
|
||||
Response::setModel(new PlatformLinux());
|
||||
Response::setModel(new PlatformList());
|
||||
Response::setModel(new Variable());
|
||||
Response::setModel(new Country());
|
||||
Response::setModel(new Continent());
|
||||
|
||||
+2
-13
@@ -245,19 +245,8 @@ $register->set('pools', function () {
|
||||
$maxConnections = (int) System::getEnv('_APP_CONNECTIONS_MAX', 151);
|
||||
$instanceConnections = $maxConnections / (int) System::getEnv('_APP_POOL_CLIENTS', 14);
|
||||
|
||||
$multiprocessing = System::getEnv('_APP_SERVER_MULTIPROCESS', 'disabled') === 'enabled';
|
||||
|
||||
if ($multiprocessing) {
|
||||
$workerCount = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
|
||||
} else {
|
||||
$workerCount = 1;
|
||||
}
|
||||
|
||||
if ($workerCount > $instanceConnections) {
|
||||
throw new \Exception('Pool size is too small. Increase the number of allowed database connections or decrease the number of workers.', 500);
|
||||
}
|
||||
|
||||
$poolSize = (int)($instanceConnections / $workerCount);
|
||||
$workerCount = intval(System::getEnv('_APP_CPU_NUM', swoole_cpu_num())) * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
|
||||
$poolSize = max(1, (int)($instanceConnections / $workerCount));
|
||||
|
||||
foreach ($connections as $key => $connection) {
|
||||
$type = $connection['type'] ?? '';
|
||||
|
||||
+38
-1372
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,435 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Delete;
|
||||
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\Screenshot;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Usage\Context;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Utopia\Audit\Adapter\Database as AdapterDatabase;
|
||||
use Utopia\Audit\Audit as UtopiaAudit;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Console;
|
||||
use Utopia\Database\Adapter\Pool as DatabasePool;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\DI\Container;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Storage\Device\Telemetry as TelemetryDevice;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter as Telemetry;
|
||||
|
||||
/**
|
||||
* Register per-job resources on the given container.
|
||||
* These resources depend on the queue message or keep mutable state and
|
||||
* must be fresh for each worker job.
|
||||
*/
|
||||
return function (Container $container): void {
|
||||
$container->set('log', fn () => new Log(), []);
|
||||
|
||||
$container->set('usage', fn () => new Context(), []);
|
||||
|
||||
$container->set('authorization', function () {
|
||||
$authorization = new Authorization();
|
||||
$authorization->disable();
|
||||
|
||||
return $authorization;
|
||||
}, []);
|
||||
|
||||
$container->set('dbForPlatform', function (Cache $cache, Group $pools, Authorization $authorization) {
|
||||
$adapter = new DatabasePool($pools->get('console'));
|
||||
$dbForPlatform = new Database($adapter, $cache);
|
||||
|
||||
$dbForPlatform
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setNamespace('_console')
|
||||
->setDocumentType('users', User::class);
|
||||
|
||||
return $dbForPlatform;
|
||||
}, ['cache', 'pools', 'authorization']);
|
||||
|
||||
$container->set('project', function ($message, Database $dbForPlatform) {
|
||||
$payload = $message->getPayload() ?? [];
|
||||
$project = new Document($payload['project'] ?? []);
|
||||
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $project;
|
||||
}
|
||||
|
||||
return $dbForPlatform->getDocument('projects', $project->getId());
|
||||
}, ['message', 'dbForPlatform']);
|
||||
|
||||
$container->set('dbForProject', function (Cache $cache, Group $pools, Document $project, Database $dbForPlatform, Authorization $authorization) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForPlatform;
|
||||
}
|
||||
|
||||
try {
|
||||
$dsn = new DSN($project->getAttribute('database'));
|
||||
} catch (\InvalidArgumentException) {
|
||||
// TODO: Temporary until all projects are using shared tables
|
||||
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
|
||||
}
|
||||
|
||||
$adapter = new DatabasePool($pools->get($dsn->getHost()));
|
||||
$database = new Database($adapter, $cache);
|
||||
$database->setDocumentType('users', User::class);
|
||||
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getSequence())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $project->getSequence());
|
||||
}
|
||||
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
|
||||
|
||||
return $database;
|
||||
}, ['cache', 'pools', 'project', 'dbForPlatform', 'authorization']);
|
||||
|
||||
$container->set('getProjectDB', function (Group $pools, Database $dbForPlatform, Cache $cache, Authorization $authorization) {
|
||||
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
|
||||
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases): Database {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForPlatform;
|
||||
}
|
||||
|
||||
try {
|
||||
$dsn = new DSN($project->getAttribute('database'));
|
||||
} catch (\InvalidArgumentException) {
|
||||
// TODO: Temporary until all projects are using shared tables
|
||||
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
|
||||
}
|
||||
|
||||
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)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getSequence())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $project->getSequence());
|
||||
}
|
||||
|
||||
return $database;
|
||||
}
|
||||
|
||||
$adapter = new DatabasePool($pools->get($dsn->getHost()));
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
$databases[$dsn->getHost()] = $database;
|
||||
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getSequence())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $project->getSequence());
|
||||
}
|
||||
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
|
||||
|
||||
$container->set('getDatabasesDB', function (Cache $cache, Registry $register, Document $project, Authorization $authorization) {
|
||||
return function (Document $database, ?Document $projectDocument = null) use ($cache, $register, $project, $authorization): Database {
|
||||
$projectDocument ??= $project;
|
||||
$databaseDSN = $database->getAttribute('database', $project->getAttribute('database', ''));
|
||||
$databaseType = $database->getAttribute('type', '');
|
||||
|
||||
// Backwards-compatibility: older or seeded legacy databases may not have a DSN stored
|
||||
// in the "database" attribute. In that case, fall back to the project's database DSN.
|
||||
if ($databaseDSN === '') {
|
||||
$databaseDSN = $projectDocument->getAttribute('database', '');
|
||||
}
|
||||
|
||||
try {
|
||||
$databaseDSN = new DSN($databaseDSN);
|
||||
} catch (\InvalidArgumentException) {
|
||||
$databaseDSN = new DSN('mysql://' . $databaseDSN);
|
||||
}
|
||||
|
||||
try {
|
||||
$dsn = new DSN($projectDocument->getAttribute('database'));
|
||||
} catch (\InvalidArgumentException) {
|
||||
// Temporary fallback until all projects use shared tables
|
||||
$dsn = new DSN('mysql://' . $projectDocument->getAttribute('database'));
|
||||
}
|
||||
|
||||
$pools = $register->get('pools');
|
||||
$pool = $pools->get($databaseDSN->getHost());
|
||||
|
||||
$adapter = new DatabasePool($pool);
|
||||
$database = new Database($adapter, $cache);
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization);
|
||||
$database->getAdapter()->setSupportForAttributes($databaseType !== DOCUMENTSDB);
|
||||
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if (\in_array($dsn->getHost(), $sharedTables, true)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant((int) $projectDocument->getSequence())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $projectDocument->getSequence());
|
||||
}
|
||||
|
||||
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
|
||||
return $database;
|
||||
};
|
||||
}, ['cache', 'register', 'project', 'authorization']);
|
||||
|
||||
$container->set('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$database = null;
|
||||
|
||||
return function (?Document $project = null) use ($pools, $cache, $authorization, &$database) {
|
||||
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$database->setTenant($project->getSequence());
|
||||
|
||||
return $database;
|
||||
}
|
||||
|
||||
$adapter = new DatabasePool($pools->get('logs'));
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setSharedTables(true)
|
||||
->setNamespace('logsV1')
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER)
|
||||
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES_WORKER);
|
||||
|
||||
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$database->setTenant($project->getSequence());
|
||||
}
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
$container->set('abuseRetention', function () {
|
||||
return \time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400); // 1 day
|
||||
}, []);
|
||||
|
||||
$container->set('auditRetention', function (Document $project) {
|
||||
if ($project->getId() === 'console') {
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE', 15778800)); // 6 months
|
||||
}
|
||||
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600)); // 14 days
|
||||
}, ['project']);
|
||||
|
||||
$container->set('executionRetention', function () {
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600)); // 14 days
|
||||
}, []);
|
||||
|
||||
$container->set('queueForDatabase', function (Publisher $publisher) {
|
||||
return new EventDatabase($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForMessaging', function (Publisher $publisher) {
|
||||
return new Messaging($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForMails', function (Publisher $publisher) {
|
||||
return new Mail($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForBuilds', function (Publisher $publisher) {
|
||||
return new Build($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForScreenshots', function (Publisher $publisher) {
|
||||
return new Screenshot($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForDeletes', function (Publisher $publisher) {
|
||||
return new Delete($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForEvents', function (Publisher $publisher) {
|
||||
return new Event($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForAudits', function (Publisher $publisher) {
|
||||
return new Audit($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForWebhooks', function (Publisher $publisher) {
|
||||
return new Webhook($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForFunctions', function (Publisher $publisher) {
|
||||
return new Func($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForRealtime', function () {
|
||||
return new Realtime();
|
||||
}, []);
|
||||
|
||||
$container->set('queueForCertificates', function (Publisher $publisher) {
|
||||
return new Certificate($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('queueForMigrations', function (Publisher $publisher) {
|
||||
return new Migration($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
$container->set('deviceForSites', function (Document $project, Telemetry $telemetry) {
|
||||
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_SITES . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
|
||||
$container->set('deviceForMigrations', function (Document $project, Telemetry $telemetry) {
|
||||
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
|
||||
$container->set('deviceForFunctions', function (Document $project, Telemetry $telemetry) {
|
||||
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
|
||||
$container->set('deviceForFiles', function (Document $project, Telemetry $telemetry) {
|
||||
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
|
||||
$container->set('deviceForBuilds', function (Document $project, Telemetry $telemetry) {
|
||||
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
|
||||
$container->set('deviceForCache', function (Document $project, Telemetry $telemetry) {
|
||||
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
|
||||
$container->set('logError', function (Registry $register, Document $project) {
|
||||
return function (Throwable $error, string $namespace, string $action, ?array $extras = null) use ($register, $project) {
|
||||
$logger = $register->get('logger');
|
||||
|
||||
if ($logger) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
$log = new Log();
|
||||
$log->setNamespace($namespace);
|
||||
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
|
||||
$log->addTag('code', $error->getCode());
|
||||
$log->addTag('verboseType', \get_class($error));
|
||||
$log->addTag('projectId', $project->getId() ?? '');
|
||||
|
||||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
|
||||
if ($error->getPrevious() !== null) {
|
||||
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
|
||||
$log->addExtra('previousMessage', $error->getPrevious()->getMessage());
|
||||
}
|
||||
$log->addExtra('previousFile', $error->getPrevious()->getFile());
|
||||
$log->addExtra('previousLine', $error->getPrevious()->getLine());
|
||||
}
|
||||
|
||||
foreach (($extras ?? []) as $key => $value) {
|
||||
$log->addExtra($key, $value);
|
||||
}
|
||||
|
||||
$log->setAction($action);
|
||||
|
||||
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
try {
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Error log pushed with status code: ' . $responseCode);
|
||||
} catch (Throwable $th) {
|
||||
Console::error('Error pushing log: ' . $th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Console::warning("Failed: {$error->getMessage()}");
|
||||
Console::warning($error->getTraceAsString());
|
||||
|
||||
if ($error->getPrevious() !== null) {
|
||||
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
|
||||
Console::warning("Previous Failed: {$error->getPrevious()->getMessage()}");
|
||||
}
|
||||
Console::warning("Previous File: {$error->getPrevious()->getFile()} Line: {$error->getPrevious()->getLine()}");
|
||||
}
|
||||
};
|
||||
}, ['register', 'project']);
|
||||
|
||||
$container->set('getAudit', function (Database $dbForPlatform, callable $getProjectDB) {
|
||||
return function (Document $project) use ($dbForPlatform, $getProjectDB) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
$adapter = new AdapterDatabase($dbForPlatform);
|
||||
|
||||
return new UtopiaAudit($adapter);
|
||||
}
|
||||
|
||||
$dbForProject = $getProjectDB($project);
|
||||
$adapter = new AdapterDatabase($dbForProject);
|
||||
|
||||
return new UtopiaAudit($adapter);
|
||||
};
|
||||
}, ['dbForPlatform', 'getProjectDB']);
|
||||
|
||||
$container->set('executionsRetentionCount', function (Document $project, array $plan) {
|
||||
if ($project->getId() === 'console' || empty($plan)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int) ($plan['executionsRetentionCount'] ?? 100);
|
||||
}, ['project', 'plan']);
|
||||
};
|
||||
+20
-5
@@ -33,7 +33,9 @@ use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\DI\Container;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Http\Adapter\FPM\Server as HttpServer;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Pools\Group;
|
||||
@@ -48,6 +50,8 @@ use Utopia\WebSocket\Server;
|
||||
*/
|
||||
require_once __DIR__ . '/init.php';
|
||||
|
||||
$registerRequestResources ??= require __DIR__ . '/init/resources/request.php';
|
||||
|
||||
Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
|
||||
|
||||
// Log uncaught exceptions in one line instead of relying on Swoole's full backtrace dump
|
||||
@@ -240,6 +244,11 @@ if (!function_exists('triggerStats')) {
|
||||
}
|
||||
}
|
||||
|
||||
global $container;
|
||||
$container->set('pools', function ($register) {
|
||||
return $register->get('pools');
|
||||
}, ['register']);
|
||||
|
||||
$realtime = getRealtime();
|
||||
|
||||
/**
|
||||
@@ -614,16 +623,22 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
Console::error('Failed to restart pub/sub...');
|
||||
});
|
||||
|
||||
$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime) {
|
||||
$app = new Http('UTC');
|
||||
$server->onOpen(function (int $connection, SwooleRequest $request) use ($server, $register, $stats, &$realtime, $registerRequestResources) {
|
||||
global $container;
|
||||
$request = new Request($request);
|
||||
$response = new Response(new SwooleResponse());
|
||||
|
||||
Console::info("Connection open (user: {$connection})");
|
||||
|
||||
Http::setResource('pools', fn () => $register->get('pools'));
|
||||
Http::setResource('request', fn () => $request);
|
||||
Http::setResource('response', fn () => $response);
|
||||
$connectionContainer = new Container($container);
|
||||
|
||||
$adapter = new HttpServer($connectionContainer);
|
||||
$app = new Http($adapter, 'UTC');
|
||||
$connectionContainer->set('utopia', fn () => $app);
|
||||
$connectionContainer->set('request', fn () => $request);
|
||||
$connectionContainer->set('response', fn () => $response);
|
||||
|
||||
$registerRequestResources($connectionContainer);
|
||||
|
||||
$project = null;
|
||||
$logUser = null;
|
||||
|
||||
+42
-530
@@ -1,564 +1,69 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/init.php';
|
||||
|
||||
$registerWorkerMessageResources = require __DIR__ . '/init/worker/message.php';
|
||||
use Appwrite\Certificates\LetsEncrypt;
|
||||
use Appwrite\Event\Audit;
|
||||
use Appwrite\Event\Build;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\Migration;
|
||||
use Appwrite\Event\Publisher\Usage as UsagePublisher;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\Screenshot;
|
||||
use Appwrite\Event\Webhook;
|
||||
use Appwrite\Platform\Appwrite;
|
||||
use Appwrite\Usage\Context;
|
||||
use Appwrite\Utopia\Database\Documents\User;
|
||||
use Executor\Executor;
|
||||
use Swoole\Runtime;
|
||||
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
|
||||
use Utopia\Audit\Adapter\Database as AdapterDatabase;
|
||||
use Utopia\Audit\Audit as UtopiaAudit;
|
||||
use Utopia\Cache\Adapter\Pool as CachePool;
|
||||
use Utopia\Cache\Adapter\Sharding;
|
||||
use Utopia\Cache\Cache;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Console;
|
||||
use Utopia\Database\Adapter\Pool as DatabasePool;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Platform\Service;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Adapter\Swoole;
|
||||
use Utopia\Queue\Broker\Pool as BrokerPool;
|
||||
use Utopia\Queue\Message;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\Queue\Queue;
|
||||
use Utopia\Queue\Server;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Storage\Device\Telemetry as TelemetryDevice;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter as Telemetry;
|
||||
use Utopia\Telemetry\Adapter\None as NoTelemetry;
|
||||
|
||||
Runtime::enableCoroutine();
|
||||
require_once __DIR__ . '/init/span.php';
|
||||
|
||||
global $register;
|
||||
Server::setResource('register', fn () => $register);
|
||||
global $container;
|
||||
$container->set('pools', function ($register) {
|
||||
return $register->get('pools');
|
||||
}, ['register']);
|
||||
|
||||
Server::setResource('authorization', function () {
|
||||
$container->set('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);
|
||||
$container->set('project', fn () => new Document([]), []);
|
||||
|
||||
$dbForPlatform
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setNamespace('_console')
|
||||
->setDocumentType('users', User::class);
|
||||
$container->set('log', fn () => new Log(), []);
|
||||
|
||||
return $dbForPlatform;
|
||||
}, ['cache', 'register', 'authorization']);
|
||||
|
||||
Server::setResource('project', function (Message $message, Database $dbForPlatform) {
|
||||
$payload = $message->getPayload() ?? [];
|
||||
$project = new Document($payload['project'] ?? []);
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
return $project;
|
||||
}
|
||||
|
||||
return $dbForPlatform->getDocument('projects', $project->getId());
|
||||
}, ['message', '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;
|
||||
}
|
||||
|
||||
$pools = $register->get('pools');
|
||||
|
||||
try {
|
||||
$dsn = new DSN($project->getAttribute('database'));
|
||||
} catch (\InvalidArgumentException) {
|
||||
// TODO: Temporary until all projects are using shared tables
|
||||
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
|
||||
}
|
||||
|
||||
$adapter = new DatabasePool($pools->get($dsn->getHost()));
|
||||
$database = new Database($adapter, $cache);
|
||||
$database->setDocumentType('users', User::class);
|
||||
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getSequence())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $project->getSequence());
|
||||
}
|
||||
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
|
||||
|
||||
return $database;
|
||||
}, ['cache', 'register', 'message', 'project', 'dbForPlatform', 'authorization']);
|
||||
|
||||
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, $authorization, &$databases): Database {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForPlatform;
|
||||
}
|
||||
|
||||
try {
|
||||
$dsn = new DSN($project->getAttribute('database'));
|
||||
} catch (\InvalidArgumentException) {
|
||||
// TODO: Temporary until all projects are using shared tables
|
||||
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
|
||||
}
|
||||
|
||||
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)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getSequence())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $project->getSequence());
|
||||
}
|
||||
|
||||
return $database;
|
||||
}
|
||||
|
||||
$adapter = new DatabasePool($pools->get($dsn->getHost()));
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
$databases[$dsn->getHost()] = $database;
|
||||
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant($project->getSequence())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $project->getSequence());
|
||||
}
|
||||
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
|
||||
|
||||
Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$database = null;
|
||||
|
||||
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
|
||||
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$database->setTenant($project->getSequence());
|
||||
return $database;
|
||||
}
|
||||
|
||||
$adapter = new DatabasePool($pools->get('logs'));
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization)
|
||||
->setSharedTables(true)
|
||||
->setNamespace('logsV1')
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER)
|
||||
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES_WORKER);
|
||||
|
||||
if ($project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$database->setTenant($project->getSequence());
|
||||
}
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
Server::setResource('getDatabasesDB', function (Cache $cache, Registry $register, Document $project, Authorization $authorization) {
|
||||
return function (Document $database, ?Document $projectDocument = null) use ($cache, $register, $project, $authorization): Database {
|
||||
$projectDocument ??= $project;
|
||||
$databaseDSN = $database->getAttribute('database', $project->getAttribute('database', ''));
|
||||
$databaseType = $database->getAttribute('type', '');
|
||||
|
||||
// Backwards‑compatibility: older or seeded legacy databases may not have a DSN stored
|
||||
// in the "database" attribute. In that case, fall back to the project's database DSN.
|
||||
if ($databaseDSN === '') {
|
||||
$databaseDSN = $projectDocument->getAttribute('database', '');
|
||||
}
|
||||
|
||||
try {
|
||||
$databaseDSN = new DSN($databaseDSN);
|
||||
} catch (\InvalidArgumentException) {
|
||||
$databaseDSN = new DSN('mysql://'.$databaseDSN);
|
||||
}
|
||||
|
||||
try {
|
||||
$dsn = new DSN($projectDocument->getAttribute('database'));
|
||||
} catch (\InvalidArgumentException) {
|
||||
// Temporary fallback until all projects use shared tables
|
||||
$dsn = new DSN('mysql://' . $projectDocument->getAttribute('database'));
|
||||
}
|
||||
|
||||
$pools = $register->get('pools');
|
||||
$pool = $pools->get($databaseDSN->getHost());
|
||||
|
||||
$adapter = new DatabasePool($pool);
|
||||
$database = new Database($adapter, $cache);
|
||||
$database
|
||||
->setDatabase(APP_DATABASE)
|
||||
->setAuthorization($authorization);
|
||||
$database->getAdapter()->setSupportForAttributes($databaseType !== DOCUMENTSDB);
|
||||
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if (\in_array($dsn->getHost(), $sharedTables, true)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant((int) $projectDocument->getSequence())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $projectDocument->getSequence());
|
||||
}
|
||||
|
||||
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
|
||||
return $database;
|
||||
};
|
||||
}, ['cache', 'register', 'project', 'authorization']);
|
||||
|
||||
Server::setResource('abuseRetention', function () {
|
||||
return time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400); // 1 day
|
||||
});
|
||||
|
||||
Server::setResource('auditRetention', function (Document $project) {
|
||||
if ($project->getId() === 'console') {
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE', 15778800)); // 6 months
|
||||
}
|
||||
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600)); // 14 days
|
||||
}, ['project']);
|
||||
|
||||
Server::setResource('executionRetention', function () {
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600)); // 14 days
|
||||
});
|
||||
|
||||
Server::setResource('cache', function (Registry $register) {
|
||||
$pools = $register->get('pools');
|
||||
$list = Config::getParam('pools-cache', []);
|
||||
$adapters = [];
|
||||
|
||||
foreach ($list as $value) {
|
||||
$adapters[] = new CachePool($pools->get($value));
|
||||
}
|
||||
|
||||
return new Cache(new Sharding($adapters));
|
||||
}, ['register']);
|
||||
|
||||
Server::setResource('redis', function () {
|
||||
$host = System::getEnv('_APP_REDIS_HOST', 'localhost');
|
||||
$port = System::getEnv('_APP_REDIS_PORT', 6379);
|
||||
$pass = System::getEnv('_APP_REDIS_PASS', '');
|
||||
|
||||
$redis = new \Redis();
|
||||
@$redis->pconnect($host, (int) $port);
|
||||
if ($pass) {
|
||||
$redis->auth($pass);
|
||||
}
|
||||
$redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
|
||||
|
||||
return $redis;
|
||||
});
|
||||
|
||||
Server::setResource('timelimit', function (\Redis $redis) {
|
||||
return function (string $key, int $limit, int $time) use ($redis) {
|
||||
return new TimeLimitRedis($key, $limit, $time, $redis);
|
||||
};
|
||||
}, ['redis']);
|
||||
|
||||
Server::setResource('log', fn () => new Log());
|
||||
|
||||
Server::setResource('publisher', function (Group $pools) {
|
||||
return new BrokerPool(publisher: $pools->get('publisher'));
|
||||
}, ['pools']);
|
||||
|
||||
Server::setResource('publisherDatabases', function (BrokerPool $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
|
||||
Server::setResource('publisherFunctions', function (BrokerPool $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
|
||||
Server::setResource('publisherMigrations', function (BrokerPool $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
|
||||
Server::setResource('publisherMessaging', function (BrokerPool $publisher) {
|
||||
return $publisher;
|
||||
}, ['publisher']);
|
||||
|
||||
Server::setResource('consumer', function (Group $pools) {
|
||||
$container->set('consumer', function (Group $pools) {
|
||||
return new BrokerPool(consumer: $pools->get('consumer'));
|
||||
}, ['pools']);
|
||||
|
||||
Server::setResource('consumerDatabases', function (BrokerPool $consumer) {
|
||||
$container->set('consumerDatabases', function (BrokerPool $consumer) {
|
||||
return $consumer;
|
||||
}, ['consumer']);
|
||||
|
||||
Server::setResource('consumerMigrations', function (BrokerPool $consumer) {
|
||||
$container->set('consumerMigrations', function (BrokerPool $consumer) {
|
||||
return $consumer;
|
||||
}, ['consumer']);
|
||||
|
||||
Server::setResource('consumerStatsUsage', function (BrokerPool $consumer) {
|
||||
$container->set('consumerStatsUsage', function (BrokerPool $consumer) {
|
||||
return $consumer;
|
||||
}, ['consumer']);
|
||||
|
||||
Server::setResource('usage', function () {
|
||||
return new Context();
|
||||
}, []);
|
||||
Server::setResource('publisherForUsage', fn (Publisher $publisher) => new UsagePublisher(
|
||||
$publisher,
|
||||
new Queue(System::getEnv('_APP_STATS_USAGE_QUEUE_NAME', Event::STATS_USAGE_QUEUE_NAME))
|
||||
), ['publisher']);
|
||||
|
||||
Server::setResource('queueForDatabase', function (Publisher $publisher) {
|
||||
return new EventDatabase($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
Server::setResource('queueForMessaging', function (Publisher $publisher) {
|
||||
return new Messaging($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
Server::setResource('queueForMails', function (Publisher $publisher) {
|
||||
return new Mail($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
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']);
|
||||
|
||||
Server::setResource('queueForEvents', function (Publisher $publisher) {
|
||||
return new Event($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
Server::setResource('queueForAudits', function (Publisher $publisher) {
|
||||
return new Audit($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
Server::setResource('queueForWebhooks', function (Publisher $publisher) {
|
||||
return new Webhook($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
Server::setResource('queueForFunctions', function (Publisher $publisher) {
|
||||
return new Func($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
Server::setResource('queueForRealtime', function () {
|
||||
return new Realtime();
|
||||
}, []);
|
||||
|
||||
Server::setResource('queueForCertificates', function (Publisher $publisher) {
|
||||
return new Certificate($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
Server::setResource('queueForMigrations', function (Publisher $publisher) {
|
||||
return new Migration($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
Server::setResource('logger', function (Registry $register) {
|
||||
return $register->get('logger');
|
||||
}, ['register']);
|
||||
|
||||
Server::setResource('pools', function (Registry $register) {
|
||||
return $register->get('pools');
|
||||
}, ['register']);
|
||||
|
||||
Server::setResource('telemetry', fn () => new NoTelemetry());
|
||||
|
||||
Server::setResource('deviceForSites', function (Document $project, Telemetry $telemetry) {
|
||||
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_SITES . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
|
||||
Server::setResource('deviceForMigrations', function (Document $project, Telemetry $telemetry) {
|
||||
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_IMPORTS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
|
||||
Server::setResource('deviceForFunctions', function (Document $project, Telemetry $telemetry) {
|
||||
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
|
||||
Server::setResource('deviceForFiles', function (Document $project, Telemetry $telemetry) {
|
||||
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
|
||||
Server::setResource('deviceForBuilds', function (Document $project, Telemetry $telemetry) {
|
||||
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_BUILDS . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
|
||||
Server::setResource('deviceForCache', function (Document $project, Telemetry $telemetry) {
|
||||
return new TelemetryDevice($telemetry, getDevice(APP_STORAGE_CACHE . '/app-' . $project->getId()));
|
||||
}, ['project', 'telemetry']);
|
||||
|
||||
Server::setResource(
|
||||
'isResourceBlocked',
|
||||
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
|
||||
);
|
||||
|
||||
Server::setResource('plan', function (array $plan = []) {
|
||||
return [];
|
||||
});
|
||||
|
||||
Server::setResource('certificates', function () {
|
||||
$container->set('certificates', function () {
|
||||
$email = System::getEnv('_APP_EMAIL_CERTIFICATES', System::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS'));
|
||||
if (empty($email)) {
|
||||
throw new Exception('You must set a valid security email address (_APP_EMAIL_CERTIFICATES) to issue a LetsEncrypt SSL certificate.');
|
||||
}
|
||||
|
||||
return new LetsEncrypt($email);
|
||||
});
|
||||
}, []);
|
||||
|
||||
Server::setResource('logError', function (Registry $register, Document $project) {
|
||||
return function (Throwable $error, string $namespace, string $action, ?array $extras = null) use ($register, $project) {
|
||||
$logger = $register->get('logger');
|
||||
|
||||
if ($logger) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
$log = new Log();
|
||||
$log->setNamespace($namespace);
|
||||
$log->setServer(System::getEnv('_APP_LOGGING_SERVICE_IDENTIFIER', \gethostname()));
|
||||
$log->setVersion($version);
|
||||
$log->setType(Log::TYPE_ERROR);
|
||||
$log->setMessage($error->getMessage());
|
||||
|
||||
$log->addTag('code', $error->getCode());
|
||||
$log->addTag('verboseType', get_class($error));
|
||||
$log->addTag('projectId', $project->getId() ?? '');
|
||||
|
||||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
|
||||
if ($error->getPrevious() !== null) {
|
||||
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
|
||||
$log->addExtra('previousMessage', $error->getPrevious()->getMessage());
|
||||
}
|
||||
$log->addExtra('previousFile', $error->getPrevious()->getFile());
|
||||
$log->addExtra('previousLine', $error->getPrevious()->getLine());
|
||||
}
|
||||
|
||||
foreach (($extras ?? []) as $key => $value) {
|
||||
$log->addExtra($key, $value);
|
||||
}
|
||||
|
||||
$log->setAction($action);
|
||||
|
||||
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
try {
|
||||
$responseCode = $logger->addLog($log);
|
||||
Console::info('Error log pushed with status code: ' . $responseCode);
|
||||
} catch (Throwable $th) {
|
||||
Console::error('Error pushing log: ' . $th->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
Console::warning("Failed: {$error->getMessage()}");
|
||||
Console::warning($error->getTraceAsString());
|
||||
|
||||
if ($error->getPrevious() !== null) {
|
||||
if ($error->getPrevious()->getMessage() != $error->getMessage()) {
|
||||
Console::warning("Previous Failed: {$error->getPrevious()->getMessage()}");
|
||||
}
|
||||
Console::warning("Previous File: {$error->getPrevious()->getFile()} Line: {$error->getPrevious()->getLine()}");
|
||||
}
|
||||
};
|
||||
}, ['register', 'project']);
|
||||
|
||||
Server::setResource('executor', fn () => new Executor());
|
||||
|
||||
Server::setResource('getAudit', function (Database $dbForPlatform, callable $getProjectDB) {
|
||||
return function (Document $project) use ($dbForPlatform, $getProjectDB) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
$adapter = new AdapterDatabase($dbForPlatform);
|
||||
|
||||
return new UtopiaAudit($adapter);
|
||||
}
|
||||
|
||||
$dbForProject = $getProjectDB($project);
|
||||
$adapter = new AdapterDatabase($dbForProject);
|
||||
|
||||
return new UtopiaAudit($adapter);
|
||||
};
|
||||
}, ['dbForPlatform', 'getProjectDB']);
|
||||
|
||||
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');
|
||||
$args = $_SERVER['argv'] ?? [];
|
||||
|
||||
if (! isset($args[1])) {
|
||||
Console::error('Missing worker name');
|
||||
@@ -574,38 +79,45 @@ if (\str_starts_with($workerName, 'databases')) {
|
||||
$queueName = System::getEnv('_APP_QUEUE_NAME', 'v1-' . strtolower($workerName));
|
||||
}
|
||||
|
||||
/** @var \Utopia\Pools\Group $pools */
|
||||
$pools = $container->get('pools');
|
||||
|
||||
$adapter = new Swoole(
|
||||
$pools->get('consumer')->pop()->getResource(),
|
||||
System::getEnv('_APP_WORKERS_NUM', 1),
|
||||
$queueName
|
||||
);
|
||||
|
||||
$worker = new Server($adapter, $container);
|
||||
|
||||
try {
|
||||
/**
|
||||
* Any worker can be configured with the following env vars:
|
||||
* - _APP_WORKERS_NUM The total number of worker processes
|
||||
* - _APP_WORKER_PER_CORE The number of worker processes per core (ignored if _APP_WORKERS_NUM is set)
|
||||
* - _APP_QUEUE_NAME The name of the queue to read for database events
|
||||
*/
|
||||
$worker->init()->action(function () use ($worker, $registerWorkerMessageResources) {
|
||||
$registerWorkerMessageResources($worker->getContainer());
|
||||
});
|
||||
|
||||
$container->set('bus', function ($register) use ($worker) {
|
||||
return $register->get('bus')->setResolver(
|
||||
fn (string $name) => $worker->getContainer()->get($name)
|
||||
);
|
||||
}, ['register']);
|
||||
|
||||
$platform->setWorker($worker);
|
||||
$platform->init(Service::TYPE_WORKER, [
|
||||
'workersNum' => System::getEnv('_APP_WORKERS_NUM', 1),
|
||||
'connection' => $pools->get('consumer')->pop()->getResource(),
|
||||
'workerName' => strtolower($workerName) ?? null,
|
||||
'queueName' => $queueName,
|
||||
'workerName' => strtolower($workerName),
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Console::error($e->getMessage() . ', File: ' . $e->getFile() . ', Line: ' . $e->getLine());
|
||||
Console::exit(1);
|
||||
}
|
||||
|
||||
$worker = $platform->getWorker();
|
||||
|
||||
Server::setResource('bus', function ($register) use ($worker) {
|
||||
return $register->get('bus')->setResolver(fn (string $name) => $worker->getResource($name));
|
||||
}, ['register']);
|
||||
|
||||
$worker
|
||||
->error()
|
||||
->inject('error')
|
||||
->inject('logger')
|
||||
->inject('log')
|
||||
->inject('pools')
|
||||
->inject('project')
|
||||
->inject('authorization')
|
||||
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project, Authorization $authorization) use ($queueName) {
|
||||
->action(function (Throwable $error, ?Logger $logger, Log $log, Document $project, Authorization $authorization) use ($queueName) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
if ($logger) {
|
||||
|
||||
+7
-8
@@ -52,34 +52,34 @@
|
||||
"appwrite/php-runtimes": "0.19.*",
|
||||
"appwrite/php-clamav": "2.0.*",
|
||||
"utopia-php/abuse": "1.2.*",
|
||||
"utopia-php/agents": "1.2.*",
|
||||
"utopia-php/analytics": "0.15.*",
|
||||
"utopia-php/audit": "2.2.*",
|
||||
"utopia-php/auth": "0.5.*",
|
||||
"utopia-php/cache": "1.0.*",
|
||||
"utopia-php/cli": "0.22.*",
|
||||
"utopia-php/cli": "0.23.*",
|
||||
"utopia-php/compression": "0.1.*",
|
||||
"utopia-php/config": "1.*",
|
||||
"utopia-php/console": "0.1.*",
|
||||
"utopia-php/database": "5.*",
|
||||
"utopia-php/agents": "1.*",
|
||||
"utopia-php/detector": "0.2.*",
|
||||
"utopia-php/domains": "1.*",
|
||||
"utopia-php/emails": "0.6.*",
|
||||
"utopia-php/dns": "1.6.*",
|
||||
"utopia-php/dsn": "0.2.1",
|
||||
"utopia-php/framework": "0.33.*",
|
||||
"utopia-php/framework": "0.34.*",
|
||||
"utopia-php/fetch": "0.5.*",
|
||||
"utopia-php/image": "0.8.*",
|
||||
"utopia-php/locale": "0.8.*",
|
||||
"utopia-php/logger": "0.6.*",
|
||||
"utopia-php/messaging": "0.22.*",
|
||||
"utopia-php/migration": "1.9.*",
|
||||
"utopia-php/platform": "0.7.*",
|
||||
"utopia-php/platform": "0.12.*",
|
||||
"utopia-php/pools": "1.*",
|
||||
"utopia-php/span": "1.1.*",
|
||||
"utopia-php/preloader": "0.2.*",
|
||||
"utopia-php/queue": "0.15.*",
|
||||
"utopia-php/servers": "0.2.5",
|
||||
"utopia-php/queue": "0.17.*",
|
||||
"utopia-php/servers": "0.3.*",
|
||||
"utopia-php/registry": "0.5.*",
|
||||
"utopia-php/storage": "1.0.*",
|
||||
"utopia-php/system": "0.10.*",
|
||||
@@ -94,8 +94,7 @@
|
||||
"spomky-labs/otphp": "11.*",
|
||||
"webonyx/graphql-php": "14.11.*",
|
||||
"league/csv": "9.14.*",
|
||||
"enshrined/svg-sanitize": "0.22.*",
|
||||
"utopia-php/di": "0.1.0"
|
||||
"enshrined/svg-sanitize": "0.22.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-fileinfo": "*",
|
||||
|
||||
Generated
+130
-67
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "4fe91e67f343fbe6deac1fdc7eda949f",
|
||||
"content-hash": "e9c38bbebc60849e70e3640aaa4422cd",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -3403,16 +3403,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/agents",
|
||||
"version": "1.3.0",
|
||||
"version": "1.2.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/agents.git",
|
||||
"reference": "06064fd9fb19b77ae45a12ec7bcbc17670912c30"
|
||||
"reference": "052227953678a30ecc4b5467401fcb0b2386471e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/agents/zipball/06064fd9fb19b77ae45a12ec7bcbc17670912c30",
|
||||
"reference": "06064fd9fb19b77ae45a12ec7bcbc17670912c30",
|
||||
"url": "https://api.github.com/repos/utopia-php/agents/zipball/052227953678a30ecc4b5467401fcb0b2386471e",
|
||||
"reference": "052227953678a30ecc4b5467401fcb0b2386471e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3450,9 +3450,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/agents/issues",
|
||||
"source": "https://github.com/utopia-php/agents/tree/1.3.0"
|
||||
"source": "https://github.com/utopia-php/agents/tree/1.2.1"
|
||||
},
|
||||
"time": "2026-03-26T03:51:11+00:00"
|
||||
"time": "2026-02-24T06:03:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/analytics",
|
||||
@@ -3658,21 +3658,21 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/cli",
|
||||
"version": "0.22.0",
|
||||
"version": "0.23.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/cli.git",
|
||||
"reference": "a7ac387ee626fd27075a87e836fb72c5be38add4"
|
||||
"reference": "8d1955b8bc4dc631f45d7c7df689ed7b63f70621"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/cli/zipball/a7ac387ee626fd27075a87e836fb72c5be38add4",
|
||||
"reference": "a7ac387ee626fd27075a87e836fb72c5be38add4",
|
||||
"url": "https://api.github.com/repos/utopia-php/cli/zipball/8d1955b8bc4dc631f45d7c7df689ed7b63f70621",
|
||||
"reference": "8d1955b8bc4dc631f45d7c7df689ed7b63f70621",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.4",
|
||||
"utopia-php/servers": "0.2.*"
|
||||
"utopia-php/servers": "0.3.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.2.*",
|
||||
@@ -3703,9 +3703,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/cli/issues",
|
||||
"source": "https://github.com/utopia-php/cli/tree/0.22.0"
|
||||
"source": "https://github.com/utopia-php/cli/tree/0.23.1"
|
||||
},
|
||||
"time": "2025-10-21T10:42:45+00:00"
|
||||
"time": "2026-04-05T15:27:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/compression",
|
||||
@@ -3954,25 +3954,26 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/di",
|
||||
"version": "0.1.0",
|
||||
"version": "0.3.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/di.git",
|
||||
"reference": "22490c95f7ac3898ed1c33f1b1b5dd577305ee31"
|
||||
"reference": "07025d721ed5d9be27932e8e640acf1467fc4b9d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/di/zipball/22490c95f7ac3898ed1c33f1b1b5dd577305ee31",
|
||||
"reference": "22490c95f7ac3898ed1c33f1b1b5dd577305ee31",
|
||||
"url": "https://api.github.com/repos/utopia-php/di/zipball/07025d721ed5d9be27932e8e640acf1467fc4b9d",
|
||||
"reference": "07025d721ed5d9be27932e8e640acf1467fc4b9d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.2"
|
||||
"php": ">=8.2",
|
||||
"psr/container": "^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "^1.2",
|
||||
"laravel/pint": "^1.27",
|
||||
"phpbench/phpbench": "^1.2",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"phpunit/phpunit": "^9.5.25",
|
||||
"swoole/ide-helper": "4.8.3"
|
||||
},
|
||||
@@ -3989,16 +3990,18 @@
|
||||
],
|
||||
"description": "A simple and lite library for managing dependency injections",
|
||||
"keywords": [
|
||||
"framework",
|
||||
"http",
|
||||
"PSR-11",
|
||||
"container",
|
||||
"dependency-injection",
|
||||
"di",
|
||||
"php",
|
||||
"upf"
|
||||
"utopia"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/di/issues",
|
||||
"source": "https://github.com/utopia-php/di/tree/0.1.0"
|
||||
"source": "https://github.com/utopia-php/di/tree/0.3.2"
|
||||
},
|
||||
"time": "2024-08-08T14:35:19+00:00"
|
||||
"time": "2026-03-21T07:42:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/dns",
|
||||
@@ -4268,30 +4271,34 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/framework",
|
||||
"version": "0.33.41",
|
||||
"version": "0.34.18",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/http.git",
|
||||
"reference": "0f3bf2377c867e547c929c3733b8224afee6ef06"
|
||||
"reference": "c8e7e8fc9b9b68aa874e365c83010fefe8ae8ccc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/http/zipball/0f3bf2377c867e547c929c3733b8224afee6ef06",
|
||||
"reference": "0f3bf2377c867e547c929c3733b8224afee6ef06",
|
||||
"url": "https://api.github.com/repos/utopia-php/http/zipball/c8e7e8fc9b9b68aa874e365c83010fefe8ae8ccc",
|
||||
"reference": "c8e7e8fc9b9b68aa874e365c83010fefe8ae8ccc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.3",
|
||||
"ext-swoole": "*",
|
||||
"php": ">=8.2",
|
||||
"utopia-php/compression": "0.1.*",
|
||||
"utopia-php/di": "0.3.*",
|
||||
"utopia-php/servers": "0.3.*",
|
||||
"utopia-php/telemetry": "0.2.*",
|
||||
"utopia-php/validators": "0.2.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/instantiator": "^1.5",
|
||||
"laravel/pint": "1.*",
|
||||
"phpbench/phpbench": "1.*",
|
||||
"phpbench/phpbench": "^1.2",
|
||||
"phpstan/phpstan": "1.*",
|
||||
"phpunit/phpunit": "9.*",
|
||||
"swoole/ide-helper": "^6.0"
|
||||
"phpunit/phpunit": "^9.5.25",
|
||||
"swoole/ide-helper": "4.8.3"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -4303,17 +4310,72 @@
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "A simple, light and advanced PHP framework",
|
||||
"description": "A simple, light and advanced PHP HTTP framework",
|
||||
"keywords": [
|
||||
"framework",
|
||||
"http",
|
||||
"php",
|
||||
"upf"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/http/issues",
|
||||
"source": "https://github.com/utopia-php/http/tree/0.33.41"
|
||||
"source": "https://github.com/utopia-php/http/tree/0.34.18"
|
||||
},
|
||||
"time": "2026-02-24T12:01:28+00:00"
|
||||
"time": "2026-04-07T08:06:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/http",
|
||||
"version": "0.34.16",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/http.git",
|
||||
"reference": "2b4021ba3f9d476264ce9fd6703d6c79de9add7f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/http/zipball/2b4021ba3f9d476264ce9fd6703d6c79de9add7f",
|
||||
"reference": "2b4021ba3f9d476264ce9fd6703d6c79de9add7f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-swoole": "*",
|
||||
"php": ">=8.2",
|
||||
"utopia-php/compression": "0.1.*",
|
||||
"utopia-php/di": "0.3.*",
|
||||
"utopia-php/servers": "0.3.*",
|
||||
"utopia-php/telemetry": "0.2.*",
|
||||
"utopia-php/validators": "0.2.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/instantiator": "^1.5",
|
||||
"laravel/pint": "1.*",
|
||||
"phpbench/phpbench": "^1.2",
|
||||
"phpstan/phpstan": "1.*",
|
||||
"phpunit/phpunit": "^9.5.25",
|
||||
"swoole/ide-helper": "4.8.3"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Utopia\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "A simple, light and advanced PHP HTTP framework",
|
||||
"keywords": [
|
||||
"framework",
|
||||
"http",
|
||||
"php",
|
||||
"upf"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/http/issues",
|
||||
"source": "https://github.com/utopia-php/http/tree/0.34.16"
|
||||
},
|
||||
"time": "2026-03-20T10:39:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/image",
|
||||
@@ -4634,30 +4696,30 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/platform",
|
||||
"version": "0.7.16",
|
||||
"version": "0.12.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/platform.git",
|
||||
"reference": "34e67e4b80b5741c380071fe765fbc12a132de4f"
|
||||
"reference": "068ee46228f0c3972e6b569f2c86b6c80fe583d8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/platform/zipball/34e67e4b80b5741c380071fe765fbc12a132de4f",
|
||||
"reference": "34e67e4b80b5741c380071fe765fbc12a132de4f",
|
||||
"url": "https://api.github.com/repos/utopia-php/platform/zipball/068ee46228f0c3972e6b569f2c86b6c80fe583d8",
|
||||
"reference": "068ee46228f0c3972e6b569f2c86b6c80fe583d8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"ext-redis": "*",
|
||||
"php": ">=8.0",
|
||||
"utopia-php/cli": "0.22.*",
|
||||
"utopia-php/framework": "0.33.*",
|
||||
"utopia-php/queue": "0.15.*"
|
||||
"php": ">=8.1",
|
||||
"utopia-php/cli": "0.23.*",
|
||||
"utopia-php/http": "0.34.*",
|
||||
"utopia-php/queue": "0.17.*",
|
||||
"utopia-php/servers": "0.3.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.*",
|
||||
"phpstan/phpstan": "2.*",
|
||||
"phpunit/phpunit": "9.*"
|
||||
"laravel/pint": "1.2.*",
|
||||
"phpunit/phpunit": "^9.3"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -4679,9 +4741,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/platform/issues",
|
||||
"source": "https://github.com/utopia-php/platform/tree/0.7.16"
|
||||
"source": "https://github.com/utopia-php/platform/tree/0.12.0"
|
||||
},
|
||||
"time": "2026-02-11T06:36:48+00:00"
|
||||
"time": "2026-03-31T14:44:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/pools",
|
||||
@@ -4791,32 +4853,33 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/queue",
|
||||
"version": "0.15.6",
|
||||
"version": "0.17.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/queue.git",
|
||||
"reference": "08e361d69610f371382b344c369eef355ca414b4"
|
||||
"reference": "0fbc7d7312f5cf76ec112513fb93317000901f5f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/queue/zipball/08e361d69610f371382b344c369eef355ca414b4",
|
||||
"reference": "08e361d69610f371382b344c369eef355ca414b4",
|
||||
"url": "https://api.github.com/repos/utopia-php/queue/zipball/0fbc7d7312f5cf76ec112513fb93317000901f5f",
|
||||
"reference": "0fbc7d7312f5cf76ec112513fb93317000901f5f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.3",
|
||||
"php-amqplib/php-amqplib": "^3.7",
|
||||
"utopia-php/di": "0.3.*",
|
||||
"utopia-php/fetch": "0.5.*",
|
||||
"utopia-php/pools": "1.*",
|
||||
"utopia-php/servers": "0.2.*",
|
||||
"utopia-php/servers": "0.3.*",
|
||||
"utopia-php/telemetry": "0.2.*",
|
||||
"utopia-php/validators": "0.2.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-redis": "*",
|
||||
"laravel/pint": "^0.2.3",
|
||||
"laravel/pint": "^1.0",
|
||||
"phpstan/phpstan": "^1.8",
|
||||
"phpunit/phpunit": "^9.5.5",
|
||||
"phpunit/phpunit": "^11.0",
|
||||
"swoole/ide-helper": "4.8.8",
|
||||
"workerman/workerman": "^4.0"
|
||||
},
|
||||
@@ -4851,9 +4914,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/queue/issues",
|
||||
"source": "https://github.com/utopia-php/queue/tree/0.15.6"
|
||||
"source": "https://github.com/utopia-php/queue/tree/0.17.0"
|
||||
},
|
||||
"time": "2026-02-23T13:03:51+00:00"
|
||||
"time": "2026-03-23T16:21:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/registry",
|
||||
@@ -4909,21 +4972,21 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/servers",
|
||||
"version": "0.2.5",
|
||||
"version": "0.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/servers.git",
|
||||
"reference": "4770e879a90685af4ba14e7e5d95d0a17c7fdf03"
|
||||
"reference": "235be31200df9437fc96a1c270ffef4c64fafe52"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/servers/zipball/4770e879a90685af4ba14e7e5d95d0a17c7fdf03",
|
||||
"reference": "4770e879a90685af4ba14e7e5d95d0a17c7fdf03",
|
||||
"url": "https://api.github.com/repos/utopia-php/servers/zipball/235be31200df9437fc96a1c270ffef4c64fafe52",
|
||||
"reference": "235be31200df9437fc96a1c270ffef4c64fafe52",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
"utopia-php/di": "0.1.*",
|
||||
"php": ">=8.2",
|
||||
"utopia-php/di": "0.3.*",
|
||||
"utopia-php/validators": "0.*"
|
||||
},
|
||||
"require-dev": {
|
||||
@@ -4957,9 +5020,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/servers/issues",
|
||||
"source": "https://github.com/utopia-php/servers/tree/0.2.5"
|
||||
"source": "https://github.com/utopia-php/servers/tree/0.3.0"
|
||||
},
|
||||
"time": "2026-02-10T04:21:53+00:00"
|
||||
"time": "2026-03-13T11:31:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/span",
|
||||
|
||||
@@ -333,6 +333,8 @@ class Exception extends \Exception
|
||||
|
||||
/** Platform */
|
||||
public const string PLATFORM_NOT_FOUND = 'platform_not_found';
|
||||
public const string PLATFORM_METHOD_UNSUPPORTED = 'platform_method_unsupported';
|
||||
public const string PLATFORM_ALREADY_EXISTS = 'platform_already_exists';
|
||||
|
||||
/** GraphqQL */
|
||||
public const string GRAPHQL_NO_QUERY = 'graphql_no_query';
|
||||
|
||||
@@ -26,9 +26,9 @@ class Resolvers
|
||||
): callable {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $route, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
$utopia = $utopia->getResource('utopia:graphql');
|
||||
$request = $utopia->getResource('request');
|
||||
$response = $utopia->getResource('response');
|
||||
|
||||
$path = $route->getPath();
|
||||
foreach ($args as $key => $value) {
|
||||
@@ -93,9 +93,9 @@ class Resolvers
|
||||
): callable {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
$utopia = $utopia->getResource('utopia:graphql');
|
||||
$request = $utopia->getResource('request');
|
||||
$response = $utopia->getResource('response');
|
||||
|
||||
$request->setMethod('GET');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
@@ -124,9 +124,9 @@ class Resolvers
|
||||
): callable {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
$utopia = $utopia->getResource('utopia:graphql');
|
||||
$request = $utopia->getResource('request');
|
||||
$response = $utopia->getResource('response');
|
||||
|
||||
$request->setMethod('GET');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
@@ -160,9 +160,9 @@ class Resolvers
|
||||
): callable {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
$utopia = $utopia->getResource('utopia:graphql');
|
||||
$request = $utopia->getResource('request');
|
||||
$response = $utopia->getResource('response');
|
||||
|
||||
$request->setMethod('POST');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
@@ -192,9 +192,9 @@ class Resolvers
|
||||
): callable {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
$utopia = $utopia->getResource('utopia:graphql');
|
||||
$request = $utopia->getResource('request');
|
||||
$response = $utopia->getResource('response');
|
||||
|
||||
$request->setMethod('PATCH');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
@@ -222,9 +222,9 @@ class Resolvers
|
||||
): callable {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
$utopia = $utopia->getResource('utopia:graphql');
|
||||
$request = $utopia->getResource('request');
|
||||
$response = $utopia->getResource('response');
|
||||
|
||||
$request->setMethod('DELETE');
|
||||
$request->setURI($url($databaseId, $collectionId, $args));
|
||||
@@ -266,7 +266,7 @@ class Resolvers
|
||||
try {
|
||||
$route = $utopia->match($request, fresh: true);
|
||||
|
||||
$utopia->execute($route, $request, $response);
|
||||
$utopia->execute($route, $request);
|
||||
} catch (\Throwable $e) {
|
||||
if ($beforeReject) {
|
||||
$e = $beforeReject($e);
|
||||
|
||||
@@ -32,10 +32,6 @@ class Schema
|
||||
array $urls,
|
||||
array $params,
|
||||
): GQLSchema {
|
||||
Http::setResource('utopia:graphql', static function () use ($utopia) {
|
||||
return $utopia;
|
||||
});
|
||||
|
||||
if (!empty(self::$schema)) {
|
||||
return self::$schema;
|
||||
}
|
||||
|
||||
@@ -6,20 +6,10 @@ class Platform
|
||||
{
|
||||
public const TYPE_UNKNOWN = 'unknown';
|
||||
public const TYPE_WEB = 'web';
|
||||
public const TYPE_FLUTTER_IOS = 'flutter-ios';
|
||||
public const TYPE_FLUTTER_ANDROID = 'flutter-android';
|
||||
public const TYPE_FLUTTER_MACOS = 'flutter-macos';
|
||||
public const TYPE_FLUTTER_WINDOWS = 'flutter-windows';
|
||||
public const TYPE_FLUTTER_LINUX = 'flutter-linux';
|
||||
public const TYPE_FLUTTER_WEB = 'flutter-web';
|
||||
public const TYPE_APPLE_IOS = 'apple-ios';
|
||||
public const TYPE_APPLE_MACOS = 'apple-macos';
|
||||
public const TYPE_APPLE_WATCHOS = 'apple-watchos';
|
||||
public const TYPE_APPLE_TVOS = 'apple-tvos';
|
||||
public const TYPE_APPLE = 'apple';
|
||||
public const TYPE_ANDROID = 'android';
|
||||
public const TYPE_UNITY = 'unity';
|
||||
public const TYPE_REACT_NATIVE_IOS = 'react-native-ios';
|
||||
public const TYPE_REACT_NATIVE_ANDROID = 'react-native-android';
|
||||
public const TYPE_WINDOWS = 'windows';
|
||||
public const TYPE_LINUX = 'linux';
|
||||
public const TYPE_SCHEME = 'scheme';
|
||||
|
||||
public const SCHEME_HTTP = 'http';
|
||||
@@ -78,24 +68,14 @@ class Platform
|
||||
|
||||
switch ($type) {
|
||||
case self::TYPE_WEB:
|
||||
case self::TYPE_FLUTTER_WEB:
|
||||
if (!empty($hostname)) {
|
||||
$hostnames[] = $hostname;
|
||||
}
|
||||
break;
|
||||
case self::TYPE_FLUTTER_IOS:
|
||||
case self::TYPE_FLUTTER_ANDROID:
|
||||
case self::TYPE_FLUTTER_MACOS:
|
||||
case self::TYPE_FLUTTER_WINDOWS:
|
||||
case self::TYPE_FLUTTER_LINUX:
|
||||
case self::TYPE_ANDROID:
|
||||
case self::TYPE_APPLE_IOS:
|
||||
case self::TYPE_APPLE_MACOS:
|
||||
case self::TYPE_APPLE_WATCHOS:
|
||||
case self::TYPE_APPLE_TVOS:
|
||||
case self::TYPE_REACT_NATIVE_IOS:
|
||||
case self::TYPE_REACT_NATIVE_ANDROID:
|
||||
case self::TYPE_UNITY:
|
||||
case self::TYPE_WINDOWS:
|
||||
case self::TYPE_LINUX:
|
||||
case self::TYPE_APPLE:
|
||||
if (!empty($key)) {
|
||||
$hostnames[] = $key;
|
||||
}
|
||||
@@ -121,37 +101,24 @@ class Platform
|
||||
}
|
||||
break;
|
||||
case self::TYPE_WEB:
|
||||
case self::TYPE_FLUTTER_WEB:
|
||||
$schemes[] = self::SCHEME_HTTP;
|
||||
$schemes[] = self::SCHEME_HTTPS;
|
||||
break;
|
||||
case self::TYPE_FLUTTER_IOS:
|
||||
case self::TYPE_APPLE_IOS:
|
||||
case self::TYPE_REACT_NATIVE_IOS:
|
||||
$schemes[] = self::SCHEME_IOS;
|
||||
break;
|
||||
case self::TYPE_FLUTTER_ANDROID:
|
||||
case self::TYPE_ANDROID:
|
||||
case self::TYPE_REACT_NATIVE_ANDROID:
|
||||
$schemes[] = self::SCHEME_ANDROID;
|
||||
break;
|
||||
case self::TYPE_FLUTTER_MACOS:
|
||||
case self::TYPE_APPLE_MACOS:
|
||||
case self::TYPE_APPLE:
|
||||
$schemes[] = self::SCHEME_WATCHOS;
|
||||
$schemes[] = self::SCHEME_MACOS;
|
||||
$schemes[] = self::SCHEME_TVOS;
|
||||
$schemes[] = self::SCHEME_IOS;
|
||||
break;
|
||||
case self::TYPE_FLUTTER_WINDOWS:
|
||||
case self::TYPE_UNITY:
|
||||
case self::TYPE_WINDOWS:
|
||||
$schemes[] = self::SCHEME_WINDOWS;
|
||||
break;
|
||||
case self::TYPE_FLUTTER_LINUX:
|
||||
case self::TYPE_LINUX:
|
||||
$schemes[] = self::SCHEME_LINUX;
|
||||
break;
|
||||
case self::TYPE_APPLE_WATCHOS:
|
||||
$schemes[] = self::SCHEME_WATCHOS;
|
||||
break;
|
||||
case self::TYPE_APPLE_TVOS:
|
||||
$schemes[] = self::SCHEME_TVOS;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -145,9 +145,20 @@ class Server
|
||||
$paths = $this->paths;
|
||||
$state = $this->state;
|
||||
|
||||
Http::setResource('installerState', fn () => $state);
|
||||
Http::setResource('installerConfig', fn () => $config);
|
||||
Http::setResource('installerPaths', fn () => $paths);
|
||||
$adapter = new class ($host, $port, ['worker_num' => 1]) extends SwooleAdapter {
|
||||
public function getNativeServer(): SwooleServer
|
||||
{
|
||||
return $this->server;
|
||||
}
|
||||
};
|
||||
|
||||
$nativeServer = $adapter->getNativeServer();
|
||||
|
||||
$container = $adapter->getContainer();
|
||||
$container->set('installerState', fn () => $state);
|
||||
$container->set('installerConfig', fn () => $config);
|
||||
$container->set('installerPaths', fn () => $paths);
|
||||
$container->set('swooleServer', fn () => $nativeServer);
|
||||
|
||||
// Register routes via Utopia Platform
|
||||
$platform = new Installer();
|
||||
@@ -160,17 +171,6 @@ class Server
|
||||
->inject('response')
|
||||
->action($errorHandler->action(...));
|
||||
|
||||
$adapter = new class ($host, $port, ['worker_num' => 1]) extends SwooleAdapter {
|
||||
public function getNativeServer(): SwooleServer
|
||||
{
|
||||
return $this->server;
|
||||
}
|
||||
};
|
||||
|
||||
$nativeServer = $adapter->getNativeServer();
|
||||
|
||||
Http::setResource('swooleServer', fn () => $nativeServer);
|
||||
|
||||
$nativeServer->on('start', function () use ($nativeServer, $port, $readyFile) {
|
||||
\Swoole\Process::signal(SIGTERM, fn () => $nativeServer->shutdown());
|
||||
\Swoole\Process::signal(SIGINT, fn () => $nativeServer->shutdown());
|
||||
@@ -180,7 +180,7 @@ class Server
|
||||
}
|
||||
});
|
||||
|
||||
$adapter->onRequest(function (Request $request, Response $response) use ($files) {
|
||||
$adapter->onRequest(function (Request $request, Response $response) use ($adapter, $files) {
|
||||
// Serve static files from memory
|
||||
$uri = $request->getURI();
|
||||
if ($files->isFileLoaded($uri)) {
|
||||
@@ -190,7 +190,7 @@ class Server
|
||||
return;
|
||||
}
|
||||
|
||||
$app = new Http('UTC');
|
||||
$app = new Http($adapter, 'UTC');
|
||||
$app->run($request, $response);
|
||||
});
|
||||
|
||||
|
||||
@@ -182,19 +182,33 @@ class Update extends Action
|
||||
$dbForDatabases = $getDatabasesDB($databaseDoc);
|
||||
|
||||
try {
|
||||
$dbForDatabases->withTransaction(function () use ($dbForDatabases, $dbForProject, $transactionState, $queueForDeletes, $transactionId, &$transaction, &$operations, &$totalOperations, &$databaseOperations, &$currentDocumentId, $authorization) {
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
|
||||
'status' => 'committing',
|
||||
])));
|
||||
$transaction = $authorization->skip(fn () => $dbForProject->updateDocument(
|
||||
'transactions',
|
||||
$transactionId,
|
||||
new Document(['status' => 'committing'])
|
||||
));
|
||||
|
||||
$operations = $authorization->skip(fn () => $dbForProject->find('transactionLogs', [
|
||||
Query::equal('transactionInternalId', [$transaction->getSequence()]),
|
||||
Query::orderAsc(),
|
||||
Query::limit(PHP_INT_MAX),
|
||||
]));
|
||||
$operations = $authorization->skip(fn () => $dbForProject->find('transactionLogs', [
|
||||
Query::equal('transactionInternalId', [$transaction->getSequence()]),
|
||||
Query::orderAsc(),
|
||||
Query::limit(PHP_INT_MAX),
|
||||
]));
|
||||
|
||||
$collections = [];
|
||||
foreach ($operations as $operation) {
|
||||
$databaseInternalId = $operation['databaseInternalId'];
|
||||
$collectionInternalId = $operation['collectionInternalId'];
|
||||
$collectionId = "database_{$databaseInternalId}_collection_{$collectionInternalId}";
|
||||
|
||||
if (!isset($collections[$collectionId])) {
|
||||
$collections[$collectionId] = $authorization->skip(
|
||||
fn () => $dbForProject->getCollection($collectionId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$dbForDatabases->withTransaction(function () use ($dbForDatabases, $transactionState, &$operations, &$totalOperations, &$databaseOperations, &$currentDocumentId, $collections) {
|
||||
$state = [];
|
||||
$collections = [];
|
||||
|
||||
foreach ($operations as $operation) {
|
||||
$databaseInternalId = $operation['databaseInternalId'];
|
||||
@@ -210,11 +224,6 @@ class Update extends Action
|
||||
$data = $data->getArrayCopy();
|
||||
}
|
||||
|
||||
if (!isset($collections[$collectionId])) {
|
||||
$collections[$collectionId] = $authorization->skip(
|
||||
fn () => $dbForProject->getCollection($collectionId)
|
||||
);
|
||||
}
|
||||
$collection = $collections[$collectionId];
|
||||
|
||||
if (\is_array($data) && !empty($data)) {
|
||||
@@ -276,16 +285,17 @@ class Update extends Action
|
||||
}
|
||||
}
|
||||
|
||||
$transaction = $authorization->skip(fn () => $dbForProject->updateDocument(
|
||||
'transactions',
|
||||
$transactionId,
|
||||
new Document(['status' => 'committed'])
|
||||
));
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($transaction);
|
||||
});
|
||||
|
||||
$transaction = $authorization->skip(fn () => $dbForProject->updateDocument(
|
||||
'transactions',
|
||||
$transactionId,
|
||||
new Document(['status' => 'committed'])
|
||||
));
|
||||
|
||||
$queueForDeletes
|
||||
->setType(DELETE_TYPE_DOCUMENT)
|
||||
->setDocument($transaction);
|
||||
} catch (NotFoundException $e) {
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
|
||||
'status' => 'failed',
|
||||
|
||||
+16
-29
@@ -1,20 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Projects\Http\Projects\Labels;
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\Labels;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Action;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Projects;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
@@ -27,39 +22,37 @@ class Update extends Action
|
||||
return 'updateProjectLabels';
|
||||
}
|
||||
|
||||
protected function getQueriesValidator(): Validator
|
||||
{
|
||||
return new Projects();
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT)
|
||||
->setHttpPath('/v1/projects/:projectId/labels')
|
||||
->setHttpPath('/v1/project/labels')
|
||||
->httpAlias('/v1/projects/:projectId/labels')
|
||||
->desc('Update project labels')
|
||||
->groups(['api', 'projects'])
|
||||
->label('scope', 'projects.write')
|
||||
->groups(['api', 'project'])
|
||||
->label('scope', 'project.write')
|
||||
->label('event', 'labels.*.update')
|
||||
->label('audits.event', 'project.labels.update')
|
||||
->label('audits.resource', 'project.labels/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'projects',
|
||||
group: 'projects',
|
||||
namespace: 'project',
|
||||
group: null,
|
||||
name: 'updateLabels',
|
||||
description: <<<EOT
|
||||
Update the project labels by its unique ID. Labels can be used to easily filter projects in an organization.
|
||||
Update the project labels. Labels can be used to easily filter projects in an organization.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN],
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PROJECT
|
||||
model: Response::MODEL_PROJECT,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('labels', [], new ArrayList(new Text(36, allowList: [...Text::NUMBERS, ...Text::ALPHABET_UPPER, ...Text::ALPHABET_LOWER]), APP_LIMIT_ARRAY_LABELS_SIZE), 'Array of project labels. Replaces the previous labels. Maximum of ' . APP_LIMIT_ARRAY_LABELS_SIZE . ' labels are allowed, each up to 36 alphanumeric characters long.')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -67,17 +60,11 @@ class Update extends Action
|
||||
* @param array<string> $labels
|
||||
*/
|
||||
public function action(
|
||||
string $projectId,
|
||||
array $labels,
|
||||
Response $response,
|
||||
Database $dbForPlatform
|
||||
Database $dbForPlatform,
|
||||
Document $project
|
||||
): void {
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$labels = (array) \array_values(\array_unique($labels));
|
||||
|
||||
$project = $dbForPlatform->updateDocument('projects', $project->getId(), new Document(['labels' => $labels]));
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\Platforms\Android;
|
||||
|
||||
use Appwrite\Event\Event as QueueEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'createProjectAndroidPlatform';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/project/platforms/android')
|
||||
->desc('Create project Android platform')
|
||||
->groups(['api', 'project'])
|
||||
->label('scope', 'project.write')
|
||||
->label('event', 'platforms.[platformId].create')
|
||||
->label('audits.event', 'project.platform.create')
|
||||
->label('audits.resource', 'project.platform/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: 'platforms',
|
||||
name: 'createAndroidPlatform',
|
||||
description: <<<EOT
|
||||
Create a new Android platform for your project. Use this endpoint to register a new Android platform where your users will run your application which will interact with the Appwrite API.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
model: Response::MODEL_PLATFORM_ANDROID,
|
||||
)
|
||||
],
|
||||
))
|
||||
->param('platformId', '', fn (Database $dbForPlatform) => new CustomId(false, $dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForPlatform'])
|
||||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('applicationId', '', new Text(256), 'Android application ID. Max length: 256 chars.')
|
||||
->inject('response')
|
||||
->inject('queueForEvents')
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $platformId,
|
||||
string $name,
|
||||
string $applicationId,
|
||||
Response $response,
|
||||
QueueEvent $queueForEvents,
|
||||
Document $project,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
) {
|
||||
$platformId = ($platformId == 'unique()') ? ID::unique() : $platformId;
|
||||
|
||||
$platform = new Document([
|
||||
'$id' => $platformId,
|
||||
'$permissions' => [],
|
||||
'projectInternalId' => $project->getSequence(),
|
||||
'projectId' => $project->getId(),
|
||||
'type' => Platform::TYPE_ANDROID,
|
||||
'name' => $name,
|
||||
'key' => $applicationId,
|
||||
'hostname' => '',
|
||||
]);
|
||||
|
||||
try {
|
||||
$platform = $authorization->skip(fn () => $dbForPlatform->createDocument('platforms', $platform));
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::PLATFORM_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$authorization->skip(fn () => $dbForPlatform->purgeCachedDocument('projects', $project->getId()));
|
||||
|
||||
$queueForEvents->setParam('platformId', $platform->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($platform, Response::MODEL_PLATFORM_ANDROID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\Platforms\Android;
|
||||
|
||||
use Appwrite\Event\Event as QueueEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'updateProjectAndroidPlatform';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT)
|
||||
->setHttpPath('/v1/project/platforms/android/:platformId')
|
||||
->desc('Update project Android platform')
|
||||
->groups(['api', 'project'])
|
||||
->label('scope', 'project.write')
|
||||
->label('event', 'platforms.[platformId].update')
|
||||
->label('audits.event', 'project.platform.update')
|
||||
->label('audits.resource', 'project.platform/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: 'platforms',
|
||||
name: 'updateAndroidPlatform',
|
||||
description: <<<EOT
|
||||
Update an Android platform by its unique ID. Use this endpoint to update the platform's name or application ID.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PLATFORM_ANDROID,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('platformId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform ID.', false, ['dbForPlatform'])
|
||||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('applicationId', '', new Text(256), 'Android application ID. Max length: 256 chars.')
|
||||
->inject('response')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('project')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $platformId,
|
||||
string $name,
|
||||
string $applicationId,
|
||||
Response $response,
|
||||
QueueEvent $queueForEvents,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
Document $project,
|
||||
) {
|
||||
$platform = $authorization->skip(fn () => $dbForPlatform->getDocument('platforms', $platformId));
|
||||
|
||||
if ($platform->isEmpty() || $platform->getAttribute('projectInternalId', '') !== $project->getSequence()) {
|
||||
throw new Exception(Exception::PLATFORM_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($platform->getAttribute('type', '') !== Platform::TYPE_ANDROID) {
|
||||
throw new Exception(Exception::PLATFORM_METHOD_UNSUPPORTED);
|
||||
}
|
||||
|
||||
$updates = new Document([
|
||||
'name' => $name,
|
||||
'key' => $applicationId,
|
||||
]);
|
||||
|
||||
try {
|
||||
$platform = $authorization->skip(fn () => $dbForPlatform->updateDocument('platforms', $platform->getId(), $updates));
|
||||
} catch (Duplicate) {
|
||||
throw new Exception(Exception::PLATFORM_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$authorization->skip(fn () => $dbForPlatform->purgeCachedDocument('projects', $project->getId()));
|
||||
|
||||
$queueForEvents->setParam('platformId', $platform->getId());
|
||||
|
||||
$response->dynamic($platform, Response::MODEL_PLATFORM_ANDROID);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\Platforms\Apple;
|
||||
|
||||
use Appwrite\Event\Event as QueueEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'createProjectApplePlatform';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/project/platforms/apple')
|
||||
->desc('Create project Apple platform')
|
||||
->groups(['api', 'project'])
|
||||
->label('scope', 'project.write')
|
||||
->label('event', 'platforms.[platformId].create')
|
||||
->label('audits.event', 'project.platform.create')
|
||||
->label('audits.resource', 'project.platform/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: 'platforms',
|
||||
name: 'createApplePlatform',
|
||||
description: <<<EOT
|
||||
Create a new Apple platform for your project. Use this endpoint to register a new Apple platform where your users will run your application which will interact with the Appwrite API.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
model: Response::MODEL_PLATFORM_APPLE,
|
||||
)
|
||||
],
|
||||
))
|
||||
->param('platformId', '', fn (Database $dbForPlatform) => new CustomId(false, $dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForPlatform'])
|
||||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('bundleIdentifier', '', new Text(256), 'Apple bundle identifier. Max length: 256 chars.')
|
||||
->inject('response')
|
||||
->inject('queueForEvents')
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $platformId,
|
||||
string $name,
|
||||
string $bundleIdentifier,
|
||||
Response $response,
|
||||
QueueEvent $queueForEvents,
|
||||
Document $project,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
) {
|
||||
$platformId = ($platformId == 'unique()') ? ID::unique() : $platformId;
|
||||
|
||||
$platform = new Document([
|
||||
'$id' => $platformId,
|
||||
'$permissions' => [],
|
||||
'projectInternalId' => $project->getSequence(),
|
||||
'projectId' => $project->getId(),
|
||||
'type' => Platform::TYPE_APPLE,
|
||||
'name' => $name,
|
||||
'key' => $bundleIdentifier,
|
||||
'hostname' => '',
|
||||
]);
|
||||
|
||||
try {
|
||||
$platform = $authorization->skip(fn () => $dbForPlatform->createDocument('platforms', $platform));
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::PLATFORM_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$authorization->skip(fn () => $dbForPlatform->purgeCachedDocument('projects', $project->getId()));
|
||||
|
||||
$queueForEvents->setParam('platformId', $platform->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($platform, Response::MODEL_PLATFORM_APPLE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\Platforms\Apple;
|
||||
|
||||
use Appwrite\Event\Event as QueueEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'updateProjectApplePlatform';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT)
|
||||
->setHttpPath('/v1/project/platforms/apple/:platformId')
|
||||
->desc('Update project Apple platform')
|
||||
->groups(['api', 'project'])
|
||||
->label('scope', 'project.write')
|
||||
->label('event', 'platforms.[platformId].update')
|
||||
->label('audits.event', 'project.platform.update')
|
||||
->label('audits.resource', 'project.platform/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: 'platforms',
|
||||
name: 'updateApplePlatform',
|
||||
description: <<<EOT
|
||||
Update an Apple platform by its unique ID. Use this endpoint to update the platform's name or bundle identifier.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PLATFORM_APPLE,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('platformId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform ID.', false, ['dbForPlatform'])
|
||||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('bundleIdentifier', '', new Text(256), 'Apple bundle identifier. Max length: 256 chars.')
|
||||
->inject('response')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('project')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $platformId,
|
||||
string $name,
|
||||
string $bundleIdentifier,
|
||||
Response $response,
|
||||
QueueEvent $queueForEvents,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
Document $project,
|
||||
) {
|
||||
$platform = $authorization->skip(fn () => $dbForPlatform->getDocument('platforms', $platformId));
|
||||
|
||||
if ($platform->isEmpty() || $platform->getAttribute('projectInternalId', '') !== $project->getSequence()) {
|
||||
throw new Exception(Exception::PLATFORM_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($platform->getAttribute('type', '') !== Platform::TYPE_APPLE) {
|
||||
throw new Exception(Exception::PLATFORM_METHOD_UNSUPPORTED);
|
||||
}
|
||||
|
||||
$updates = new Document([
|
||||
'name' => $name,
|
||||
'key' => $bundleIdentifier,
|
||||
]);
|
||||
|
||||
try {
|
||||
$platform = $authorization->skip(fn () => $dbForPlatform->updateDocument('platforms', $platform->getId(), $updates));
|
||||
} catch (Duplicate) {
|
||||
throw new Exception(Exception::PLATFORM_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$authorization->skip(fn () => $dbForPlatform->purgeCachedDocument('projects', $project->getId()));
|
||||
|
||||
$queueForEvents->setParam('platformId', $platform->getId());
|
||||
|
||||
$response->dynamic($platform, Response::MODEL_PLATFORM_APPLE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\Platforms;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
||||
class Delete extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'deleteProjectPlatform';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_DELETE)
|
||||
->setHttpPath('/v1/project/platforms/:platformId')
|
||||
->httpAlias('/v1/projects/:projectId/platforms/:platformId')
|
||||
->desc('Delete project platform')
|
||||
->groups(['api', 'project'])
|
||||
->label('scope', 'project.write')
|
||||
->label('event', 'platforms.[platformId].delete')
|
||||
->label('audits.event', 'project.platform.delete')
|
||||
->label('audits.resource', 'project.platform/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: 'platforms',
|
||||
name: 'deletePlatform',
|
||||
description: <<<EOT
|
||||
Delete a platform by its unique ID. This endpoint removes the platform and all its configurations from the project.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_NOCONTENT,
|
||||
model: Response::MODEL_NONE,
|
||||
)
|
||||
],
|
||||
contentType: ContentType::NONE
|
||||
))
|
||||
->param('platformId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform ID.', false, ['dbForPlatform'])
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('project')
|
||||
->inject('queueForEvents')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $platformId,
|
||||
Response $response,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
Document $project,
|
||||
Event $queueForEvents,
|
||||
) {
|
||||
$platform = $authorization->skip(fn () => $dbForPlatform->getDocument('platforms', $platformId));
|
||||
|
||||
if ($platform->isEmpty() || $platform->getAttribute('projectInternalId', '') !== $project->getSequence()) {
|
||||
throw new Exception(Exception::PLATFORM_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$authorization->skip(fn () => $dbForPlatform->deleteDocument('platforms', $platform->getId()))) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Failed to remove document from DB');
|
||||
};
|
||||
|
||||
$authorization->skip(fn () => $dbForPlatform->purgeCachedDocument('projects', $project->getId()));
|
||||
|
||||
$queueForEvents->setParam('platformId', $platform->getId());
|
||||
|
||||
$response->noContent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\Platforms;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'getProjectPlatform';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/project/platforms/:platformId')
|
||||
->httpAlias('/v1/projects/:projectId/platforms/:platformId')
|
||||
->desc('Get project platform')
|
||||
->groups(['api', 'project'])
|
||||
->label('scope', 'project.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: 'platforms',
|
||||
name: 'getPlatform',
|
||||
description: <<<EOT
|
||||
Get a platform by its unique ID. This endpoint returns the platform's details, including its name, type, and key configurations.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: [
|
||||
Response::MODEL_PLATFORM_WEB,
|
||||
Response::MODEL_PLATFORM_APPLE,
|
||||
Response::MODEL_PLATFORM_ANDROID,
|
||||
Response::MODEL_PLATFORM_WINDOWS,
|
||||
Response::MODEL_PLATFORM_LINUX,
|
||||
],
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('platformId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform ID.', false, ['dbForPlatform'])
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('project')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $platformId,
|
||||
Response $response,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
Document $project
|
||||
) {
|
||||
$platform = $authorization->skip(fn () => $dbForPlatform->getDocument('platforms', $platformId));
|
||||
|
||||
if ($platform->isEmpty() || $platform->getAttribute('projectInternalId', '') !== $project->getSequence()) {
|
||||
throw new Exception(Exception::PLATFORM_NOT_FOUND);
|
||||
}
|
||||
|
||||
$type = $platform->getAttribute('type');
|
||||
|
||||
$model = match($type) {
|
||||
Platform::TYPE_WEB => Response::MODEL_PLATFORM_WEB,
|
||||
Platform::TYPE_APPLE => Response::MODEL_PLATFORM_APPLE,
|
||||
Platform::TYPE_ANDROID => Response::MODEL_PLATFORM_ANDROID,
|
||||
Platform::TYPE_WINDOWS => Response::MODEL_PLATFORM_WINDOWS,
|
||||
Platform::TYPE_LINUX => Response::MODEL_PLATFORM_LINUX,
|
||||
default => Response::MODEL_PLATFORM_WEB // Backwards compatibility
|
||||
};
|
||||
|
||||
$response->dynamic($platform, $model);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\Platforms\Linux;
|
||||
|
||||
use Appwrite\Event\Event as QueueEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'createProjectLinuxPlatform';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/project/platforms/linux')
|
||||
->desc('Create project Linux platform')
|
||||
->groups(['api', 'project'])
|
||||
->label('scope', 'project.write')
|
||||
->label('event', 'platforms.[platformId].create')
|
||||
->label('audits.event', 'project.platform.create')
|
||||
->label('audits.resource', 'project.platform/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: 'platforms',
|
||||
name: 'createLinuxPlatform',
|
||||
description: <<<EOT
|
||||
Create a new Linux platform for your project. Use this endpoint to register a new Linux platform where your users will run your application which will interact with the Appwrite API.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
model: Response::MODEL_PLATFORM_LINUX,
|
||||
)
|
||||
],
|
||||
))
|
||||
->param('platformId', '', fn (Database $dbForPlatform) => new CustomId(false, $dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForPlatform'])
|
||||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('packageName', '', new Text(256), 'Linux package name. Max length: 256 chars.')
|
||||
->inject('response')
|
||||
->inject('queueForEvents')
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $platformId,
|
||||
string $name,
|
||||
string $packageName,
|
||||
Response $response,
|
||||
QueueEvent $queueForEvents,
|
||||
Document $project,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
) {
|
||||
$platformId = ($platformId == 'unique()') ? ID::unique() : $platformId;
|
||||
|
||||
$platform = new Document([
|
||||
'$id' => $platformId,
|
||||
'$permissions' => [],
|
||||
'projectInternalId' => $project->getSequence(),
|
||||
'projectId' => $project->getId(),
|
||||
'type' => Platform::TYPE_LINUX,
|
||||
'name' => $name,
|
||||
'key' => $packageName,
|
||||
'hostname' => '', // Web platform attribute
|
||||
]);
|
||||
|
||||
try {
|
||||
$platform = $authorization->skip(fn () => $dbForPlatform->createDocument('platforms', $platform));
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::PLATFORM_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$authorization->skip(fn () => $dbForPlatform->purgeCachedDocument('projects', $project->getId()));
|
||||
|
||||
$queueForEvents->setParam('platformId', $platform->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($platform, Response::MODEL_PLATFORM_LINUX);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\Platforms\Linux;
|
||||
|
||||
use Appwrite\Event\Event as QueueEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'updateProjectLinuxPlatform';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT)
|
||||
->setHttpPath('/v1/project/platforms/linux/:platformId')
|
||||
->desc('Update project Linux platform')
|
||||
->groups(['api', 'project'])
|
||||
->label('scope', 'project.write')
|
||||
->label('event', 'platforms.[platformId].update')
|
||||
->label('audits.event', 'project.platform.update')
|
||||
->label('audits.resource', 'project.platform/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: 'platforms',
|
||||
name: 'updateLinuxPlatform',
|
||||
description: <<<EOT
|
||||
Update a Linux platform by its unique ID. Use this endpoint to update the platform's name or package name.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PLATFORM_LINUX,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('platformId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform ID.', false, ['dbForPlatform'])
|
||||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('packageName', '', new Text(256), 'Linux package name. Max length: 256 chars.')
|
||||
->inject('response')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('project')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $platformId,
|
||||
string $name,
|
||||
string $packageName,
|
||||
Response $response,
|
||||
QueueEvent $queueForEvents,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
Document $project,
|
||||
) {
|
||||
$platform = $authorization->skip(fn () => $dbForPlatform->getDocument('platforms', $platformId));
|
||||
|
||||
if ($platform->isEmpty() || $platform->getAttribute('projectInternalId', '') !== $project->getSequence()) {
|
||||
throw new Exception(Exception::PLATFORM_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($platform->getAttribute('type', '') !== Platform::TYPE_LINUX) {
|
||||
throw new Exception(Exception::PLATFORM_METHOD_UNSUPPORTED);
|
||||
}
|
||||
|
||||
$updates = new Document([
|
||||
'name' => $name,
|
||||
'key' => $packageName,
|
||||
]);
|
||||
|
||||
try {
|
||||
$platform = $authorization->skip(fn () => $dbForPlatform->updateDocument('platforms', $platform->getId(), $updates));
|
||||
} catch (Duplicate) {
|
||||
throw new Exception(Exception::PLATFORM_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$authorization->skip(fn () => $dbForPlatform->purgeCachedDocument('projects', $project->getId()));
|
||||
|
||||
$queueForEvents->setParam('platformId', $platform->getId());
|
||||
|
||||
$response->dynamic($platform, Response::MODEL_PLATFORM_LINUX);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\Platforms\Web;
|
||||
|
||||
use Appwrite\Event\Event as QueueEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Hostname;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
/**
|
||||
* WARNING: This kind of platform has most complex action, because it holds backwards compatibility too.
|
||||
* If possible, refer to any other type of platform for APIs, for more simpler endpoint.
|
||||
*/
|
||||
class Create extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'createProjectWebPlatform';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/project/platforms/web')
|
||||
->httpAlias('/v1/projects/:projectId/platforms')
|
||||
->desc('Create project web platform')
|
||||
->groups(['api', 'project'])
|
||||
->label('scope', 'project.write')
|
||||
->label('event', 'platforms.[platformId].create')
|
||||
->label('audits.event', 'project.platform.create')
|
||||
->label('audits.resource', 'project.platform/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: 'platforms',
|
||||
name: 'createWebPlatform',
|
||||
description: <<<EOT
|
||||
Create a new web platform for your project. Use this endpoint to register a new platform where your users will run your application which will interact with the Appwrite API.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
model: Response::MODEL_PLATFORM_WEB,
|
||||
)
|
||||
],
|
||||
))
|
||||
->param('platformId', '', fn (Database $dbForPlatform) => new CustomId(false, $dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForPlatform'])
|
||||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('hostname', '', new Hostname(), 'Platform web hostname. Max length: 256 chars.', optional: true) // Optional for backwards compatibility
|
||||
->param('key', '', new Text(256), 'Deprecated: Package name for Android or bundle ID for iOS or macOS. Max length: 256 chars.', optional: true, deprecated: true) // Exists for backwards compatibility
|
||||
->param('type', '', new Text(256), 'Deprecated: Platform type. Max length: 256 chars.', optional: true, deprecated: true) // Exists for backwards compatibility
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('queueForEvents')
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $platformId,
|
||||
string $name,
|
||||
string $hostname,
|
||||
?string $key, // For backwards compatibility
|
||||
?string $type, // For backwards compatibility
|
||||
Request $request,
|
||||
Response $response,
|
||||
QueueEvent $queueForEvents,
|
||||
Document $project,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
) {
|
||||
$key = $key ?? ''; // App platform attribute, backwards compatibility
|
||||
$type = $type ?? ''; // App platform attribute, backwards compatibility
|
||||
|
||||
// Backwards compatibility
|
||||
// Used to have: type, name, key, hostname
|
||||
if (!empty($type)) {
|
||||
// Validate deprecated type, and rename to new type
|
||||
$deprecatedTypeMapping = [
|
||||
// Web
|
||||
'web' => Platform::TYPE_WEB,
|
||||
'flutter-web' => Platform::TYPE_WEB,
|
||||
'unity' => Platform::TYPE_WEB, // Was not officially supported anyway
|
||||
|
||||
// Apple
|
||||
'flutter-macos' => Platform::TYPE_APPLE,
|
||||
'flutter-ios' => Platform::TYPE_APPLE,
|
||||
'react-native-ios' => Platform::TYPE_APPLE,
|
||||
'apple-ios' => Platform::TYPE_APPLE,
|
||||
'apple-macos' => Platform::TYPE_APPLE,
|
||||
'apple-watchos' => Platform::TYPE_APPLE,
|
||||
'apple-tvos' => Platform::TYPE_APPLE,
|
||||
|
||||
// Android
|
||||
'flutter-android' => Platform::TYPE_ANDROID,
|
||||
'android' => Platform::TYPE_ANDROID,
|
||||
'react-native-android' => Platform::TYPE_ANDROID,
|
||||
|
||||
'flutter-linux' => Platform::TYPE_LINUX,
|
||||
'flutter-windows' => Platform::TYPE_WINDOWS,
|
||||
];
|
||||
|
||||
$typeValidator = new WhiteList(\array_keys($deprecatedTypeMapping));
|
||||
if (!$typeValidator->isValid($request->getParam('type', ''))) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Param "type" is invalid: ' . $typeValidator->getDescription());
|
||||
}
|
||||
|
||||
$type = $deprecatedTypeMapping[$request->getParam('type', '')] ?? '';
|
||||
}
|
||||
|
||||
if (!empty($key)) {
|
||||
// Validate deprecated app id (key)
|
||||
$keyValidator = new Text(256);
|
||||
if (!$keyValidator->isValid($key)) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Param "key" is invalid: ' . $keyValidator->getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($key) && empty($type)) {
|
||||
// Modern request, validate hostname
|
||||
if (empty($hostname)) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Param "hostname" is not optional.');
|
||||
}
|
||||
}
|
||||
|
||||
$platformId = ($platformId == 'unique()') ? ID::unique() : $platformId;
|
||||
|
||||
$platform = new Document([
|
||||
'$id' => $platformId,
|
||||
'$permissions' => [],
|
||||
'projectInternalId' => $project->getSequence(),
|
||||
'projectId' => $project->getId(),
|
||||
'type' => $type ?: Platform::TYPE_WEB, // Preserve type for backwards compatibility
|
||||
'name' => $name,
|
||||
'key' => $key,
|
||||
'hostname' => $hostname
|
||||
]);
|
||||
|
||||
try {
|
||||
$platform = $authorization->skip(fn () => $dbForPlatform->createDocument('platforms', $platform));
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::PLATFORM_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$authorization->skip(fn () => $dbForPlatform->purgeCachedDocument('projects', $project->getId()));
|
||||
|
||||
$queueForEvents->setParam('platformId', $platform->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($platform, Response::MODEL_PLATFORM_WEB);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\Platforms\Web;
|
||||
|
||||
use Appwrite\Event\Event as QueueEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Hostname;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'updateProjectWebPlatform';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT)
|
||||
->setHttpPath('/v1/project/platforms/web/:platformId')
|
||||
->httpAlias('/v1/projects/:projectId/platforms/:platformId')
|
||||
->desc('Update project web platform')
|
||||
->groups(['api', 'project'])
|
||||
->label('scope', 'project.write')
|
||||
->label('event', 'platforms.[platformId].update')
|
||||
->label('audits.event', 'project.platform.update')
|
||||
->label('audits.resource', 'project.platform/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: 'platforms',
|
||||
name: 'updateWebPlatform',
|
||||
description: <<<EOT
|
||||
Update a web platform by its unique ID. Use this endpoint to update the platform's name or hostname.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PLATFORM_WEB,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('platformId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform ID.', false, ['dbForPlatform'])
|
||||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('hostname', '', new Hostname(), 'Platform web hostname. Max length: 256 chars.', optional: true) // Optional for backwards compatibility
|
||||
->param('key', '', new Text(256), 'Package name for Android or bundle ID for iOS or macOS. Max length: 256 chars.', optional: true, deprecated: true) // Exists for backwards compatibility
|
||||
->inject('response')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('project')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $platformId,
|
||||
string $name,
|
||||
string $hostname,
|
||||
?string $key, // For backwards compatibility
|
||||
Response $response,
|
||||
QueueEvent $queueForEvents,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
Document $project,
|
||||
) {
|
||||
$key = $key ?? ''; // App platform attribute, backwards compatibility
|
||||
|
||||
// Backwards compatibility
|
||||
// Used to have: type, name, key, hostname
|
||||
if (!empty($key)) {
|
||||
// Validate deprecated app id (key)
|
||||
$keyValidator = new Text(256);
|
||||
if (!$keyValidator->isValid($key)) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Param "key" is invalid: ' . $keyValidator->getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
// One day, ideally, we ensure hostname is not empty
|
||||
// But for backwards compatibility backend must threat it as optional for now
|
||||
|
||||
$platform = $authorization->skip(fn () => $dbForPlatform->getDocument('platforms', $platformId));
|
||||
|
||||
if ($platform->isEmpty() || $platform->getAttribute('projectInternalId', '') !== $project->getSequence()) {
|
||||
throw new Exception(Exception::PLATFORM_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Wrapped in if, for backwards compatibility
|
||||
if (!empty($hostname)) {
|
||||
$supportedTypes = [
|
||||
Platform::TYPE_WEB,
|
||||
// Backwards compatibility
|
||||
'flutter-web',
|
||||
'unity',
|
||||
'flutter-macos',
|
||||
'flutter-ios',
|
||||
'react-native-ios',
|
||||
'apple-ios',
|
||||
'apple-macos',
|
||||
'apple-watchos',
|
||||
'apple-tvos',
|
||||
'flutter-android',
|
||||
'react-native-android',
|
||||
'flutter-windows',
|
||||
'flutter-linux',
|
||||
];
|
||||
if (!in_array($platform->getAttribute('type', ''), $supportedTypes)) {
|
||||
throw new Exception(Exception::PLATFORM_METHOD_UNSUPPORTED);
|
||||
}
|
||||
}
|
||||
|
||||
$updates = new Document([
|
||||
'name' => $name,
|
||||
]);
|
||||
|
||||
// Wrapped in if, for backwards compatibility
|
||||
if (!empty($hostname)) {
|
||||
$updates->setAttribute('hostname', $hostname);
|
||||
}
|
||||
|
||||
// Backwards compatibility
|
||||
if (!empty($key)) {
|
||||
$updates->setAttribute('key', $key);
|
||||
}
|
||||
|
||||
try {
|
||||
$platform = $authorization->skip(fn () => $dbForPlatform->updateDocument('platforms', $platform->getId(), $updates));
|
||||
} catch (Duplicate) {
|
||||
throw new Exception(Exception::PLATFORM_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$authorization->skip(fn () => $dbForPlatform->purgeCachedDocument('projects', $project->getId()));
|
||||
|
||||
$queueForEvents->setParam('platformId', $platform->getId());
|
||||
|
||||
$response->dynamic($platform, Response::MODEL_PLATFORM_WEB);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\Platforms\Windows;
|
||||
|
||||
use Appwrite\Event\Event as QueueEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate as DuplicateException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Create extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'createProjectWindowsPlatform';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_POST)
|
||||
->setHttpPath('/v1/project/platforms/windows')
|
||||
->desc('Create project Windows platform')
|
||||
->groups(['api', 'project'])
|
||||
->label('scope', 'project.write')
|
||||
->label('event', 'platforms.[platformId].create')
|
||||
->label('audits.event', 'project.platform.create')
|
||||
->label('audits.resource', 'project.platform/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: 'platforms',
|
||||
name: 'createWindowsPlatform',
|
||||
description: <<<EOT
|
||||
Create a new Windows platform for your project. Use this endpoint to register a new Windows platform where your users will run your application which will interact with the Appwrite API.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_CREATED,
|
||||
model: Response::MODEL_PLATFORM_WINDOWS,
|
||||
)
|
||||
],
|
||||
))
|
||||
->param('platformId', '', fn (Database $dbForPlatform) => new CustomId(false, $dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', false, ['dbForPlatform'])
|
||||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('packageIdentifierName', '', new Text(256), 'Windows package identifier name. Max length: 256 chars.')
|
||||
->inject('response')
|
||||
->inject('queueForEvents')
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $platformId,
|
||||
string $name,
|
||||
string $packageIdentifierName,
|
||||
Response $response,
|
||||
QueueEvent $queueForEvents,
|
||||
Document $project,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
) {
|
||||
$platformId = ($platformId == 'unique()') ? ID::unique() : $platformId;
|
||||
|
||||
$platform = new Document([
|
||||
'$id' => $platformId,
|
||||
'$permissions' => [],
|
||||
'projectInternalId' => $project->getSequence(),
|
||||
'projectId' => $project->getId(),
|
||||
'type' => Platform::TYPE_WINDOWS,
|
||||
'name' => $name,
|
||||
'key' => $packageIdentifierName,
|
||||
'hostname' => '',
|
||||
]);
|
||||
|
||||
try {
|
||||
$platform = $authorization->skip(fn () => $dbForPlatform->createDocument('platforms', $platform));
|
||||
} catch (DuplicateException) {
|
||||
throw new Exception(Exception::PLATFORM_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$authorization->skip(fn () => $dbForPlatform->purgeCachedDocument('projects', $project->getId()));
|
||||
|
||||
$queueForEvents->setParam('platformId', $platform->getId());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($platform, Response::MODEL_PLATFORM_WINDOWS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\Platforms\Windows;
|
||||
|
||||
use Appwrite\Event\Event as QueueEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Update extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'updateProjectWindowsPlatform';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->setHttpMethod(Action::HTTP_REQUEST_METHOD_PUT)
|
||||
->setHttpPath('/v1/project/platforms/windows/:platformId')
|
||||
->desc('Update project Windows platform')
|
||||
->groups(['api', 'project'])
|
||||
->label('scope', 'project.write')
|
||||
->label('event', 'platforms.[platformId].update')
|
||||
->label('audits.event', 'project.platform.update')
|
||||
->label('audits.resource', 'project.platform/{response.$id}')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: 'platforms',
|
||||
name: 'updateWindowsPlatform',
|
||||
description: <<<EOT
|
||||
Update a Windows platform by its unique ID. Use this endpoint to update the platform's name or package identifier name.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PLATFORM_WINDOWS,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('platformId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Platform ID.', false, ['dbForPlatform'])
|
||||
->param('name', null, new Text(128), 'Platform name. Max length: 128 chars.')
|
||||
->param('packageIdentifierName', '', new Text(256), 'Windows package identifier name. Max length: 256 chars.')
|
||||
->inject('response')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('project')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
string $platformId,
|
||||
string $name,
|
||||
string $packageIdentifierName,
|
||||
Response $response,
|
||||
QueueEvent $queueForEvents,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
Document $project,
|
||||
) {
|
||||
$platform = $authorization->skip(fn () => $dbForPlatform->getDocument('platforms', $platformId));
|
||||
|
||||
if ($platform->isEmpty() || $platform->getAttribute('projectInternalId', '') !== $project->getSequence()) {
|
||||
throw new Exception(Exception::PLATFORM_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($platform->getAttribute('type', '') !== Platform::TYPE_WINDOWS) {
|
||||
throw new Exception(Exception::PLATFORM_METHOD_UNSUPPORTED);
|
||||
}
|
||||
|
||||
$updates = new Document([
|
||||
'name' => $name,
|
||||
'key' => $packageIdentifierName,
|
||||
]);
|
||||
|
||||
try {
|
||||
$platform = $authorization->skip(fn () => $dbForPlatform->updateDocument('platforms', $platform->getId(), $updates));
|
||||
} catch (Duplicate) {
|
||||
throw new Exception(Exception::PLATFORM_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$authorization->skip(fn () => $dbForPlatform->purgeCachedDocument('projects', $project->getId()));
|
||||
|
||||
$queueForEvents->setParam('platformId', $platform->getId());
|
||||
|
||||
$response->dynamic($platform, Response::MODEL_PLATFORM_WINDOWS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\Platforms;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Platforms;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Order as OrderException;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
||||
class XList extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
public static function getName()
|
||||
{
|
||||
return 'listProjectPlatforms';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
|
||||
->setHttpPath('/v1/project/platforms')
|
||||
->httpAlias('/v1/projects/:projectId/platforms')
|
||||
->desc('List project platforms')
|
||||
->groups(['api', 'project'])
|
||||
->label('scope', 'project.read')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'project',
|
||||
group: 'platforms',
|
||||
name: 'listPlatforms',
|
||||
description: <<<EOT
|
||||
Get a list of all platforms in the project. This endpoint returns an array of all platforms and their configurations.
|
||||
EOT,
|
||||
auth: [AuthType::ADMIN, AuthType::KEY],
|
||||
responses: [
|
||||
new SDKResponse(
|
||||
code: Response::STATUS_CODE_OK,
|
||||
model: Response::MODEL_PLATFORM_LIST,
|
||||
)
|
||||
]
|
||||
))
|
||||
->param('queries', [], new Platforms(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Platforms::ALLOWED_ATTRIBUTES), 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('project')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $queries
|
||||
*/
|
||||
public function action(
|
||||
array $queries,
|
||||
bool $includeTotal,
|
||||
Document $project,
|
||||
Response $response,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
foreach ($queries as $query) {
|
||||
if (\in_array($query->getAttribute(), ['bundleIdentifier', 'applicationId', 'packageIdentifierName', 'packageName'])) {
|
||||
$query->setAttribute('key');
|
||||
}
|
||||
}
|
||||
|
||||
$queries[] = Query::equal('projectInternalId', [$project->getSequence()]);
|
||||
|
||||
$cursor = Query::getCursorQueries($queries, false);
|
||||
$cursor = \reset($cursor);
|
||||
|
||||
if ($cursor !== false) {
|
||||
$validator = new Cursor();
|
||||
if (!$validator->isValid($cursor)) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
|
||||
}
|
||||
|
||||
$platformId = $cursor->getValue();
|
||||
$cursorDocument = $authorization->skip(fn () => $dbForPlatform->findOne('platforms', [
|
||||
Query::equal('$id', [$platformId]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
]));
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Platform '{$platformId}' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
try {
|
||||
$platforms = $authorization->skip(fn () => $dbForPlatform->find('platforms', $queries));
|
||||
$total = $includeTotal ? $authorization->skip(fn () => $dbForPlatform->count('platforms', $filterQueries, APP_LIMIT_COUNT)) : 0;
|
||||
} catch (OrderException $e) {
|
||||
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.");
|
||||
}
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'platforms' => $platforms,
|
||||
'total' => $total,
|
||||
]), Response::MODEL_PLATFORM_LIST);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ namespace Appwrite\Platform\Modules\Project\Http\Project\Variables;
|
||||
|
||||
use Appwrite\Event\Event as QueueEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -19,7 +18,7 @@ use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Create extends Base
|
||||
class Create extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace Appwrite\Platform\Modules\Project\Http\Project\Variables;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
@@ -16,7 +15,7 @@ use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
||||
class Delete extends Base
|
||||
class Delete extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\Variables;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -13,7 +12,7 @@ use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
||||
class Get extends Base
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace Appwrite\Platform\Modules\Project\Http\Project\Variables;
|
||||
|
||||
use Appwrite\Event\Event as QueueEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -19,7 +18,7 @@ use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Text;
|
||||
|
||||
class Update extends Base
|
||||
class Update extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace Appwrite\Platform\Modules\Project\Http\Project\Variables;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -19,7 +18,7 @@ use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
||||
class XList extends Base
|
||||
class XList extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
namespace Appwrite\Platform\Modules\Project\Services;
|
||||
|
||||
use Appwrite\Platform\Modules\Project\Http\Init;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Labels\Update as UpdateProjectLabels;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Android\Create as CreateAndroidPlatform;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Android\Update as UpdateAndroidPlatform;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Apple\Create as CreateApplePlatform;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Apple\Update as UpdateApplePlatform;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Delete as DeletePlatform;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Get as GetPlatform;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Linux\Create as CreateLinuxPlatform;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Linux\Update as UpdateLinuxPlatform;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Web\Create as CreateWebPlatform;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Web\Update as UpdateWebPlatform;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Windows\Create as CreateWindowsPlatform;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\Windows\Update as UpdateWindowsPlatform;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Platforms\XList as ListPlatforms;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Variables\Create as CreateVariable;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Variables\Delete as DeleteVariable;
|
||||
use Appwrite\Platform\Modules\Project\Http\Project\Variables\Get as GetVariable;
|
||||
@@ -20,10 +34,28 @@ class Http extends Service
|
||||
$this->addAction(Init::getName(), new Init());
|
||||
|
||||
// Project
|
||||
$this->addAction(UpdateProjectLabels::getName(), new UpdateProjectLabels());
|
||||
|
||||
// Variables
|
||||
$this->addAction(CreateVariable::getName(), new CreateVariable());
|
||||
$this->addAction(ListVariables::getName(), new ListVariables());
|
||||
$this->addAction(GetVariable::getName(), new GetVariable());
|
||||
$this->addAction(DeleteVariable::getName(), new DeleteVariable());
|
||||
$this->addAction(UpdateVariable::getName(), new UpdateVariable());
|
||||
|
||||
// Platforms
|
||||
$this->addAction(DeletePlatform::getName(), new DeletePlatform());
|
||||
$this->addAction(UpdateWebPlatform::getName(), new UpdateWebPlatform());
|
||||
$this->addAction(UpdateApplePlatform::getName(), new UpdateApplePlatform());
|
||||
$this->addAction(UpdateAndroidPlatform::getName(), new UpdateAndroidPlatform());
|
||||
$this->addAction(UpdateWindowsPlatform::getName(), new UpdateWindowsPlatform());
|
||||
$this->addAction(UpdateLinuxPlatform::getName(), new UpdateLinuxPlatform());
|
||||
$this->addAction(CreateWebPlatform::getName(), new CreateWebPlatform());
|
||||
$this->addAction(CreateApplePlatform::getName(), new CreateApplePlatform());
|
||||
$this->addAction(CreateAndroidPlatform::getName(), new CreateAndroidPlatform());
|
||||
$this->addAction(CreateWindowsPlatform::getName(), new CreateWindowsPlatform());
|
||||
$this->addAction(CreateLinuxPlatform::getName(), new CreateLinuxPlatform());
|
||||
$this->addAction(GetPlatform::getName(), new GetPlatform());
|
||||
$this->addAction(ListPlatforms::getName(), new ListPlatforms());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use Appwrite\Platform\Modules\Projects\Http\DevKeys\Get as GetDevKey;
|
||||
use Appwrite\Platform\Modules\Projects\Http\DevKeys\Update as UpdateDevKey;
|
||||
use Appwrite\Platform\Modules\Projects\Http\DevKeys\XList as ListDevKeys;
|
||||
use Appwrite\Platform\Modules\Projects\Http\Projects\Create as CreateProject;
|
||||
use Appwrite\Platform\Modules\Projects\Http\Projects\Labels\Update as UpdateProjectLabels;
|
||||
use Appwrite\Platform\Modules\Projects\Http\Projects\Team\Update as UpdateProjectTeam;
|
||||
use Appwrite\Platform\Modules\Projects\Http\Projects\Update as UpdateProject;
|
||||
use Appwrite\Platform\Modules\Projects\Http\Projects\XList as ListProjects;
|
||||
@@ -31,7 +30,6 @@ class Http extends Service
|
||||
$this->addAction(CreateProject::getName(), new CreateProject());
|
||||
$this->addAction(UpdateProject::getName(), new UpdateProject());
|
||||
$this->addAction(ListProjects::getName(), new ListProjects());
|
||||
$this->addAction(UpdateProjectLabels::getName(), new UpdateProjectLabels());
|
||||
$this->addAction(UpdateProjectTeam::getName(), new UpdateProjectTeam());
|
||||
|
||||
$this->addAction(CreateSchedule::getName(), new CreateSchedule());
|
||||
|
||||
@@ -74,10 +74,12 @@ class Update extends Action
|
||||
->inject('queueForEvents')
|
||||
->inject('store')
|
||||
->inject('proofForToken')
|
||||
->inject('domainVerification')
|
||||
->inject('cookieDomain')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(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)
|
||||
public function action(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, bool $domainVerification, ?string $cookieDomain)
|
||||
{
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
@@ -162,7 +164,7 @@ class Update extends Action
|
||||
->setProperty('secret', $secret)
|
||||
->encode();
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
if (!$domainVerification) {
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded]));
|
||||
}
|
||||
|
||||
@@ -172,7 +174,7 @@ class Update extends Action
|
||||
value: $encoded,
|
||||
expire: (new \DateTime($expire))->getTimestamp(),
|
||||
path: '/',
|
||||
domain: Config::getParam('cookieDomain'),
|
||||
domain: $cookieDomain,
|
||||
secure: ('https' === $protocol),
|
||||
httponly: true
|
||||
)
|
||||
@@ -181,7 +183,7 @@ class Update extends Action
|
||||
value: $encoded,
|
||||
expire: (new \DateTime($expire))->getTimestamp(),
|
||||
path: '/',
|
||||
domain: Config::getParam('cookieDomain'),
|
||||
domain: $cookieDomain,
|
||||
secure: ('https' === $protocol),
|
||||
httponly: true,
|
||||
sameSite: Config::getParam('cookieSamesite')
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace Appwrite\Platform\Modules\Webhooks\Http\Webhooks;
|
||||
use Appwrite\Event\Event as QueueEvent;
|
||||
use Appwrite\Event\Validator\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -25,7 +24,7 @@ use Utopia\Validator\Multiple;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
|
||||
class Create extends Base
|
||||
class Create extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace Appwrite\Platform\Modules\Webhooks\Http\Webhooks;
|
||||
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Method;
|
||||
@@ -18,7 +17,7 @@ use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
||||
class Delete extends Base
|
||||
class Delete extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace Appwrite\Platform\Modules\Webhooks\Http\Webhooks;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -16,7 +15,7 @@ use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
||||
class Get extends Base
|
||||
class Get extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace Appwrite\Platform\Modules\Webhooks\Http\Webhooks\Signature;
|
||||
|
||||
use Appwrite\Event\Event as QueueEvent;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -17,7 +16,7 @@ use Utopia\Database\Validator\UID;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
||||
class Update extends Base
|
||||
class Update extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace Appwrite\Platform\Modules\Webhooks\Http\Webhooks;
|
||||
use Appwrite\Event\Event as QueueEvent;
|
||||
use Appwrite\Event\Validator\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -24,7 +23,7 @@ use Utopia\Validator\Multiple;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
|
||||
class Update extends Base
|
||||
class Update extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace Appwrite\Platform\Modules\Webhooks\Http\Webhooks;
|
||||
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Platform\Modules\Compute\Base;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
@@ -20,7 +19,7 @@ use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
use Utopia\Validator\Boolean;
|
||||
|
||||
class XList extends Base
|
||||
class XList extends Action
|
||||
{
|
||||
use HTTP;
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Registry\Registry;
|
||||
use Utopia\Validator\Text;
|
||||
@@ -32,6 +31,7 @@ class Migrate extends Action
|
||||
->inject('getProjectDB')
|
||||
->inject('register')
|
||||
->inject('authorization')
|
||||
->inject('console')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
@@ -48,7 +48,8 @@ class Migrate extends Action
|
||||
Database $dbForPlatform,
|
||||
callable $getProjectDB,
|
||||
Registry $register,
|
||||
Authorization $authorization
|
||||
Authorization $authorization,
|
||||
Document $console
|
||||
): void {
|
||||
|
||||
if (!\array_key_exists($version, Migration::$versions)) {
|
||||
@@ -125,8 +126,6 @@ class Migrate extends Action
|
||||
Console::log('Migrated ' . ++$count . '/' . $total . ' projects...');
|
||||
});
|
||||
|
||||
$console = (new Http('UTC'))->getResource('console');
|
||||
|
||||
try {
|
||||
$migration
|
||||
->setProject($console, $getProjectDB($console), $dbForPlatform, $authorization, $getProjectDB);
|
||||
|
||||
@@ -639,29 +639,15 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
|
||||
// Checkout dev branch (or create if it doesn't exist)
|
||||
// Create or checkout dev branch from the base branch
|
||||
// This ensures dev always starts from the latest base branch,
|
||||
// avoiding history divergence caused by squash merges.
|
||||
try {
|
||||
$repo->execute('checkout', '-f', $gitBranch);
|
||||
$repo->execute('checkout', '-B', $gitBranch, $repoBranch);
|
||||
} catch (\Throwable) {
|
||||
$repo->execute('checkout', '-b', $gitBranch);
|
||||
}
|
||||
|
||||
// Fetch dev branch, or push to create it on remote
|
||||
try {
|
||||
$repo->execute('fetch', 'origin', $gitBranch, '--quiet', '--no-tags', '--depth', '1');
|
||||
} catch (\Throwable) {
|
||||
try {
|
||||
$repo->execute('push', '-u', 'origin', $gitBranch, '--quiet');
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
}
|
||||
|
||||
// Sync with remote dev branch
|
||||
try {
|
||||
$repo->execute('reset', '--hard', "origin/{$gitBranch}");
|
||||
} catch (\Throwable) {
|
||||
}
|
||||
|
||||
// Backup .github before cleaning working tree
|
||||
$githubDir = $target . '/.github';
|
||||
$githubBackup = \sys_get_temp_dir() . '/.github-backup-' . \getmypid();
|
||||
@@ -699,7 +685,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
return true;
|
||||
}
|
||||
|
||||
$repo->execute('push', '-u', 'origin', $gitBranch, '--quiet');
|
||||
$repo->execute('push', '--force-with-lease', '-u', 'origin', $gitBranch, '--quiet');
|
||||
} catch (\Throwable $e) {
|
||||
Console::warning(" Git push failed: " . $e->getMessage());
|
||||
return false;
|
||||
|
||||
@@ -61,7 +61,7 @@ class ScheduleFunctions extends ScheduleBase
|
||||
$nextDate = $cron->getNextRunDate();
|
||||
$next = DateTime::format($nextDate);
|
||||
|
||||
$currentTick = $next <= $timeFrame;
|
||||
$currentTick = $next < $timeFrame;
|
||||
|
||||
if (!$currentTick) {
|
||||
continue;
|
||||
@@ -88,7 +88,7 @@ class ScheduleFunctions extends ScheduleBase
|
||||
$scheduleKey = $delayConfig['key'];
|
||||
// Ensure schedule was not deleted
|
||||
if (!\array_key_exists($scheduleKey, $this->schedules)) {
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
$schedule = $this->schedules[$scheduleKey];
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\Network\Validator\Redirect;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Specification\Format\OpenAPI3;
|
||||
@@ -18,6 +19,9 @@ use Utopia\Config\Config;
|
||||
use Utopia\Console;
|
||||
use Utopia\Database\Adapter\MySQL;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\DI\Container;
|
||||
use Utopia\Http\Adapter\FPM\Server as FPMServer;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Http\Request as UtopiaRequest;
|
||||
use Utopia\Http\Response as UtopiaResponse;
|
||||
@@ -336,11 +340,17 @@ class Specs extends Action
|
||||
|
||||
$mocks = ($mode === 'mocks');
|
||||
|
||||
// Mock dependencies
|
||||
Http::setResource('request', fn () => $this->getRequest());
|
||||
Http::setResource('response', fn () => $response);
|
||||
Http::setResource('dbForPlatform', fn () => new Database(new MySQL(''), new Cache(new None())));
|
||||
Http::setResource('dbForProject', fn () => new Database(new MySQL(''), new Cache(new None())));
|
||||
// Mock dependencies needed by param validator injections in route definitions
|
||||
$specsContainer = new Container();
|
||||
$specsContainer->set('request', fn () => $this->getRequest());
|
||||
$specsContainer->set('response', fn () => $response);
|
||||
$specsContainer->set('dbForPlatform', fn () => new Database(new MySQL(''), new Cache(new None())));
|
||||
$specsContainer->set('dbForProject', fn () => new Database(new MySQL(''), new Cache(new None())));
|
||||
$specsContainer->set('redirectValidator', fn () => new Redirect([], []));
|
||||
$specsContainer->set('project', fn () => new Document([]));
|
||||
$specsContainer->set('passwordsDictionary', fn () => []);
|
||||
$specsContainer->set('localeCodes', fn () => \array_map(fn ($locale) => $locale['code'], Config::getParam('locale-codes', [])));
|
||||
$specsContainer->set('plan', fn () => []);
|
||||
|
||||
$platforms = static::getPlatforms();
|
||||
$authCounts = $this->getAuthCounts();
|
||||
@@ -438,7 +448,7 @@ class Specs extends Action
|
||||
}
|
||||
|
||||
$arguments = [
|
||||
new Http('UTC'),
|
||||
new Http(new FPMServer($specsContainer), 'UTC'),
|
||||
$services,
|
||||
$routes,
|
||||
$models,
|
||||
@@ -472,7 +482,12 @@ class Specs extends Action
|
||||
? $specsDir . '/' . $format . '-mocks-' . $platform . '.json'
|
||||
: $specsDir . '/' . $format . '-' . $version . '-' . $platform . '.json';
|
||||
|
||||
$parsedSpecs = $specs->parse();
|
||||
try {
|
||||
$parsedSpecs = $specs->parse();
|
||||
} catch (\RuntimeException $e) {
|
||||
throw new \RuntimeException("Spec generation failed for {$platform} ({$format}): " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
$encodedSpecs = \json_encode($parsedSpecs, JSON_PRETTY_PRINT);
|
||||
|
||||
unset($parsedSpecs);
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
|
||||
namespace Appwrite\Promises;
|
||||
|
||||
use Swoole\Coroutine;
|
||||
use Swoole\Coroutine\Channel;
|
||||
use Utopia\DI\Container;
|
||||
|
||||
class Swoole extends Promise
|
||||
{
|
||||
private const REQUEST_CONTAINER_CONTEXT_KEY = '__utopia_http_request_container';
|
||||
|
||||
public function __construct(?callable $executor = null)
|
||||
{
|
||||
parent::__construct($executor);
|
||||
@@ -16,7 +20,14 @@ class Swoole extends Promise
|
||||
callable $resolve,
|
||||
callable $reject
|
||||
): void {
|
||||
\go(function () use ($executor, $resolve, $reject) {
|
||||
$parentContainer = (Coroutine::getCid() !== -1)
|
||||
? (Coroutine::getContext()[self::REQUEST_CONTAINER_CONTEXT_KEY] ?? null)
|
||||
: null;
|
||||
|
||||
\go(function () use ($executor, $resolve, $reject, $parentContainer) {
|
||||
if ($parentContainer !== null) {
|
||||
Coroutine::getContext()[self::REQUEST_CONTAINER_CONTEXT_KEY] = new Container($parentContainer);
|
||||
}
|
||||
try {
|
||||
$executor($resolve, $reject);
|
||||
} catch (\Throwable $exception) {
|
||||
|
||||
@@ -769,6 +769,9 @@ abstract class Format
|
||||
protected function getNestedModels(Model $model, array &$usedModels): void
|
||||
{
|
||||
foreach ($model->getRules() as $rule) {
|
||||
if (($rule['hidden'] ?? false) === true) {
|
||||
continue;
|
||||
}
|
||||
if (!in_array($model->getType(), $usedModels)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -278,6 +278,18 @@ class OpenAPI3 extends Format
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_string($model)) {
|
||||
throw new \RuntimeException("Unresolved response model '{$model}' for method '{$sdk->getNamespace()}.{$sdk->getMethodName()}'. Ensure the model is registered.");
|
||||
}
|
||||
|
||||
if (\is_array($model)) {
|
||||
foreach ($model as $m) {
|
||||
if (\is_string($m)) {
|
||||
throw new \RuntimeException("Unresolved response model '{$m}' for method '{$sdk->getNamespace()}.{$sdk->getMethodName()}'. Ensure the model is registered.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!(\is_array($model)) && $model->isNone()) {
|
||||
$temp['responses'][(string)$response->getCode() ?? '500'] = [
|
||||
'description' => in_array($produces, [
|
||||
@@ -821,6 +833,10 @@ class OpenAPI3 extends Format
|
||||
}
|
||||
|
||||
foreach ($model->getRules() as $name => $rule) {
|
||||
if (($rule['hidden'] ?? false) === true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = '';
|
||||
$format = null;
|
||||
$items = null;
|
||||
|
||||
@@ -285,6 +285,18 @@ class Swagger2 extends Format
|
||||
}
|
||||
}
|
||||
|
||||
if (\is_string($model)) {
|
||||
throw new \RuntimeException("Unresolved response model '{$model}' for method '{$sdk->getNamespace()}.{$sdk->getMethodName()}'. Ensure the model is registered.");
|
||||
}
|
||||
|
||||
if (\is_array($model)) {
|
||||
foreach ($model as $m) {
|
||||
if (\is_string($m)) {
|
||||
throw new \RuntimeException("Unresolved response model '{$m}' for method '{$sdk->getNamespace()}.{$sdk->getMethodName()}'. Ensure the model is registered.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!(\is_array($model)) && $model->isNone()) {
|
||||
$temp['responses'][(string)$response->getCode() ?? '500'] = [
|
||||
'description' => in_array($produces, [
|
||||
@@ -801,6 +813,10 @@ class Swagger2 extends Format
|
||||
}
|
||||
|
||||
foreach ($model->getRules() as $name => $rule) {
|
||||
if (($rule['hidden'] ?? false) === true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = '';
|
||||
$format = null;
|
||||
$items = null;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
class Platforms extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
'type',
|
||||
'name',
|
||||
'hostname',
|
||||
'bundleIdentifier',
|
||||
'applicationId',
|
||||
'packageIdentifierName',
|
||||
'packageName',
|
||||
];
|
||||
|
||||
/**
|
||||
* Expression constructor
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('platforms', self::ALLOWED_ATTRIBUTES);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,63 @@ class V21 extends Filter
|
||||
public function parse(array $content, string $model): array
|
||||
{
|
||||
switch ($model) {
|
||||
// Web is special case compared to others, because it holds backwards compatibility logic
|
||||
case 'project.createWebPlatform':
|
||||
$content = $this->fillPlatformId($content);
|
||||
$content = $this->removePlatformStore($content);
|
||||
// Keep 'key' for backwards compatibility
|
||||
break;
|
||||
case 'project.updateWebPlatform':
|
||||
$content = $this->removePlatformStore($content);
|
||||
// Keep 'key' for backwards compatibility
|
||||
break;
|
||||
case 'project.createApplePlatform':
|
||||
$content = $this->fillPlatformId($content);
|
||||
$content = $this->removePlatformStore($content);
|
||||
$content = $this->replacePlatformKey($content, 'bundleIdentifier');
|
||||
unset($content['hostname']); // Hostname unsupported
|
||||
break;
|
||||
case 'project.updateApplePlatform':
|
||||
$content = $this->removePlatformStore($content);
|
||||
$content = $this->replacePlatformKey($content, 'bundleIdentifier');
|
||||
unset($content['hostname']); // Hostname unsupported
|
||||
break;
|
||||
case 'project.createAndroidPlatform':
|
||||
$content = $this->fillPlatformId($content);
|
||||
$content = $this->removePlatformStore($content);
|
||||
$content = $this->replacePlatformKey($content, 'applicationId');
|
||||
unset($content['hostname']); // Hostname unsupported
|
||||
break;
|
||||
case 'project.updateAndroidPlatform':
|
||||
$content = $this->removePlatformStore($content);
|
||||
$content = $this->replacePlatformKey($content, 'applicationId');
|
||||
unset($content['hostname']); // Hostname unsupported
|
||||
break;
|
||||
case 'project.createWindowsPlatform':
|
||||
$content = $this->fillPlatformId($content);
|
||||
$content = $this->removePlatformStore($content);
|
||||
$content = $this->replacePlatformKey($content, 'packageIdentifierName');
|
||||
unset($content['hostname']); // Hostname unsupported
|
||||
break;
|
||||
case 'project.updateWindowsPlatform':
|
||||
$content = $this->removePlatformStore($content);
|
||||
$content = $this->replacePlatformKey($content, 'packageIdentifierName');
|
||||
unset($content['hostname']); // Hostname unsupported
|
||||
break;
|
||||
case 'project.createLinuxPlatform':
|
||||
$content = $this->fillPlatformId($content);
|
||||
$content = $this->removePlatformStore($content);
|
||||
$content = $this->replacePlatformKey($content, 'packageName');
|
||||
unset($content['hostname']); // Hostname unsupported
|
||||
break;
|
||||
case 'project.updateLinuxPlatform':
|
||||
$content = $this->removePlatformStore($content);
|
||||
$content = $this->replacePlatformKey($content, 'packageName');
|
||||
unset($content['hostname']); // Hostname unsupported
|
||||
break;
|
||||
case 'project.listPlatforms':
|
||||
$content = $this->preservePlatformsQueries($content);
|
||||
break;
|
||||
case 'webhooks.create':
|
||||
$content = $this->fillWebhookid($content);
|
||||
break;
|
||||
@@ -79,4 +136,33 @@ class V21 extends Filter
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function fillPlatformId(array $content): array
|
||||
{
|
||||
$content['platformId'] = $content['platformId'] ?? 'unique()';
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function replacePlatformKey(array $content, string $newKey): array
|
||||
{
|
||||
$content[$newKey] = $content[$newKey] ?? $content['key'] ?? null;
|
||||
unset($content['key']);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function removePlatformStore(array $content): array
|
||||
{
|
||||
unset($content['store']);
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function preservePlatformsQueries(array $content): array
|
||||
{
|
||||
$content['queries'] = $content['queries'] ?? [
|
||||
Query::limit(5000)
|
||||
];
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +256,11 @@ class Response extends SwooleResponse
|
||||
public const MODEL_MOCK_NUMBER = 'mockNumber';
|
||||
public const MODEL_AUTH_PROVIDER = 'authProvider';
|
||||
public const MODEL_AUTH_PROVIDER_LIST = 'authProviderList';
|
||||
public const MODEL_PLATFORM = 'platform';
|
||||
public const MODEL_PLATFORM_APPLE = 'platformApple';
|
||||
public const MODEL_PLATFORM_ANDROID = 'platformAndroid';
|
||||
public const MODEL_PLATFORM_WINDOWS = 'platformWindows';
|
||||
public const MODEL_PLATFORM_LINUX = 'platformLinux';
|
||||
public const MODEL_PLATFORM_WEB = 'platformWeb';
|
||||
public const MODEL_PLATFORM_LIST = 'platformList';
|
||||
public const MODEL_VARIABLE = 'variable';
|
||||
public const MODEL_VARIABLE_LIST = 'variableList';
|
||||
@@ -476,7 +480,13 @@ class Response extends SwooleResponse
|
||||
foreach ($rule['type'] as $type) {
|
||||
$condition = false;
|
||||
foreach ($this->getModel($type)->conditions as $attribute => $val) {
|
||||
$condition = $item->getAttribute($attribute) === $val;
|
||||
|
||||
if (\is_array($val)) {
|
||||
$condition = \in_array($item->getAttribute($attribute), $val);
|
||||
} else {
|
||||
$condition = $item->getAttribute($attribute) === $val;
|
||||
}
|
||||
|
||||
if (!$condition) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,23 @@ class V21 extends Filter
|
||||
public function parse(array $content, string $model): array
|
||||
{
|
||||
return match ($model) {
|
||||
// Web is special case, it has backwards compatibility
|
||||
Response::MODEL_PLATFORM_WEB => $this->parsePlatform($content),
|
||||
Response::MODEL_PLATFORM_APPLE => $this->parsePlatform($content),
|
||||
Response::MODEL_PLATFORM_ANDROID => $this->parsePlatform($content),
|
||||
Response::MODEL_PLATFORM_WINDOWS => $this->parsePlatform($content),
|
||||
Response::MODEL_PLATFORM_LINUX => $this->parsePlatform($content),
|
||||
Response::MODEL_PLATFORM_LIST => $this->handleList(
|
||||
$content,
|
||||
"platforms",
|
||||
fn ($item) => $this->parsePlatform($item),
|
||||
),
|
||||
Response::MODEL_PROJECT => $this->parseProjectForPlatform($content),
|
||||
Response::MODEL_PROJECT_LIST => $this->handleList(
|
||||
$content,
|
||||
"projects",
|
||||
fn ($item) => $this->parseProjectForPlatform($item),
|
||||
),
|
||||
Response::MODEL_USER => $this->parseUser($content),
|
||||
Response::MODEL_USER_LIST => $this->handleList(
|
||||
$content,
|
||||
@@ -107,4 +124,34 @@ class V21 extends Filter
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function parseProjectForPlatform(array $content): array
|
||||
{
|
||||
// Parse platforms under project, since it's a subquery
|
||||
$content['platforms'] = \array_map(fn ($item) => $this->parsePlatform($item), $content['platforms']);
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function parsePlatform(array $content): array
|
||||
{
|
||||
// Map platform-specific identifier fields back to 'key'
|
||||
$content['key'] =
|
||||
($content['bundleIdentifier'] ?? '')
|
||||
?: ($content['applicationId'] ?? '')
|
||||
?: ($content['packageIdentifierName'] ?? '')
|
||||
?: ($content['packageName'] ?? '')
|
||||
?: ($content['key'] ?? '')
|
||||
?: '';
|
||||
|
||||
unset($content['bundleIdentifier']);
|
||||
unset($content['applicationId']);
|
||||
unset($content['packageIdentifierName']);
|
||||
unset($content['packageName']);
|
||||
|
||||
// Restore fields removed in v1.9
|
||||
$content['store'] = $content['store'] ?? '';
|
||||
$content['hostname'] = $content['hostname'] ?? '';
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class Platform extends Model
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $public = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('$id', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Platform ID.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Platform creation date in ISO 8601 format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Platform update date in ISO 8601 format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Platform name.',
|
||||
'default' => '',
|
||||
'example' => 'My Web App',
|
||||
])
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_ENUM,
|
||||
'description' => 'Platform type. Possible values are: web, flutter-web, flutter-ios, flutter-android, flutter-linux, flutter-macos, flutter-windows, apple-ios, apple-macos, apple-watchos, apple-tvos, android, unity, react-native-ios, react-native-android.',
|
||||
'default' => '',
|
||||
'example' => 'web',
|
||||
'enum' => ['web', 'flutter-web', 'flutter-ios', 'flutter-android', 'flutter-linux', 'flutter-macos', 'flutter-windows', 'apple-ios', 'apple-macos', 'apple-watchos', 'apple-tvos', 'android', 'unity', 'react-native-ios', 'react-native-android'],
|
||||
])
|
||||
->addRule('key', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Platform Key. iOS bundle ID or Android package name. Empty string for other platforms.',
|
||||
'default' => '',
|
||||
'example' => 'com.company.appname',
|
||||
])
|
||||
->addRule('store', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'App store or Google Play store ID.',
|
||||
'example' => '',
|
||||
])
|
||||
->addRule('hostname', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Web app hostname. Empty string for other platforms.',
|
||||
'default' => '',
|
||||
'example' => 'app.example.com',
|
||||
])
|
||||
->addRule('httpUser', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'HTTP basic authentication username.',
|
||||
'default' => '',
|
||||
'example' => 'username',
|
||||
])
|
||||
->addRule('httpPass', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'HTTP basic authentication password.',
|
||||
'default' => '',
|
||||
'example' => 'password',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Platform';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_PLATFORM;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class PlatformAndroid extends PlatformBase
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->conditions = [
|
||||
'type' => Platform::TYPE_ANDROID,
|
||||
];
|
||||
|
||||
parent::__construct();
|
||||
|
||||
$this
|
||||
->addRule('applicationId', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Android application ID.',
|
||||
'default' => '',
|
||||
'example' => 'com.company.appname',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Platform Android';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_PLATFORM_ANDROID;
|
||||
}
|
||||
|
||||
public function filter(Document $document): Document
|
||||
{
|
||||
// DB level: 'key'
|
||||
// API level: 'applicationId'
|
||||
$document->setAttribute('applicationId', $document->getAttribute('key', null));
|
||||
$document->removeAttribute('key');
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class PlatformApple extends PlatformBase
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->conditions = [
|
||||
'type' => Platform::TYPE_APPLE,
|
||||
];
|
||||
|
||||
parent::__construct();
|
||||
|
||||
$this
|
||||
->addRule('bundleIdentifier', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Apple bundle identifier.',
|
||||
'default' => '',
|
||||
'example' => 'com.company.appname',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Platform Apple';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_PLATFORM_APPLE;
|
||||
}
|
||||
|
||||
public function filter(Document $document): Document
|
||||
{
|
||||
// DB level: 'key'
|
||||
// API level: 'bundleIdentifier'
|
||||
$document->setAttribute('bundleIdentifier', $document->getAttribute('key', null));
|
||||
$document->removeAttribute('key');
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
abstract class PlatformBase extends Model
|
||||
{
|
||||
public function getSupportedTypes(): array
|
||||
{
|
||||
return [
|
||||
Platform::TYPE_WINDOWS,
|
||||
Platform::TYPE_APPLE,
|
||||
Platform::TYPE_ANDROID,
|
||||
Platform::TYPE_LINUX,
|
||||
Platform::TYPE_WEB,
|
||||
];
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('$id', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Platform ID.',
|
||||
'default' => '',
|
||||
'example' => '5e5ea5c16897e',
|
||||
])
|
||||
->addRule('$createdAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Platform creation date in ISO 8601 format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('$updatedAt', [
|
||||
'type' => self::TYPE_DATETIME,
|
||||
'description' => 'Platform update date in ISO 8601 format.',
|
||||
'default' => '',
|
||||
'example' => self::TYPE_DATETIME_EXAMPLE,
|
||||
])
|
||||
->addRule('name', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Platform name.',
|
||||
'default' => '',
|
||||
'example' => 'My Web App',
|
||||
])
|
||||
->addRule('type', [
|
||||
'type' => self::TYPE_ENUM,
|
||||
'description' => 'Platform type. Possible values are: ' . implode(', ', self::getSupportedTypes()) . '.',
|
||||
'default' => '',
|
||||
'example' => Platform::TYPE_WEB,
|
||||
'enum' => self::getSupportedTypes(),
|
||||
])
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class PlatformLinux extends PlatformBase
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->conditions = [
|
||||
'type' => Platform::TYPE_LINUX,
|
||||
];
|
||||
|
||||
parent::__construct();
|
||||
|
||||
$this
|
||||
->addRule('packageName', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Linux package name.',
|
||||
'default' => '',
|
||||
'example' => 'com.company.appname',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Platform Linux';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_PLATFORM_LINUX;
|
||||
}
|
||||
|
||||
public function filter(Document $document): Document
|
||||
{
|
||||
// DB level: 'key'
|
||||
// API level: 'packageName'
|
||||
$document->setAttribute('packageName', $document->getAttribute('key', null));
|
||||
$document->removeAttribute('key');
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class PlatformList extends Model
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->addRule('total', [
|
||||
'type' => self::TYPE_INTEGER,
|
||||
'description' => 'Total number of platforms in the given project.',
|
||||
'default' => 0,
|
||||
'example' => 5,
|
||||
])
|
||||
->addRule('platforms', [
|
||||
'type' => [
|
||||
Response::MODEL_PLATFORM_WEB,
|
||||
Response::MODEL_PLATFORM_APPLE,
|
||||
Response::MODEL_PLATFORM_ANDROID,
|
||||
Response::MODEL_PLATFORM_WINDOWS,
|
||||
Response::MODEL_PLATFORM_LINUX,
|
||||
],
|
||||
'description' => 'List of platforms.',
|
||||
'default' => [],
|
||||
'array' => true
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Platforms List';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_PLATFORM_LIST;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\Utopia\Response;
|
||||
|
||||
class PlatformWeb extends PlatformBase
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->conditions = [
|
||||
'type' => [
|
||||
Platform::TYPE_WEB,
|
||||
// Backwards compatibility
|
||||
'flutter-web',
|
||||
'unity',
|
||||
'flutter-macos',
|
||||
'flutter-ios',
|
||||
'react-native-ios',
|
||||
'apple-ios',
|
||||
'apple-macos',
|
||||
'apple-watchos',
|
||||
'apple-tvos',
|
||||
'flutter-android',
|
||||
'react-native-android',
|
||||
'flutter-windows',
|
||||
'flutter-linux',
|
||||
],
|
||||
];
|
||||
|
||||
parent::__construct();
|
||||
|
||||
$this
|
||||
->addRule('hostname', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Web app hostname. Empty string for other platforms.',
|
||||
'default' => '',
|
||||
'example' => 'app.example.com',
|
||||
])
|
||||
// Backwards compatibility
|
||||
->addRule('key', [
|
||||
'hidden' => true,
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Deprecated for old versions using alias endpoint to create universal platform.',
|
||||
'default' => '',
|
||||
'example' => 'com.company.appname',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Platform Web';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_PLATFORM_WEB;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Response\Model;
|
||||
|
||||
use Appwrite\Network\Platform;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\Database\Document;
|
||||
|
||||
class PlatformWindows extends PlatformBase
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->conditions = [
|
||||
'type' => Platform::TYPE_WINDOWS,
|
||||
];
|
||||
|
||||
parent::__construct();
|
||||
|
||||
$this
|
||||
->addRule('packageIdentifierName', [
|
||||
'type' => self::TYPE_STRING,
|
||||
'description' => 'Windows package identifier name.',
|
||||
'default' => '',
|
||||
'example' => 'com.company.appname',
|
||||
])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Platform Windows';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return Response::MODEL_PLATFORM_WINDOWS;
|
||||
}
|
||||
|
||||
public function filter(Document $document): Document
|
||||
{
|
||||
// DB level: 'key'
|
||||
// API level: 'packageIdentifierName'
|
||||
$document->setAttribute('packageIdentifierName', $document->getAttribute('key', null));
|
||||
$document->removeAttribute('key');
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,6 @@ use Utopia\Database\Document;
|
||||
|
||||
class Project extends Model
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $public = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
@@ -200,7 +195,13 @@ class Project extends Model
|
||||
'array' => true,
|
||||
])
|
||||
->addRule('platforms', [
|
||||
'type' => Response::MODEL_PLATFORM,
|
||||
'type' => [
|
||||
Response::MODEL_PLATFORM_WEB,
|
||||
Response::MODEL_PLATFORM_APPLE,
|
||||
Response::MODEL_PLATFORM_ANDROID,
|
||||
Response::MODEL_PLATFORM_WINDOWS,
|
||||
Response::MODEL_PLATFORM_LINUX,
|
||||
],
|
||||
'description' => 'List of Platforms.',
|
||||
'default' => [],
|
||||
'example' => new \stdClass(),
|
||||
|
||||
@@ -7,11 +7,6 @@ use Appwrite\Utopia\Response\Model;
|
||||
|
||||
class Webhook extends Model
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected bool $public = true;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
|
||||
@@ -1324,8 +1324,8 @@ class UsageTest extends Scope
|
||||
$this->assertEquals($requestsTotal, $response['body']['requests'][array_key_last($response['body']['requests'])]['value']);
|
||||
$this->validateDates($response['body']['requests']);
|
||||
// vectordbTotal should reflect only VectorsDB instances, not relational databases.
|
||||
$this->assertEquals($vectordbTotal, $response['body']['vectordbDatabasesTotal']);
|
||||
$this->assertEquals($documentsTotal, $response['body']['vectordbDocumentsTotal']);
|
||||
$this->assertEquals($vectordbTotal, $response['body']['vectorsdbDatabasesTotal']);
|
||||
$this->assertEquals($documentsTotal, $response['body']['vectorsdbDocumentsTotal']);
|
||||
});
|
||||
|
||||
$response = $this->client->call(
|
||||
|
||||
@@ -4150,4 +4150,178 @@ class AccountCustomClientTest extends Scope
|
||||
|
||||
$this->assertEquals(401, $verification3['headers']['status-code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a new email/password session is immediately usable even when
|
||||
* a concurrent request re-populates the user cache between the cache purge
|
||||
* and session creation.
|
||||
*
|
||||
* Regression test for: purging the user cache BEFORE persisting the session
|
||||
* allows a concurrent request (from a different Swoole worker) to re-cache
|
||||
* a stale user document that lacks the new session, causing sessionVerify
|
||||
* to fail with 401 on subsequent requests using the new session.
|
||||
*/
|
||||
public function testEmailPasswordSessionNotCorruptedByConcurrentRequests(): void
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
$endpoint = $this->client->getEndpoint();
|
||||
|
||||
$email = uniqid('race_', true) . getmypid() . '@localhost.test';
|
||||
$password = 'password123!';
|
||||
|
||||
// Create user
|
||||
$response = $this->client->call(Client::METHOD_POST, '/account', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], [
|
||||
'userId' => ID::unique(),
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
'name' => 'Race Test User',
|
||||
]);
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
|
||||
// Login to get session A
|
||||
$responseA = $this->client->call(Client::METHOD_POST, '/account/sessions/email', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
], [
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]);
|
||||
$this->assertEquals(201, $responseA['headers']['status-code']);
|
||||
$sessionA = $responseA['cookies']['a_session_' . $projectId];
|
||||
|
||||
// Verify session A works
|
||||
$verifyA = $this->client->call(Client::METHOD_GET, '/account', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'cookie' => 'a_session_' . $projectId . '=' . $sessionA,
|
||||
]);
|
||||
$this->assertEquals(200, $verifyA['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Race condition scenario:
|
||||
* 1. Start login B via curl_multi (non-blocking)
|
||||
* 2. Drive the transfer for ~150ms so login B reaches purgeCachedDocument
|
||||
* (findOne ~15ms + Argon2 hash verify ~60ms + middleware overhead)
|
||||
* 3. THEN add GET requests to curl_multi - these hit different workers and
|
||||
* re-cache a stale user document (without session B) during the window
|
||||
* between purgeCachedDocument and createDocument
|
||||
* 4. After all complete, verify session B is usable
|
||||
*/
|
||||
for ($attempt = 0; $attempt < 5; $attempt++) {
|
||||
$loginCookies = [];
|
||||
|
||||
$multi = curl_multi_init();
|
||||
|
||||
// Start login B first (alone)
|
||||
$loginHandle = curl_init("{$endpoint}/account/sessions/email");
|
||||
curl_setopt_array($loginHandle, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'origin: http://localhost',
|
||||
'content-type: application/json',
|
||||
"x-appwrite-project: {$projectId}",
|
||||
],
|
||||
CURLOPT_POSTFIELDS => \json_encode([
|
||||
'email' => $email,
|
||||
'password' => $password,
|
||||
]),
|
||||
CURLOPT_HEADERFUNCTION => function ($curl, $header) use (&$loginCookies) {
|
||||
if (\stripos($header, 'set-cookie:') === 0) {
|
||||
$cookiePart = \trim(\substr($header, 11));
|
||||
$eqPos = \strpos($cookiePart, '=');
|
||||
if ($eqPos !== false) {
|
||||
$name = \substr($cookiePart, 0, $eqPos);
|
||||
$rest = \substr($cookiePart, $eqPos + 1);
|
||||
$semiPos = \strpos($rest, ';');
|
||||
$loginCookies[$name] = $semiPos !== false
|
||||
? \substr($rest, 0, $semiPos)
|
||||
: $rest;
|
||||
}
|
||||
}
|
||||
return \strlen($header);
|
||||
},
|
||||
]);
|
||||
curl_multi_add_handle($multi, $loginHandle);
|
||||
|
||||
// Drive the login transfer forward and wait for the server to start
|
||||
// processing the login (past hash verification + cache purge).
|
||||
$deadline = \microtime(true) + 0.15; // 150ms
|
||||
do {
|
||||
curl_multi_exec($multi, $active);
|
||||
curl_multi_select($multi, 0.005);
|
||||
} while (\microtime(true) < $deadline && $active);
|
||||
|
||||
// NOW add GET requests - they arrive after the cache purge
|
||||
// but before session creation (which is delayed by the usleep or I/O).
|
||||
$getHandles = [];
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$gh = curl_init("{$endpoint}/account");
|
||||
curl_setopt_array($gh, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'origin: http://localhost',
|
||||
'content-type: application/json',
|
||||
"x-appwrite-project: {$projectId}",
|
||||
"cookie: a_session_{$projectId}={$sessionA}",
|
||||
],
|
||||
]);
|
||||
curl_multi_add_handle($multi, $gh);
|
||||
$getHandles[] = $gh;
|
||||
}
|
||||
|
||||
// Drive all to completion
|
||||
do {
|
||||
$status = curl_multi_exec($multi, $active);
|
||||
if ($active) {
|
||||
curl_multi_select($multi, 0.05);
|
||||
}
|
||||
} while ($active && $status === CURLM_OK);
|
||||
|
||||
$loginStatus = curl_getinfo($loginHandle, CURLINFO_HTTP_CODE);
|
||||
|
||||
curl_multi_remove_handle($multi, $loginHandle);
|
||||
curl_close($loginHandle);
|
||||
foreach ($getHandles as $gh) {
|
||||
curl_multi_remove_handle($multi, $gh);
|
||||
curl_close($gh);
|
||||
}
|
||||
curl_multi_close($multi);
|
||||
|
||||
$this->assertEquals(201, $loginStatus, 'Login for session B should succeed');
|
||||
|
||||
$sessionBCookie = $loginCookies["a_session_{$projectId}"] ?? null;
|
||||
$this->assertNotNull($sessionBCookie, 'Session B cookie should be set');
|
||||
|
||||
// THE CRITICAL CHECK: verify session B is usable immediately
|
||||
$verifyB = $this->client->call(Client::METHOD_GET, '/account', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'cookie' => "a_session_{$projectId}={$sessionBCookie}",
|
||||
]);
|
||||
|
||||
$this->assertEquals(
|
||||
200,
|
||||
$verifyB['headers']['status-code'],
|
||||
'Session B must be immediately usable after login. '
|
||||
. 'A 401 here means a stale user cache (without the new session) was served. '
|
||||
. 'The fix is to create the session document BEFORE purging the user cache.'
|
||||
);
|
||||
|
||||
// Clean up session B for next iteration
|
||||
$this->client->call(Client::METHOD_DELETE, '/account/sessions/current', [
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'cookie' => "a_session_{$projectId}={$sessionBCookie}",
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Project;
|
||||
|
||||
trait LabelsBase
|
||||
{
|
||||
// Update labels tests
|
||||
|
||||
public function testUpdateLabels(): void
|
||||
{
|
||||
$response = $this->updateLabels(['frontend', 'backend']);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertIsArray($response['body']['labels']);
|
||||
$this->assertCount(2, $response['body']['labels']);
|
||||
$this->assertContains('frontend', $response['body']['labels']);
|
||||
$this->assertContains('backend', $response['body']['labels']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateLabels([]);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsReplace(): void
|
||||
{
|
||||
$response = $this->updateLabels(['alpha', 'beta']);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertCount(2, $response['body']['labels']);
|
||||
$this->assertContains('alpha', $response['body']['labels']);
|
||||
$this->assertContains('beta', $response['body']['labels']);
|
||||
|
||||
// Replace with new labels
|
||||
$response = $this->updateLabels(['gamma', 'delta', 'epsilon']);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertCount(3, $response['body']['labels']);
|
||||
$this->assertContains('gamma', $response['body']['labels']);
|
||||
$this->assertContains('delta', $response['body']['labels']);
|
||||
$this->assertContains('epsilon', $response['body']['labels']);
|
||||
$this->assertNotContains('alpha', $response['body']['labels']);
|
||||
$this->assertNotContains('beta', $response['body']['labels']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateLabels([]);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsEmpty(): void
|
||||
{
|
||||
// Set some labels first
|
||||
$response = $this->updateLabels(['toRemove']);
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertCount(1, $response['body']['labels']);
|
||||
|
||||
// Clear all labels
|
||||
$response = $this->updateLabels([]);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertIsArray($response['body']['labels']);
|
||||
$this->assertCount(0, $response['body']['labels']);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsDeduplicated(): void
|
||||
{
|
||||
$response = $this->updateLabels(['duplicate', 'duplicate', 'unique']);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertCount(2, $response['body']['labels']);
|
||||
$this->assertContains('duplicate', $response['body']['labels']);
|
||||
$this->assertContains('unique', $response['body']['labels']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateLabels([]);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsSingleLabel(): void
|
||||
{
|
||||
$response = $this->updateLabels(['solo']);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertCount(1, $response['body']['labels']);
|
||||
$this->assertContains('solo', $response['body']['labels']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateLabels([]);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsWithoutAuthentication(): void
|
||||
{
|
||||
$response = $this->updateLabels(['unauthorized'], false);
|
||||
|
||||
$this->assertSame(401, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsInvalidLabelTooLong(): void
|
||||
{
|
||||
$response = $this->updateLabels([str_repeat('a', 37)]);
|
||||
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsInvalidLabelCharacters(): void
|
||||
{
|
||||
$response = $this->updateLabels(['invalid-label!']);
|
||||
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsAlphanumericOnly(): void
|
||||
{
|
||||
$response = $this->updateLabels(['ABC123', 'lowercase', 'UPPERCASE', '0123456789']);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertCount(4, $response['body']['labels']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateLabels([]);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsMaxLength(): void
|
||||
{
|
||||
$label = str_repeat('a', 36);
|
||||
$response = $this->updateLabels([$label]);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertCount(1, $response['body']['labels']);
|
||||
$this->assertContains($label, $response['body']['labels']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateLabels([]);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsIdempotent(): void
|
||||
{
|
||||
$labels = ['stable', 'production'];
|
||||
|
||||
$first = $this->updateLabels($labels);
|
||||
$this->assertSame(200, $first['headers']['status-code']);
|
||||
|
||||
$second = $this->updateLabels($labels);
|
||||
$this->assertSame(200, $second['headers']['status-code']);
|
||||
|
||||
$this->assertSame($first['body']['labels'], $second['body']['labels']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateLabels([]);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsDeduplicatedOrder(): void
|
||||
{
|
||||
$response = $this->updateLabels(['b', 'a', 'b']);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertCount(2, $response['body']['labels']);
|
||||
$this->assertSame('b', $response['body']['labels'][0]);
|
||||
$this->assertSame('a', $response['body']['labels'][1]);
|
||||
|
||||
// Cleanup
|
||||
$this->updateLabels([]);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsInvalidHyphen(): void
|
||||
{
|
||||
$response = $this->updateLabels(['my-label']);
|
||||
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsInvalidUnderscore(): void
|
||||
{
|
||||
$response = $this->updateLabels(['my_label']);
|
||||
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsInvalidSpace(): void
|
||||
{
|
||||
$response = $this->updateLabels(['my label']);
|
||||
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsInvalidEmptyString(): void
|
||||
{
|
||||
$response = $this->updateLabels(['']);
|
||||
|
||||
$this->assertSame(400, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testUpdateLabelsResponseModel(): void
|
||||
{
|
||||
$response = $this->updateLabels(['test']);
|
||||
|
||||
$this->assertSame(200, $response['headers']['status-code']);
|
||||
$this->assertArrayHasKey('$id', $response['body']);
|
||||
$this->assertArrayHasKey('name', $response['body']);
|
||||
$this->assertArrayHasKey('labels', $response['body']);
|
||||
$this->assertIsArray($response['body']['labels']);
|
||||
$this->assertContains('test', $response['body']['labels']);
|
||||
|
||||
// Cleanup
|
||||
$this->updateLabels([]);
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
/**
|
||||
* @param array<string> $labels
|
||||
*/
|
||||
protected function updateLabels(array $labels, bool $authenticated = true): mixed
|
||||
{
|
||||
$headers = [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
];
|
||||
|
||||
if ($authenticated) {
|
||||
$headers = array_merge($headers, $this->getHeaders());
|
||||
}
|
||||
|
||||
return $this->client->call(\Tests\E2E\Client::METHOD_PUT, '/project/labels', $headers, [
|
||||
'labels' => $labels,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Project;
|
||||
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideConsole;
|
||||
|
||||
class LabelsConsoleClientTest extends Scope
|
||||
{
|
||||
use LabelsBase;
|
||||
use ProjectCustom;
|
||||
use SideConsole;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Project;
|
||||
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
|
||||
class LabelsCustomServerTest extends Scope
|
||||
{
|
||||
use LabelsBase;
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Project;
|
||||
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideConsole;
|
||||
|
||||
class PlatformsConsoleClientTest extends Scope
|
||||
{
|
||||
use PlatformsBase;
|
||||
use ProjectCustom;
|
||||
use SideConsole;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\E2E\Services\Project;
|
||||
|
||||
use Tests\E2E\Scopes\ProjectCustom;
|
||||
use Tests\E2E\Scopes\Scope;
|
||||
use Tests\E2E\Scopes\SideServer;
|
||||
|
||||
class PlatformsCustomServerTest extends Scope
|
||||
{
|
||||
use PlatformsBase;
|
||||
use ProjectCustom;
|
||||
use SideServer;
|
||||
}
|
||||
@@ -151,6 +151,7 @@ trait ProjectsBase
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'web',
|
||||
'name' => 'Web App',
|
||||
@@ -163,6 +164,7 @@ trait ProjectsBase
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'flutter-ios',
|
||||
'name' => 'Flutter App (iOS)',
|
||||
@@ -175,6 +177,7 @@ trait ProjectsBase
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'flutter-android',
|
||||
'name' => 'Flutter App (Android)',
|
||||
@@ -187,6 +190,7 @@ trait ProjectsBase
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'flutter-web',
|
||||
'name' => 'Flutter App (Web)',
|
||||
@@ -199,6 +203,7 @@ trait ProjectsBase
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'apple-ios',
|
||||
'name' => 'iOS App',
|
||||
@@ -211,6 +216,7 @@ trait ProjectsBase
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'apple-macos',
|
||||
'name' => 'macOS App',
|
||||
@@ -223,6 +229,7 @@ trait ProjectsBase
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'apple-watchos',
|
||||
'name' => 'watchOS App',
|
||||
@@ -235,6 +242,7 @@ trait ProjectsBase
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'apple-tvos',
|
||||
'name' => 'tvOS App',
|
||||
|
||||
@@ -3754,6 +3754,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'web',
|
||||
'name' => 'Web App',
|
||||
@@ -3773,6 +3774,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'flutter-ios',
|
||||
'name' => 'Flutter App (iOS)',
|
||||
@@ -3781,7 +3783,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals('flutter-ios', $response['body']['type']);
|
||||
$this->assertEquals('apple', $response['body']['type']); // Origianlly flutter-ios, but new version renames
|
||||
$this->assertEquals('Flutter App (iOS)', $response['body']['name']);
|
||||
$this->assertEquals('com.example.ios', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -3792,6 +3794,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'flutter-android',
|
||||
'name' => 'Flutter App (Android)',
|
||||
@@ -3800,7 +3803,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals('flutter-android', $response['body']['type']);
|
||||
$this->assertEquals('android', $response['body']['type']); // Origianlly flutter-android, but new version renames
|
||||
$this->assertEquals('Flutter App (Android)', $response['body']['name']);
|
||||
$this->assertEquals('com.example.android', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -3811,6 +3814,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'flutter-web',
|
||||
'name' => 'Flutter App (Web)',
|
||||
@@ -3819,7 +3823,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals('flutter-web', $response['body']['type']);
|
||||
$this->assertEquals('web', $response['body']['type']); // Origianlly flutter-web, but new version renames
|
||||
$this->assertEquals('Flutter App (Web)', $response['body']['name']);
|
||||
$this->assertEquals('', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -3830,6 +3834,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'apple-ios',
|
||||
'name' => 'iOS App',
|
||||
@@ -3838,7 +3843,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals('apple-ios', $response['body']['type']);
|
||||
$this->assertEquals('apple', $response['body']['type']); // Origianlly apple-ios, but new version renames
|
||||
$this->assertEquals('iOS App', $response['body']['name']);
|
||||
$this->assertEquals('com.example.ios', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -3849,6 +3854,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'apple-macos',
|
||||
'name' => 'macOS App',
|
||||
@@ -3857,7 +3863,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals('apple-macos', $response['body']['type']);
|
||||
$this->assertEquals('apple', $response['body']['type']); // Origianlly apple-macos, but new version renames
|
||||
$this->assertEquals('macOS App', $response['body']['name']);
|
||||
$this->assertEquals('com.example.macos', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -3868,6 +3874,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'apple-watchos',
|
||||
'name' => 'watchOS App',
|
||||
@@ -3876,7 +3883,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals('apple-watchos', $response['body']['type']);
|
||||
$this->assertEquals('apple', $response['body']['type']); // Origianlly apple-watchos, but new version renames
|
||||
$this->assertEquals('watchOS App', $response['body']['name']);
|
||||
$this->assertEquals('com.example.watchos', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -3887,6 +3894,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'apple-tvos',
|
||||
'name' => 'tvOS App',
|
||||
@@ -3895,7 +3903,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals('apple-tvos', $response['body']['type']);
|
||||
$this->assertEquals('apple', $response['body']['type']); // Origianlly apple-tvos, but new version renames
|
||||
$this->assertEquals('tvOS App', $response['body']['name']);
|
||||
$this->assertEquals('com.example.tvos', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -3909,6 +3917,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'unknown',
|
||||
'name' => 'Web App',
|
||||
@@ -3927,6 +3936,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
@@ -3950,6 +3960,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformWebId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
@@ -3966,12 +3977,13 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformFultteriOSId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals($platformFultteriOSId, $response['body']['$id']);
|
||||
$this->assertEquals('flutter-ios', $response['body']['type']);
|
||||
$this->assertEquals('apple', $response['body']['type']);
|
||||
$this->assertEquals('Flutter App (iOS)', $response['body']['name']);
|
||||
$this->assertEquals('com.example.ios', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -3982,12 +3994,13 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformFultterAndroidId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals($platformFultterAndroidId, $response['body']['$id']);
|
||||
$this->assertEquals('flutter-android', $response['body']['type']);
|
||||
$this->assertEquals('android', $response['body']['type']);
|
||||
$this->assertEquals('Flutter App (Android)', $response['body']['name']);
|
||||
$this->assertEquals('com.example.android', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -3998,12 +4011,13 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformFultterWebId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals($platformFultterWebId, $response['body']['$id']);
|
||||
$this->assertEquals('flutter-web', $response['body']['type']);
|
||||
$this->assertEquals('web', $response['body']['type']);
|
||||
$this->assertEquals('Flutter App (Web)', $response['body']['name']);
|
||||
$this->assertEquals('', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -4014,12 +4028,13 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformAppleIosId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals($platformAppleIosId, $response['body']['$id']);
|
||||
$this->assertEquals('apple-ios', $response['body']['type']);
|
||||
$this->assertEquals('apple', $response['body']['type']);
|
||||
$this->assertEquals('iOS App', $response['body']['name']);
|
||||
$this->assertEquals('com.example.ios', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -4030,12 +4045,13 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformAppleMacOsId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals($platformAppleMacOsId, $response['body']['$id']);
|
||||
$this->assertEquals('apple-macos', $response['body']['type']);
|
||||
$this->assertEquals('apple', $response['body']['type']);
|
||||
$this->assertEquals('macOS App', $response['body']['name']);
|
||||
$this->assertEquals('com.example.macos', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -4046,12 +4062,13 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformAppleWatchOsId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals($platformAppleWatchOsId, $response['body']['$id']);
|
||||
$this->assertEquals('apple-watchos', $response['body']['type']);
|
||||
$this->assertEquals('apple', $response['body']['type']);
|
||||
$this->assertEquals('watchOS App', $response['body']['name']);
|
||||
$this->assertEquals('com.example.watchos', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -4062,12 +4079,13 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformAppleTvOsId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals($platformAppleTvOsId, $response['body']['$id']);
|
||||
$this->assertEquals('apple-tvos', $response['body']['type']);
|
||||
$this->assertEquals('apple', $response['body']['type']);
|
||||
$this->assertEquals('tvOS App', $response['body']['name']);
|
||||
$this->assertEquals('com.example.tvos', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -4079,6 +4097,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/error', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
@@ -4094,6 +4113,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/platforms/' . $platformWebId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Web App 2',
|
||||
'hostname' => 'localhost-new',
|
||||
@@ -4113,6 +4133,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/platforms/' . $platformFultteriOSId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Flutter App (iOS) 2',
|
||||
'key' => 'com.example.ios2',
|
||||
@@ -4121,7 +4142,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals($platformFultteriOSId, $response['body']['$id']);
|
||||
$this->assertEquals('flutter-ios', $response['body']['type']);
|
||||
$this->assertEquals('apple', $response['body']['type']); // Origianlly flutter-ios, but new version renames
|
||||
$this->assertEquals('Flutter App (iOS) 2', $response['body']['name']);
|
||||
$this->assertEquals('com.example.ios2', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -4132,6 +4153,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/platforms/' . $platformFultterAndroidId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Flutter App (Android) 2',
|
||||
'key' => 'com.example.android2',
|
||||
@@ -4140,7 +4162,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals($platformFultterAndroidId, $response['body']['$id']);
|
||||
$this->assertEquals('flutter-android', $response['body']['type']);
|
||||
$this->assertEquals('android', $response['body']['type']); // Origianlly flutter-android, but new version renames
|
||||
$this->assertEquals('Flutter App (Android) 2', $response['body']['name']);
|
||||
$this->assertEquals('com.example.android2', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -4151,6 +4173,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/platforms/' . $platformFultterWebId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Flutter App (Web) 2',
|
||||
'hostname' => 'flutter2.appwrite.io',
|
||||
@@ -4159,7 +4182,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals($platformFultterWebId, $response['body']['$id']);
|
||||
$this->assertEquals('flutter-web', $response['body']['type']);
|
||||
$this->assertEquals('web', $response['body']['type']); // Originally flutter-web, but new version renames
|
||||
$this->assertEquals('Flutter App (Web) 2', $response['body']['name']);
|
||||
$this->assertEquals('', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -4170,6 +4193,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/platforms/' . $platformAppleIosId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'iOS App 2',
|
||||
'key' => 'com.example.ios2',
|
||||
@@ -4178,7 +4202,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals($platformAppleIosId, $response['body']['$id']);
|
||||
$this->assertEquals('apple-ios', $response['body']['type']);
|
||||
$this->assertEquals('apple', $response['body']['type']); // Originally apple-ios, but new version renames
|
||||
$this->assertEquals('iOS App 2', $response['body']['name']);
|
||||
$this->assertEquals('com.example.ios2', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -4189,6 +4213,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/platforms/' . $platformAppleMacOsId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'macOS App 2',
|
||||
'key' => 'com.example.macos2',
|
||||
@@ -4197,7 +4222,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals($platformAppleMacOsId, $response['body']['$id']);
|
||||
$this->assertEquals('apple-macos', $response['body']['type']);
|
||||
$this->assertEquals('apple', $response['body']['type']); // Originally apple-macos, but new version renames
|
||||
$this->assertEquals('macOS App 2', $response['body']['name']);
|
||||
$this->assertEquals('com.example.macos2', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -4208,6 +4233,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/platforms/' . $platformAppleWatchOsId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'watchOS App 2',
|
||||
'key' => 'com.example.watchos2',
|
||||
@@ -4216,7 +4242,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals($platformAppleWatchOsId, $response['body']['$id']);
|
||||
$this->assertEquals('apple-watchos', $response['body']['type']);
|
||||
$this->assertEquals('apple', $response['body']['type']); // Originally apple-watchos, but new version renames
|
||||
$this->assertEquals('watchOS App 2', $response['body']['name']);
|
||||
$this->assertEquals('com.example.watchos2', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -4227,6 +4253,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/platforms/' . $platformAppleTvOsId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'tvOS App 2',
|
||||
'key' => 'com.example.tvos2',
|
||||
@@ -4235,7 +4262,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
$this->assertEquals($platformAppleTvOsId, $response['body']['$id']);
|
||||
$this->assertEquals('apple-tvos', $response['body']['type']);
|
||||
$this->assertEquals('apple', $response['body']['type']); // Originally apple-tvos, but new version renames
|
||||
$this->assertEquals('tvOS App 2', $response['body']['name']);
|
||||
$this->assertEquals('com.example.tvos2', $response['body']['key']);
|
||||
$this->assertEquals('', $response['body']['store']);
|
||||
@@ -4247,6 +4274,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_PUT, '/projects/' . $id . '/platforms/error', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Flutter App (Android) 2',
|
||||
'key' => 'com.example.android2',
|
||||
@@ -4265,6 +4293,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'web',
|
||||
'name' => 'Web App',
|
||||
@@ -4277,6 +4306,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'flutter-ios',
|
||||
'name' => 'Flutter App (iOS)',
|
||||
@@ -4289,6 +4319,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'flutter-android',
|
||||
'name' => 'Flutter App (Android)',
|
||||
@@ -4301,6 +4332,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'flutter-web',
|
||||
'name' => 'Flutter App (Web)',
|
||||
@@ -4313,6 +4345,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'apple-ios',
|
||||
'name' => 'iOS App',
|
||||
@@ -4325,6 +4358,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'apple-macos',
|
||||
'name' => 'macOS App',
|
||||
@@ -4337,6 +4371,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'apple-watchos',
|
||||
'name' => 'watchOS App',
|
||||
@@ -4349,6 +4384,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/platforms', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), [
|
||||
'type' => 'apple-tvos',
|
||||
'name' => 'tvOS App',
|
||||
@@ -4360,6 +4396,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/platforms/' . $platformWebId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
@@ -4368,6 +4405,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformWebId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
@@ -4375,6 +4413,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/platforms/' . $platformFultteriOSId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
@@ -4383,6 +4422,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformFultteriOSId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
@@ -4390,6 +4430,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/platforms/' . $platformFultterAndroidId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
@@ -4398,6 +4439,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformFultterAndroidId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
@@ -4405,6 +4447,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/platforms/' . $platformFultterWebId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
@@ -4413,6 +4456,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformFultterWebId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
@@ -4420,6 +4464,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/platforms/' . $platformAppleIosId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
@@ -4428,6 +4473,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformAppleIosId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
@@ -4435,6 +4481,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/platforms/' . $platformAppleMacOsId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
@@ -4443,6 +4490,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformAppleMacOsId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
@@ -4450,6 +4498,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/platforms/' . $platformAppleWatchOsId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
@@ -4458,6 +4507,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformAppleWatchOsId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
@@ -4465,6 +4515,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $id . '/platforms/' . $platformAppleTvOsId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(204, $response['headers']['status-code']);
|
||||
@@ -4473,6 +4524,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/platforms/' . $platformAppleTvOsId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
'x-appwrite-response-format' => '1.8.0',
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(404, $response['headers']['status-code']);
|
||||
|
||||
@@ -9,7 +9,7 @@ class MockPublisher implements Publisher
|
||||
{
|
||||
private array $events = [];
|
||||
|
||||
public function enqueue(Queue $queue, array $payload): bool
|
||||
public function enqueue(Queue $queue, array $payload, bool $priority = false): bool
|
||||
{
|
||||
if (!isset($this->events[$queue->name])) {
|
||||
$this->events[$queue->name] = [];
|
||||
|
||||
Reference in New Issue
Block a user