mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Merge branch 'joins2' of https://github.com/appwrite/appwrite into joins-feature
This commit is contained in:
@@ -81,6 +81,7 @@ RUN chmod +x /usr/local/bin/doctor && \
|
||||
chmod +x /usr/local/bin/worker-certificates && \
|
||||
chmod +x /usr/local/bin/worker-databases && \
|
||||
chmod +x /usr/local/bin/worker-deletes && \
|
||||
chmod +x /usr/local/bin/worker-executions && \
|
||||
chmod +x /usr/local/bin/worker-functions && \
|
||||
chmod +x /usr/local/bin/worker-mails && \
|
||||
chmod +x /usr/local/bin/worker-messaging && \
|
||||
|
||||
@@ -1139,6 +1139,11 @@ return [
|
||||
'description' => 'Key with the requested ID could not be found.',
|
||||
'code' => 404,
|
||||
],
|
||||
Exception::KEY_ALREADY_EXISTS => [
|
||||
'name' => Exception::KEY_ALREADY_EXISTS,
|
||||
'description' => 'Key with the same ID already exists. Try again with a different ID.',
|
||||
'code' => 409,
|
||||
],
|
||||
Exception::PLATFORM_NOT_FOUND => [
|
||||
'name' => Exception::PLATFORM_NOT_FOUND,
|
||||
'description' => 'Platform with the requested ID could not be found.',
|
||||
|
||||
@@ -1512,21 +1512,25 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
}
|
||||
|
||||
// Allow redirect to rule URL if related to project
|
||||
//Check if $redirectValidator is instance of Redirect class
|
||||
// Check if $redirectValidator is instance of Redirect class
|
||||
if ($redirectValidator instanceof Redirect) {
|
||||
$rules = $authorization->skip(fn () => $dbForPlatform->find('rules', [
|
||||
Query::equal('domain', [
|
||||
parse_url($state['success'], PHP_URL_HOST),
|
||||
parse_url($state['failure'], PHP_URL_HOST)
|
||||
]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
Query::limit(2)
|
||||
]));
|
||||
$domains = \array_filter([
|
||||
parse_url($state['success'], PHP_URL_HOST) ?? '',
|
||||
parse_url($state['failure'], PHP_URL_HOST) ?? ''
|
||||
], fn ($domain) => \is_string($domain) && $domain !== '');
|
||||
|
||||
foreach ($rules as $rule) {
|
||||
$allowedHostnames = $redirectValidator->getAllowedHostnames();
|
||||
$allowedHostnames[] = $rule->getAttribute('domain', '');
|
||||
$redirectValidator->setAllowedHostnames($allowedHostnames);
|
||||
if (!empty($domains)) {
|
||||
$rules = $authorization->skip(fn () => $dbForPlatform->find('rules', [
|
||||
Query::equal('domain', \array_values(\array_unique($domains))),
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
Query::limit(2)
|
||||
]));
|
||||
|
||||
foreach ($rules as $rule) {
|
||||
$allowedHostnames = $redirectValidator->getAllowedHostnames();
|
||||
$allowedHostnames[] = $rule->getAttribute('domain', '');
|
||||
$redirectValidator->setAllowedHostnames($allowedHostnames);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,16 +15,20 @@ use Appwrite\SDK\Method;
|
||||
use Appwrite\SDK\Response as SDKResponse;
|
||||
use Appwrite\Template\Template;
|
||||
use Appwrite\Utopia\Database\Validator\CustomId;
|
||||
use Appwrite\Utopia\Database\Validator\Queries\Keys;
|
||||
use Appwrite\Utopia\Response;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Domains\Validator\PublicDomain;
|
||||
use Utopia\Http;
|
||||
@@ -1129,7 +1133,11 @@ Http::post('/v1/projects/:projectId/keys')
|
||||
'secret' => API_KEY_STANDARD . '_' . \bin2hex(\random_bytes(128)),
|
||||
]);
|
||||
|
||||
$key = $dbForPlatform->createDocument('keys', $key);
|
||||
try {
|
||||
$key = $dbForPlatform->createDocument('keys', $key);
|
||||
} catch (Duplicate) {
|
||||
throw new Exception(Exception::KEY_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
|
||||
@@ -1156,10 +1164,11 @@ Http::get('/v1/projects/:projectId/keys')
|
||||
]
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('queries', [], new Keys(), '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(', ', Keys::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('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, bool $includeTotal, Response $response, Database $dbForPlatform) {
|
||||
->action(function (string $projectId, array $queries, bool $includeTotal, Response $response, Database $dbForPlatform) {
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
|
||||
@@ -1167,15 +1176,46 @@ Http::get('/v1/projects/:projectId/keys')
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
|
||||
$keys = $dbForPlatform->find('keys', [
|
||||
Query::equal('resourceType', ['projects']),
|
||||
Query::equal('resourceInternalId', [$project->getSequence()]),
|
||||
Query::limit(5000),
|
||||
]);
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
// Backwards compatibility
|
||||
if (\count(Query::getByType($queries, [Query::TYPE_LIMIT])) === 0) {
|
||||
$queries[] = Query::limit(5000);
|
||||
}
|
||||
|
||||
$queries[] = Query::equal('resourceType', ['projects']);
|
||||
$queries[] = Query::equal('resourceInternalId', [$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());
|
||||
}
|
||||
|
||||
$keyId = $cursor->getValue();
|
||||
$cursorDocument = $dbForPlatform->getDocument('keys', $keyId);
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Key '{$keyId}' for the 'cursor' value not found.");
|
||||
}
|
||||
|
||||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
$keys = $dbForPlatform->find('keys', $queries);
|
||||
|
||||
$response->dynamic(new Document([
|
||||
'keys' => $keys,
|
||||
'total' => $includeTotal ? count($keys) : 0,
|
||||
'total' => $includeTotal ? $dbForPlatform->count('keys', $filterQueries, APP_LIMIT_COUNT) : 0,
|
||||
]), Response::MODEL_KEY_LIST);
|
||||
});
|
||||
|
||||
|
||||
+16
-19
@@ -8,7 +8,7 @@ use Appwrite\Auth\Key;
|
||||
use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Delete as DeleteEvent;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Execution;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception as AppwriteException;
|
||||
use Appwrite\Network\Cors;
|
||||
@@ -61,7 +61,7 @@ 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, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey, DeleteEvent $queueForDeletes, int $executionsRetentionCount)
|
||||
function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Execution $queueForExecutions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Authorization $authorization, ?Key $apiKey, DeleteEvent $queueForDeletes, int $executionsRetentionCount)
|
||||
{
|
||||
$host = $request->getHostname() ?? '';
|
||||
if (!empty($previewHostname)) {
|
||||
@@ -697,14 +697,11 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
|
||||
throw $th;
|
||||
}
|
||||
} finally {
|
||||
if ($type === 'function') {
|
||||
$queueForFunctions
|
||||
->setType(Func::TYPE_ASYNC_WRITE)
|
||||
if ($type === 'function' || $type === 'site') {
|
||||
$queueForExecutions
|
||||
->setExecution($execution)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
} elseif ($type === 'site') { // TODO: Move it to logs worker later
|
||||
$dbForProject->createDocument('executions', $execution);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -878,7 +875,7 @@ Http::init()
|
||||
->inject('geodb')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForExecutions')
|
||||
->inject('executor')
|
||||
->inject('platform')
|
||||
->inject('isResourceBlocked')
|
||||
@@ -889,7 +886,7 @@ Http::init()
|
||||
->inject('authorization')
|
||||
->inject('queueForDeletes')
|
||||
->inject('executionsRetentionCount')
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Func $queueForFunctions, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Execution $queueForExecutions, Executor $executor, array $platform, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
@@ -897,7 +894,7 @@ Http::init()
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
// Only run Router when external domain
|
||||
if (!\in_array($hostname, $platformHostnames) || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForExecutions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
@@ -1179,7 +1176,7 @@ Http::options()
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForExecutions')
|
||||
->inject('executor')
|
||||
->inject('geodb')
|
||||
->inject('isResourceBlocked')
|
||||
@@ -1192,14 +1189,14 @@ Http::options()
|
||||
->inject('authorization')
|
||||
->inject('queueForDeletes')
|
||||
->inject('executionsRetentionCount')
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Execution $queueForExecutions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Cors $cors, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
// Only run Router when external domain
|
||||
if (!in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForExecutions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
@@ -1575,7 +1572,7 @@ Http::get('/robots.txt')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForExecutions')
|
||||
->inject('executor')
|
||||
->inject('geodb')
|
||||
->inject('isResourceBlocked')
|
||||
@@ -1585,13 +1582,13 @@ Http::get('/robots.txt')
|
||||
->inject('authorization')
|
||||
->inject('queueForDeletes')
|
||||
->inject('executionsRetentionCount')
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Execution $queueForExecutions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
|
||||
$template = new View(__DIR__ . '/../views/general/robots.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForExecutions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
@@ -1610,7 +1607,7 @@ Http::get('/humans.txt')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForFunctions')
|
||||
->inject('queueForExecutions')
|
||||
->inject('executor')
|
||||
->inject('geodb')
|
||||
->inject('isResourceBlocked')
|
||||
@@ -1620,13 +1617,13 @@ Http::get('/humans.txt')
|
||||
->inject('authorization')
|
||||
->inject('queueForDeletes')
|
||||
->inject('executionsRetentionCount')
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
|
||||
->action(function (Http $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Execution $queueForExecutions, Executor $executor, Reader $geodb, callable $isResourceBlocked, array $platform, string $previewHostname, ?Key $apiKey, Authorization $authorization, DeleteEvent $queueForDeletes, int $executionsRetentionCount) {
|
||||
$platformHostnames = $platform['hostnames'] ?? [];
|
||||
if (in_array($request->getHostname(), $platformHostnames) || !empty($previewHostname)) {
|
||||
$template = new View(__DIR__ . '/../views/general/humans.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForExecutions, $executor, $geodb, $isResourceBlocked, $platform, $previewHostname, $authorization, $apiKey, $queueForDeletes, $executionsRetentionCount)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ use Appwrite\Platform\Modules\Compute\Specification;
|
||||
const APP_NAME = 'Appwrite';
|
||||
const APP_DOMAIN = 'appwrite.io';
|
||||
|
||||
const APP_VIEWS_DIR = __DIR__ . '/../views';
|
||||
|
||||
// Email
|
||||
const APP_EMAIL_TEAM = 'team@localhost.test'; // Default email address
|
||||
const APP_EMAIL_SECURITY = ''; // Default security email address
|
||||
|
||||
@@ -10,6 +10,7 @@ use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Execution;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
@@ -159,6 +160,9 @@ Http::setResource('queueForAudits', function (Publisher $publisher) {
|
||||
Http::setResource('queueForFunctions', function (Publisher $publisher) {
|
||||
return new Func($publisher);
|
||||
}, ['publisher']);
|
||||
Http::setResource('queueForExecutions', function (Publisher $publisher) {
|
||||
return new Execution($publisher);
|
||||
}, ['publisher']);
|
||||
Http::setResource('eventProcessor', function () {
|
||||
return new EventProcessor();
|
||||
}, []);
|
||||
|
||||
@@ -9,6 +9,7 @@ use Appwrite\Event\Certificate;
|
||||
use Appwrite\Event\Database as EventDatabase;
|
||||
use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Execution;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
@@ -358,6 +359,10 @@ Server::setResource('queueForFunctions', function (Publisher $publisher) {
|
||||
return new Func($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
Server::setResource('queueForExecutions', function (Publisher $publisher) {
|
||||
return new Execution($publisher);
|
||||
}, ['publisher']);
|
||||
|
||||
Server::setResource('queueForRealtime', function () {
|
||||
return new Realtime();
|
||||
}, []);
|
||||
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
exec php /usr/src/code/app/worker.php executions "$@"
|
||||
@@ -642,6 +642,37 @@ services:
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
appwrite-worker-executions:
|
||||
entrypoint: worker-executions
|
||||
<<: *x-logging
|
||||
container_name: appwrite-worker-executions
|
||||
image: appwrite-dev
|
||||
networks:
|
||||
- appwrite
|
||||
volumes:
|
||||
- ./app:/usr/src/code/app
|
||||
- ./src:/usr/src/code/src
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_started
|
||||
mariadb:
|
||||
condition: service_started
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_WORKER_PER_CORE
|
||||
- _APP_POOL_ADAPTER
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_REDIS_USER
|
||||
- _APP_REDIS_PASS
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
- _APP_LOGGING_CONFIG
|
||||
- _APP_DATABASE_SHARED_TABLES
|
||||
|
||||
appwrite-worker-functions:
|
||||
entrypoint: worker-functions
|
||||
<<: *x-logging
|
||||
|
||||
@@ -46,6 +46,9 @@ class Event
|
||||
public const MESSAGING_QUEUE_NAME = 'v1-messaging';
|
||||
public const MESSAGING_CLASS_NAME = 'MessagingV1';
|
||||
|
||||
public const EXECUTIONS_QUEUE_NAME = 'v1-executions';
|
||||
public const EXECUTIONS_CLASS_NAME = 'ExecutionsV1';
|
||||
|
||||
public const MIGRATIONS_QUEUE_NAME = 'v1-migrations';
|
||||
public const MIGRATIONS_CLASS_NAME = 'MigrationsV1';
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Event;
|
||||
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Queue\Publisher;
|
||||
|
||||
class Execution extends Event
|
||||
{
|
||||
protected ?Document $execution = null;
|
||||
|
||||
public function __construct(protected Publisher $publisher)
|
||||
{
|
||||
parent::__construct($publisher);
|
||||
|
||||
$this
|
||||
->setQueue(Event::EXECUTIONS_QUEUE_NAME)
|
||||
->setClass(Event::EXECUTIONS_CLASS_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets execution document for the execution event.
|
||||
*
|
||||
* @param Document $execution
|
||||
* @return self
|
||||
*/
|
||||
public function setExecution(Document $execution): self
|
||||
{
|
||||
$this->execution = $execution;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns set execution document for the execution event.
|
||||
*
|
||||
* @return null|Document
|
||||
*/
|
||||
public function getExecution(): ?Document
|
||||
{
|
||||
return $this->execution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare payload for the execution event.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function preparePayload(): array
|
||||
{
|
||||
return [
|
||||
'project' => $this->project,
|
||||
'execution' => $this->execution,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,6 @@ use Utopia\System\System;
|
||||
|
||||
class Func extends Event
|
||||
{
|
||||
public const TYPE_ASYNC_WRITE = 'async_write';
|
||||
|
||||
protected string $jwt = '';
|
||||
protected string $type = '';
|
||||
protected string $body = '';
|
||||
|
||||
@@ -86,4 +86,11 @@ class StatsUsage extends Event
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
public function reset(): Event
|
||||
{
|
||||
$this->metrics = [];
|
||||
parent::reset();
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,6 +318,7 @@ class Exception extends \Exception
|
||||
|
||||
/** Keys */
|
||||
public const string KEY_NOT_FOUND = 'key_not_found';
|
||||
public const string KEY_ALREADY_EXISTS = 'key_already_exists';
|
||||
|
||||
/** Variables */
|
||||
public const string VARIABLE_NOT_FOUND = 'variable_not_found';
|
||||
|
||||
@@ -31,7 +31,7 @@ class Get extends Action
|
||||
->desc('Create GitHub app installation')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'vcs.read')
|
||||
->label('error', __DIR__ . '/../../views/general/error.phtml')
|
||||
->label('error', APP_VIEWS_DIR . '/general/error.phtml')
|
||||
->label('sdk', new Method(
|
||||
namespace: 'vcs',
|
||||
group: 'installations',
|
||||
|
||||
@@ -35,7 +35,7 @@ class Get extends Action
|
||||
->desc('Get installation and authorization from GitHub app')
|
||||
->groups(['api', 'vcs'])
|
||||
->label('scope', 'public')
|
||||
->label('error', __DIR__ . '/../../views/general/error.phtml')
|
||||
->label('error', APP_VIEWS_DIR . '/general/error.phtml')
|
||||
->param('installation_id', '', new Text(256, 0), 'GitHub installation ID', true)
|
||||
->param('setup_action', '', new Text(256, 0), 'GitHub setup action type', true)
|
||||
->param('state', '', new Text(2048), 'GitHub state. Contains info sent when starting authorization flow.', true)
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Services;
|
||||
use Appwrite\Platform\Workers\Audits;
|
||||
use Appwrite\Platform\Workers\Certificates;
|
||||
use Appwrite\Platform\Workers\Deletes;
|
||||
use Appwrite\Platform\Workers\Executions;
|
||||
use Appwrite\Platform\Workers\Functions;
|
||||
use Appwrite\Platform\Workers\Mails;
|
||||
use Appwrite\Platform\Workers\Messaging;
|
||||
@@ -23,6 +24,7 @@ class Workers extends Service
|
||||
->addAction(Audits::getName(), new Audits())
|
||||
->addAction(Certificates::getName(), new Certificates())
|
||||
->addAction(Deletes::getName(), new Deletes())
|
||||
->addAction(Executions::getName(), new Executions())
|
||||
->addAction(Functions::getName(), new Functions())
|
||||
->addAction(Mails::getName(), new Mails())
|
||||
->addAction(Messaging::getName(), new Messaging())
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Workers;
|
||||
|
||||
use Exception;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Message;
|
||||
use Utopia\System\System;
|
||||
|
||||
class Executions extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'executions';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->desc('Executions worker')
|
||||
->groups(['executions'])
|
||||
->inject('message')
|
||||
->inject('dbForProject')
|
||||
->callback($this->action(...));
|
||||
}
|
||||
|
||||
public function action(
|
||||
Message $message,
|
||||
Database $dbForProject,
|
||||
): void {
|
||||
$payload = $message->getPayload() ?? [];
|
||||
|
||||
if (empty($payload)) {
|
||||
throw new Exception('Missing payload');
|
||||
}
|
||||
|
||||
$execution = new Document($payload['execution'] ?? []);
|
||||
|
||||
if ($execution->isEmpty()) {
|
||||
throw new Exception('Missing execution');
|
||||
}
|
||||
|
||||
if (System::getEnv('_APP_REGION') !== 'nyc') { // TODO: Remove region check
|
||||
$dbForProject->upsertDocument('executions', $execution);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace Appwrite\Platform\Workers;
|
||||
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Execution as ExecutionEvent;
|
||||
use Appwrite\Event\Func;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
@@ -16,8 +17,6 @@ use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Conflict;
|
||||
use Utopia\Database\Exception\Structure;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
@@ -50,6 +49,7 @@ class Functions extends Action
|
||||
->inject('queueForRealtime')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('queueForExecutions')
|
||||
->inject('log')
|
||||
->inject('executor')
|
||||
->inject('isResourceBlocked')
|
||||
@@ -65,6 +65,7 @@ class Functions extends Action
|
||||
Realtime $queueForRealtime,
|
||||
Event $queueForEvents,
|
||||
StatsUsage $queueForStatsUsage,
|
||||
ExecutionEvent $queueForExecutions,
|
||||
Log $log,
|
||||
Executor $executor,
|
||||
callable $isResourceBlocked
|
||||
@@ -77,15 +78,6 @@ class Functions extends Action
|
||||
|
||||
$type = $payload['type'] ?? '';
|
||||
|
||||
// Short-term solution to offhand write operation from API container
|
||||
if ($type === Func::TYPE_ASYNC_WRITE) {
|
||||
$execution = new Document($payload['execution'] ?? []);
|
||||
if (System::getEnv('_APP_REGION') !== 'nyc') { // TODO: Remove region check
|
||||
$dbForProject->createDocument('executions', $execution);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$events = $payload['events'] ?? [];
|
||||
$data = $payload['body'] ?? '';
|
||||
$eventData = $payload['payload'] ?? '';
|
||||
@@ -167,6 +159,7 @@ class Functions extends Action
|
||||
queueForRealtime: $queueForRealtime,
|
||||
queueForStatsUsage: $queueForStatsUsage,
|
||||
queueForEvents: $queueForEvents,
|
||||
queueForExecutions: $queueForExecutions,
|
||||
project: $project,
|
||||
function: $function,
|
||||
executor: $executor,
|
||||
@@ -211,6 +204,7 @@ class Functions extends Action
|
||||
queueForRealtime: $queueForRealtime,
|
||||
queueForStatsUsage: $queueForStatsUsage,
|
||||
queueForEvents: $queueForEvents,
|
||||
queueForExecutions: $queueForExecutions,
|
||||
project: $project,
|
||||
function: $function,
|
||||
executor: $executor,
|
||||
@@ -237,6 +231,7 @@ class Functions extends Action
|
||||
queueForRealtime: $queueForRealtime,
|
||||
queueForStatsUsage: $queueForStatsUsage,
|
||||
queueForEvents: $queueForEvents,
|
||||
queueForExecutions: $queueForExecutions,
|
||||
project: $project,
|
||||
function: $function,
|
||||
executor: $executor,
|
||||
@@ -269,7 +264,8 @@ class Functions extends Action
|
||||
*/
|
||||
private function fail(
|
||||
string $message,
|
||||
Database $dbForProject,
|
||||
Document $project,
|
||||
ExecutionEvent $queueForExecutions,
|
||||
Document $function,
|
||||
string $trigger,
|
||||
string $path,
|
||||
@@ -312,13 +308,10 @@ class Functions extends Action
|
||||
'duration' => 0.0,
|
||||
]);
|
||||
|
||||
if (System::getEnv('_APP_REGION') !== 'nyc') { // TODO: Remove region check
|
||||
$execution = $dbForProject->createDocument('executions', $execution);
|
||||
|
||||
if ($execution->isEmpty()) {
|
||||
throw new Exception('Failed to create execution');
|
||||
}
|
||||
}
|
||||
$queueForExecutions
|
||||
->setExecution($execution)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -342,9 +335,6 @@ class Functions extends Action
|
||||
* @param string|null $eventData
|
||||
* @param string|null $executionId
|
||||
* @return void
|
||||
* @throws Structure
|
||||
* @throws \Utopia\Database\Exception
|
||||
* @throws Conflict
|
||||
*/
|
||||
private function execute(
|
||||
Log $log,
|
||||
@@ -354,6 +344,7 @@ class Functions extends Action
|
||||
Realtime $queueForRealtime,
|
||||
StatsUsage $queueForStatsUsage,
|
||||
Event $queueForEvents,
|
||||
ExecutionEvent $queueForExecutions,
|
||||
Document $project,
|
||||
Document $function,
|
||||
Executor $executor,
|
||||
@@ -381,19 +372,19 @@ class Functions extends Action
|
||||
|
||||
if ($deployment->getAttribute('resourceId') !== $functionId) {
|
||||
$errorMessage = 'The execution could not be completed because a corresponding deployment was not found. A function deployment needs to be created before it can be executed. Please create a deployment for your function and try again.';
|
||||
$this->fail($errorMessage, $dbForProject, $function, $trigger, $path, $method, $user, $jwt, $event);
|
||||
$this->fail($errorMessage, $project, $queueForExecutions, $function, $trigger, $path, $method, $user, $jwt, $event);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($deployment->isEmpty()) {
|
||||
$errorMessage = 'The execution could not be completed because a corresponding deployment was not found. A function deployment needs to be created before it can be executed. Please create a deployment for your function and try again.';
|
||||
$this->fail($errorMessage, $dbForProject, $function, $trigger, $path, $method, $user, $jwt, $event);
|
||||
$this->fail($errorMessage, $project, $queueForExecutions, $function, $trigger, $path, $method, $user, $jwt, $event);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($deployment->getAttribute('status') !== 'ready') {
|
||||
$errorMessage = 'The execution could not be completed because the build is not ready. Please wait for the build to complete and try again.';
|
||||
$this->fail($errorMessage, $dbForProject, $function, $trigger, $path, $method, $user, $jwt, $event);
|
||||
$this->fail($errorMessage, $project, $queueForExecutions, $function, $trigger, $path, $method, $user, $jwt, $event);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -424,60 +415,38 @@ class Functions extends Action
|
||||
$headers['x-appwrite-continent-code'] = '';
|
||||
$headers['x-appwrite-continent-eu'] = 'false';
|
||||
|
||||
/** Create execution or update execution status */
|
||||
$execution = $dbForProject->getDocument('executions', $executionId ?? '');
|
||||
if ($execution->isEmpty()) {
|
||||
/** Create or update execution to processing status */
|
||||
if (empty($executionId)) {
|
||||
$executionId = ID::unique();
|
||||
$headers['x-appwrite-execution-id'] = $executionId;
|
||||
$headersFiltered = [];
|
||||
foreach ($headers as $key => $value) {
|
||||
if (\in_array(\strtolower($key), FUNCTION_ALLOWLIST_HEADERS_REQUEST)) {
|
||||
$headersFiltered[] = [ 'name' => $key, 'value' => $value ];
|
||||
}
|
||||
}
|
||||
}
|
||||
$headers['x-appwrite-execution-id'] = $executionId;
|
||||
|
||||
$execution = new Document([
|
||||
'$id' => $executionId,
|
||||
'$permissions' => $user->isEmpty() ? [] : [Permission::read(Role::user($user->getId()))],
|
||||
'resourceInternalId' => $function->getSequence(),
|
||||
'resourceId' => $function->getId(),
|
||||
'resourceType' => 'functions',
|
||||
'deploymentInternalId' => $deployment->getSequence(),
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'trigger' => $trigger,
|
||||
'status' => 'processing',
|
||||
'responseStatusCode' => 0,
|
||||
'responseHeaders' => [],
|
||||
'requestPath' => $path,
|
||||
'requestMethod' => $method,
|
||||
'requestHeaders' => $headersFiltered,
|
||||
'errors' => '',
|
||||
'logs' => '',
|
||||
'duration' => 0.0,
|
||||
]);
|
||||
|
||||
if (System::getEnv('_APP_REGION') !== 'nyc') { // TODO: Remove region check
|
||||
$execution = $dbForProject->createDocument('executions', $execution);
|
||||
|
||||
// TODO: @Meldiron Trigger executions.create event here
|
||||
|
||||
if ($execution->isEmpty()) {
|
||||
throw new Exception('Failed to create or read execution');
|
||||
}
|
||||
$headersFiltered = [];
|
||||
foreach ($headers as $key => $value) {
|
||||
if (\in_array(\strtolower($key), FUNCTION_ALLOWLIST_HEADERS_REQUEST)) {
|
||||
$headersFiltered[] = [ 'name' => $key, 'value' => $value ];
|
||||
}
|
||||
}
|
||||
|
||||
if ($execution->getAttribute('status') !== 'processing') {
|
||||
$execution->setAttribute('status', 'processing');
|
||||
|
||||
if (System::getEnv('_APP_REGION') !== 'nyc') { // TODO: Remove region check
|
||||
try {
|
||||
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
|
||||
} catch (\Throwable $e) {
|
||||
$log->addExtra('updateError', $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
$execution = new Document([
|
||||
'$id' => $executionId,
|
||||
'$permissions' => $user->isEmpty() ? [] : [Permission::read(Role::user($user->getId()))],
|
||||
'resourceInternalId' => $function->getSequence(),
|
||||
'resourceId' => $function->getId(),
|
||||
'resourceType' => 'functions',
|
||||
'deploymentInternalId' => $deployment->getSequence(),
|
||||
'deploymentId' => $deployment->getId(),
|
||||
'trigger' => $trigger,
|
||||
'status' => 'processing',
|
||||
'responseStatusCode' => 0,
|
||||
'responseHeaders' => [],
|
||||
'requestPath' => $path,
|
||||
'requestMethod' => $method,
|
||||
'requestHeaders' => $headersFiltered,
|
||||
'errors' => '',
|
||||
'logs' => '',
|
||||
'duration' => 0.0,
|
||||
]);
|
||||
|
||||
$durationStart = \microtime(true);
|
||||
|
||||
@@ -619,14 +588,11 @@ class Functions extends Action
|
||||
$error = $th->getMessage();
|
||||
$errorCode = $th->getCode();
|
||||
} finally {
|
||||
/** Update execution status */
|
||||
if (System::getEnv('_APP_REGION') !== 'nyc') { // TODO: Remove region check
|
||||
try {
|
||||
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
|
||||
} catch (\Throwable $e) {
|
||||
$log->addExtra('updateError', $e->getMessage());
|
||||
}
|
||||
}
|
||||
/** Persist final execution status */
|
||||
$queueForExecutions
|
||||
->setExecution($execution)
|
||||
->setProject($project)
|
||||
->trigger();
|
||||
|
||||
/** Trigger usage queue */
|
||||
$queueForStatsUsage
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Workers;
|
||||
use Ahc\Jwt\JWT;
|
||||
use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Realtime;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Template\Template;
|
||||
use Utopia\CLI\Console;
|
||||
@@ -25,6 +26,10 @@ use Utopia\Migration\Destination;
|
||||
use Utopia\Migration\Destinations\Appwrite as DestinationAppwrite;
|
||||
use Utopia\Migration\Destinations\CSV as DestinationCSV;
|
||||
use Utopia\Migration\Exception as MigrationException;
|
||||
use Utopia\Migration\Resource;
|
||||
use Utopia\Migration\Resources\Database\Database as ResourceDatabase;
|
||||
use Utopia\Migration\Resources\Database\Row as ResourceRow;
|
||||
use Utopia\Migration\Resources\Database\Table as ResourceTable;
|
||||
use Utopia\Migration\Source;
|
||||
use Utopia\Migration\Sources\Appwrite as SourceAppwrite;
|
||||
use Utopia\Migration\Sources\CSV;
|
||||
@@ -52,6 +57,7 @@ class Migrations extends Action
|
||||
*/
|
||||
protected array $sourceReport = [];
|
||||
|
||||
private string $source;
|
||||
/**
|
||||
* @var callable|null
|
||||
*/
|
||||
@@ -78,6 +84,7 @@ class Migrations extends Action
|
||||
->inject('deviceForMigrations')
|
||||
->inject('deviceForFiles')
|
||||
->inject('queueForMails')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('plan')
|
||||
->inject('authorization')
|
||||
->callback($this->action(...));
|
||||
@@ -96,6 +103,7 @@ class Migrations extends Action
|
||||
Device $deviceForMigrations,
|
||||
Device $deviceForFiles,
|
||||
Mail $queueForMails,
|
||||
StatsUsage $queueForStatsUsage,
|
||||
array $plan,
|
||||
Authorization $authorization,
|
||||
): void {
|
||||
@@ -139,6 +147,7 @@ class Migrations extends Action
|
||||
$migration,
|
||||
$queueForRealtime,
|
||||
$queueForMails,
|
||||
$queueForStatsUsage,
|
||||
$platform,
|
||||
$authorization
|
||||
);
|
||||
@@ -324,6 +333,7 @@ class Migrations extends Action
|
||||
Document $migration,
|
||||
Realtime $queueForRealtime,
|
||||
Mail $queueForMails,
|
||||
StatsUsage $queueForStatsUsage,
|
||||
array $platform,
|
||||
Authorization $authorization,
|
||||
): void {
|
||||
@@ -368,6 +378,7 @@ class Migrations extends Action
|
||||
$destination
|
||||
);
|
||||
|
||||
$aggregatedResources = [];
|
||||
/** Start Transfer */
|
||||
if (empty($source->getErrors())) {
|
||||
$migration->setAttribute('stage', 'migrating');
|
||||
@@ -375,9 +386,40 @@ class Migrations extends Action
|
||||
|
||||
$transfer->run(
|
||||
$migration->getAttribute('resources'),
|
||||
function () use ($migration, $transfer, $project, $queueForRealtime) {
|
||||
function ($resources) use ($migration, $transfer, $project, $queueForRealtime, &$aggregatedResources) {
|
||||
$migration->setAttribute('resourceData', json_encode($transfer->getCache()));
|
||||
$migration->setAttribute('statusCounters', json_encode($transfer->getStatusCounters()));
|
||||
|
||||
if (!empty($resources)) {
|
||||
/**
|
||||
* @var Resource $resource
|
||||
*/
|
||||
$resource = $resources[0];
|
||||
$count = count($resources);
|
||||
$databaseId = null;
|
||||
$tableId = null;
|
||||
switch ($resource->getName()) {
|
||||
case ResourceTable::getName():
|
||||
/** @var ResourceTable $resource */
|
||||
$databaseId = $resource->getDatabase()->getSequence();
|
||||
break;
|
||||
case ResourceRow::getName():
|
||||
/** @var ResourceRow $resource */
|
||||
$table = $resource->getTable();
|
||||
$databaseId = $table->getDatabase()->getSequence();
|
||||
$tableId = $table->getSequence();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
$aggregatedResources[] = [
|
||||
'name' => $resource->getName(),
|
||||
'count' => $count,
|
||||
'databaseId' => $databaseId,
|
||||
'tableId' => $tableId
|
||||
];
|
||||
|
||||
}
|
||||
$this->updateMigrationDocument($migration, $project, $queueForRealtime);
|
||||
},
|
||||
$migration->getAttribute('resourceId'),
|
||||
@@ -443,6 +485,16 @@ class Migrations extends Action
|
||||
}
|
||||
|
||||
if ($migration->getAttribute('status', '') === 'completed') {
|
||||
foreach ($aggregatedResources as $resource) {
|
||||
$this->processMigrationResourceStats(
|
||||
$resource,
|
||||
$queueForStatsUsage,
|
||||
$project,
|
||||
$migration->getAttribute('source'),
|
||||
$authorization,
|
||||
$migration->getAttribute('resourceId')
|
||||
);
|
||||
}
|
||||
$destination?->success();
|
||||
$source?->success();
|
||||
|
||||
@@ -737,4 +789,58 @@ class Migrations extends Action
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
private function processMigrationResourceStats(array $resources, StatsUsage $queueForStatsUsage, Document $projectDocument, string $source, Authorization $authorization, ?string $resourceId)
|
||||
{
|
||||
$resourceName = $resources['name'];
|
||||
$count = $resources['count'];
|
||||
$databaseInternalId = $resources['databaseId'];
|
||||
$tableInternalId = $resources['tableId'];
|
||||
|
||||
if ($source === CSV::getName()) {
|
||||
[$databaseId, $tableId] = explode(':', $resourceId);
|
||||
$database = $authorization->skip(fn () => $this->dbForProject->getDocument('databases', $databaseId));
|
||||
$table = $authorization->skip(fn () => $this->dbForProject->getDocument('database_' . $database->getSequence(), $tableId));
|
||||
$databaseInternalId = (int) $database->getSequence();
|
||||
$tableInternalId = (int) $table->getSequence();
|
||||
}
|
||||
|
||||
switch ($resourceName) {
|
||||
case ResourceDatabase::getName():
|
||||
$queueForStatsUsage->addMetric(METRIC_DATABASES, $count);
|
||||
break;
|
||||
|
||||
case ResourceTable::getName():
|
||||
$queueForStatsUsage
|
||||
->addMetric(METRIC_COLLECTIONS, $count)
|
||||
->addMetric(
|
||||
str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_COLLECTIONS),
|
||||
$count
|
||||
);
|
||||
break;
|
||||
|
||||
case ResourceRow::getName():
|
||||
$queueForStatsUsage
|
||||
->addMetric(
|
||||
str_replace(
|
||||
['{databaseInternalId}','{collectionInternalId}'],
|
||||
[$databaseInternalId, $tableInternalId],
|
||||
METRIC_DATABASE_ID_COLLECTION_ID_DOCUMENTS
|
||||
),
|
||||
$count
|
||||
)
|
||||
->addMetric(
|
||||
str_replace('{databaseInternalId}', $databaseInternalId, METRIC_DATABASE_ID_DOCUMENTS),
|
||||
$count
|
||||
)
|
||||
->addMetric(METRIC_DOCUMENTS, $count);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$queueForStatsUsage->setProject($projectDocument)->trigger();
|
||||
$queueForStatsUsage->reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Utopia\Database\Validator\Queries;
|
||||
|
||||
class Keys extends Base
|
||||
{
|
||||
public const ALLOWED_ATTRIBUTES = [
|
||||
'expire',
|
||||
'accessedAt',
|
||||
'name',
|
||||
'scopes',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('keys', self::ALLOWED_ATTRIBUTES);
|
||||
}
|
||||
}
|
||||
@@ -1751,7 +1751,10 @@ class FunctionsCustomServerTest extends Scope
|
||||
$this->assertEventually(function () use ($functionId, $userId) {
|
||||
$executions = $this->listExecutions($functionId);
|
||||
|
||||
$lastExecution = $executions['body']['executions'][0];
|
||||
$this->assertEquals(200, $executions['headers']['status-code']);
|
||||
$executionsList = $executions['body']['executions'] ?? [];
|
||||
$this->assertNotEmpty($executionsList);
|
||||
$lastExecution = $executionsList[0];
|
||||
|
||||
$this->assertEquals('completed', $lastExecution['status']);
|
||||
$this->assertEquals(204, $lastExecution['responseStatusCode']);
|
||||
|
||||
@@ -1219,7 +1219,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'duration' => 60, // Set session duration to 1 minute
|
||||
'duration' => 10, // Set session duration to 10 seconds
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
@@ -1228,7 +1228,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertArrayHasKey('platforms', $response['body']);
|
||||
$this->assertArrayHasKey('webhooks', $response['body']);
|
||||
$this->assertArrayHasKey('keys', $response['body']);
|
||||
$this->assertEquals(60, $response['body']['authDuration']);
|
||||
$this->assertEquals(10, $response['body']['authDuration']);
|
||||
|
||||
$projectId = $response['body']['$id'];
|
||||
|
||||
@@ -1269,44 +1269,30 @@ class ProjectsConsoleClientTest extends Scope
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
// Check session doesn't expire too soon.
|
||||
sleep(30);
|
||||
// Eventually session expires, within 15 seconds (10+variance)
|
||||
$this->assertEventually(function () use ($projectId, $sessionCookie) {
|
||||
// Get User
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'Cookie' => $sessionCookie,
|
||||
]));
|
||||
|
||||
// Get User
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'Cookie' => $sessionCookie,
|
||||
]));
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
}, timeoutMs: 15 * 1000);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
// Wait just over a minute
|
||||
sleep(35);
|
||||
|
||||
// Get User
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'Cookie' => $sessionCookie,
|
||||
]));
|
||||
|
||||
$this->assertEquals(401, $response['headers']['status-code']);
|
||||
|
||||
// Set session duration to 15s
|
||||
// Set session duration to 10min
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $id . '/auth/duration', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'duration' => 15, // seconds
|
||||
'duration' => 600, // seconds
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(15, $response['body']['authDuration']);
|
||||
|
||||
// Wait 20 seconds, ensure non-valid session
|
||||
\sleep(20);
|
||||
$this->assertEquals(600, $response['body']['authDuration']);
|
||||
|
||||
// Ensure session is still expired (new duration only affects new sessions)
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
@@ -3208,7 +3194,7 @@ class ProjectsConsoleClientTest extends Scope
|
||||
/**
|
||||
* Test for SUCCESS with custom ID
|
||||
*/
|
||||
$customKeyId = 'key-with-custom-id';
|
||||
$customKeyId = \uniqid() . 'custom-id';
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
@@ -3221,6 +3207,20 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$this->assertSame($customKeyId, $response['body']['$id']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE with custom ID
|
||||
*/
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'keyId' => $customKeyId,
|
||||
'name' => 'Key Custom',
|
||||
'scopes' => ['teams.read', 'teams.write'],
|
||||
]);
|
||||
|
||||
$this->assertEquals(409, $response['headers']['status-code']);
|
||||
|
||||
/**
|
||||
* Test for SUCCESS with magic string ID
|
||||
*/
|
||||
@@ -3267,19 +3267,258 @@ class ProjectsConsoleClientTest extends Scope
|
||||
{
|
||||
$id = $data['projectId'] ?? '';
|
||||
|
||||
/** Create a second key with an expiry for query testing */
|
||||
$expireDate = DateTime::addSeconds(new \DateTime(), 3600);
|
||||
$response = $this->client->call(Client::METHOD_POST, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'name' => 'Key Test 2',
|
||||
'scopes' => ['users.read'],
|
||||
'expire' => $expireDate,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
$key2Id = $response['body']['$id'];
|
||||
|
||||
/** List all keys (no queries) */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), []);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(5, $response['body']['total']);
|
||||
$this->assertCount(5, $response['body']['keys']);
|
||||
|
||||
/** List keys with limit */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::limit(1)->toString(),
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertCount(1, $response['body']['keys']);
|
||||
$this->assertEquals(5, $response['body']['total']);
|
||||
|
||||
/** List keys with offset */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::offset(1)->toString(),
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertCount(4, $response['body']['keys']);
|
||||
$this->assertEquals(5, $response['body']['total']);
|
||||
|
||||
/** List keys with cursor after */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::cursorAfter(new Document(['$id' => $data['keyId']]))->toString(),
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertCount(1, $response['body']['keys']);
|
||||
$this->assertEquals(5, $response['body']['total']);
|
||||
$this->assertEquals($key2Id, $response['body']['keys'][0]['$id']);
|
||||
|
||||
/** List keys filtering by expire (lessThan now — should match none) */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::lessThan('expire', (new \DateTime())->format('Y-m-d H:i:s'))->toString(),
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(0, $response['body']['total']);
|
||||
|
||||
/** List keys filtering by expire (greaterThan now — should match the key with expiry) */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::greaterThan('expire', (new \DateTime())->format('Y-m-d H:i:s'))->toString(),
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(1, $response['body']['total']);
|
||||
$this->assertCount(1, $response['body']['keys']);
|
||||
|
||||
/** List keys filtering by name (equal — exact match) */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::equal('name', ['Key Test'])->toString(),
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(1, $response['body']['total']);
|
||||
$this->assertCount(1, $response['body']['keys']);
|
||||
$this->assertEquals('Key Test', $response['body']['keys'][0]['name']);
|
||||
|
||||
/** List keys filtering by name (equal — multiple values) */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::equal('name', ['Key Test', 'Key Test 2'])->toString(),
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(2, $response['body']['total']);
|
||||
$this->assertCount(2, $response['body']['keys']);
|
||||
|
||||
/** List keys filtering by name (equal — no match) */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::equal('name', ['Non Existent Key'])->toString(),
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(0, $response['body']['total']);
|
||||
$this->assertCount(0, $response['body']['keys']);
|
||||
|
||||
/** List keys filtering by scopes (contains — match key with teams.read) */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::contains('scopes', ['teams.read'])->toString(),
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(4, $response['body']['total']);
|
||||
$this->assertCount(4, $response['body']['keys']);
|
||||
|
||||
/** List keys filtering by scopes (contains — match key with users.read) */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::contains('scopes', ['users.read'])->toString(),
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(1, $response['body']['total']);
|
||||
$this->assertCount(1, $response['body']['keys']);
|
||||
|
||||
/** List keys filtering by scopes (contains — no match) */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::contains('scopes', ['databases.read'])->toString(),
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(0, $response['body']['total']);
|
||||
$this->assertCount(0, $response['body']['keys']);
|
||||
|
||||
/** List keys filtering by name and scopes combined */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::equal('name', ['Key Test'])->toString(),
|
||||
Query::contains('scopes', ['teams.read'])->toString(),
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertEquals(1, $response['body']['total']);
|
||||
$this->assertCount(1, $response['body']['keys']);
|
||||
$this->assertEquals('Key Test', $response['body']['keys'][0]['name']);
|
||||
|
||||
/** List keys with orderDesc */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::orderDesc('$createdAt')->toString(),
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertCount(5, $response['body']['keys']);
|
||||
$this->assertGreaterThan($response['body']['keys'][1]['$createdAt'], $response['body']['keys'][0]['$createdAt']);
|
||||
|
||||
/** List keys with total disabled */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'total' => false,
|
||||
'queries' => [
|
||||
Query::limit(1)->toString()
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
$this->assertCount(1, $response['body']['keys']);
|
||||
$this->assertEquals(0, $response['body']['total']);
|
||||
|
||||
/**
|
||||
* Test for FAILURE
|
||||
*/
|
||||
|
||||
/** Test invalid query attribute */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::equal('secret', ['test'])->toString(),
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
/** Test invalid cursor */
|
||||
$response = $this->client->call(Client::METHOD_GET, '/projects/' . $id . '/keys', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'queries' => [
|
||||
Query::cursorAfter(new Document(['$id' => 'invalid']))->toString(),
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertEquals(400, $response['headers']['status-code']);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -5373,6 +5612,55 @@ class ProjectsConsoleClientTest extends Scope
|
||||
$this->assertEquals(201, $response['headers']['status-code']);
|
||||
}
|
||||
|
||||
public function testOAuthRedirectWithCustomSchemeState(): void
|
||||
{
|
||||
// Prepare project
|
||||
$projectId = $this->setupProject([
|
||||
'projectId' => ID::unique(),
|
||||
'name' => 'testOAuthRedirectWithCustomSchemeState',
|
||||
'region' => System::getEnv('_APP_REGION', 'default')
|
||||
]);
|
||||
|
||||
$provider = 'mock';
|
||||
$appId = '1';
|
||||
$secret = '123456';
|
||||
|
||||
// Prepare OAuth provider
|
||||
$response = $this->client->call(Client::METHOD_PATCH, '/projects/' . $projectId . '/oauth2', array_merge([
|
||||
'origin' => 'http://localhost',
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $this->getProject()['$id'],
|
||||
], $this->getHeaders()), [
|
||||
'provider' => $provider,
|
||||
'appId' => $appId,
|
||||
'secret' => $secret,
|
||||
'enabled' => true,
|
||||
]);
|
||||
$this->assertEquals(200, $response['headers']['status-code']);
|
||||
|
||||
$scheme = 'appwrite-callback-' . $projectId;
|
||||
$state = \json_encode([
|
||||
'success' => $scheme . ':///',
|
||||
'failure' => $scheme . ':///'
|
||||
]);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/account/sessions/oauth2/' . $provider . '/redirect', [
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-project' => $projectId,
|
||||
'origin' => '',
|
||||
'referer' => '',
|
||||
], [
|
||||
'code' => 'any-code',
|
||||
'state' => $state,
|
||||
'error' => 'access_denied',
|
||||
'error_description' => 'test',
|
||||
], followRedirects: false);
|
||||
|
||||
$this->assertEquals(301, $response['headers']['status-code']);
|
||||
$this->assertStringStartsWith($scheme . '://', $response['headers']['location']);
|
||||
$this->assertStringContainsString('error=', $response['headers']['location']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @group abuseEnabled
|
||||
*/
|
||||
|
||||
@@ -212,6 +212,27 @@ services:
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
|
||||
appwrite-worker-executions:
|
||||
entrypoint: worker-executions
|
||||
container_name: appwrite-worker-executions
|
||||
build:
|
||||
context: .
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- appwrite
|
||||
depends_on:
|
||||
- redis
|
||||
- mariadb
|
||||
environment:
|
||||
- _APP_ENV
|
||||
- _APP_REDIS_HOST
|
||||
- _APP_REDIS_PORT
|
||||
- _APP_DB_HOST
|
||||
- _APP_DB_PORT
|
||||
- _APP_DB_SCHEMA
|
||||
- _APP_DB_USER
|
||||
- _APP_DB_PASS
|
||||
|
||||
appwrite-worker-functions:
|
||||
entrypoint: worker-functions
|
||||
container_name: appwrite-worker-functions
|
||||
|
||||
@@ -23,7 +23,7 @@ class ComposeTest extends TestCase
|
||||
|
||||
public function testServices(): void
|
||||
{
|
||||
$this->assertCount(15, $this->object->getServices());
|
||||
$this->assertCount(16, $this->object->getServices());
|
||||
$this->assertEquals('appwrite', $this->object->getService('appwrite')->getContainerName());
|
||||
$this->assertEquals('', $this->object->getService('appwrite')->getImageVersion());
|
||||
$this->assertEquals('3.6', $this->object->getService('traefik')->getImageVersion());
|
||||
|
||||
Reference in New Issue
Block a user