diff --git a/.gitmodules b/.gitmodules index 065fe76c46..e9d2cb296f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "app/console"] path = app/console url = https://github.com/appwrite/console - branch = 4.3.23 + branch = 4.3.27 diff --git a/app/console b/app/console index 112fccf5a0..e0828010f4 160000 --- a/app/console +++ b/app/console @@ -1 +1 @@ -Subproject commit 112fccf5a044c9795b31b264f5974224a3545a17 +Subproject commit e0828010f4c222bb99cf65ddfed288e60602666f diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index a4ef8c2a4c..7a47649ae6 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -669,6 +669,7 @@ App::post('/v1/teams/:teamId/memberships') } $queueForEvents + ->setParam('userId', $invitee->getId()) ->setParam('teamId', $team->getId()) ->setParam('membershipId', $membership->getId()) ; @@ -902,6 +903,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId') $dbForProject->purgeCachedDocument('users', $profile->getId()); $queueForEvents + ->setParam('userId', $profile->getId()) ->setParam('teamId', $team->getId()) ->setParam('membershipId', $membership->getId()); @@ -1027,6 +1029,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status') Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1)); $queueForEvents + ->setParam('userId', $user->getId()) ->setParam('teamId', $team->getId()) ->setParam('membershipId', $membership->getId()) ; @@ -1108,6 +1111,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId') } $queueForEvents + ->setParam('userId', $user->getId()) ->setParam('teamId', $team->getId()) ->setParam('membershipId', $membership->getId()) ->setPayload($response->output($membership, Response::MODEL_MEMBERSHIP)) diff --git a/app/controllers/general.php b/app/controllers/general.php index 302eb1a2af..43f19f9c02 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -1,6 +1,6 @@ getRoute()?->label('error', __DIR__.'/../views/general/error.phtml'); + $utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml'); $host = $request->getHostname() ?? ''; $route = Authorization::skip( fn () => $dbForConsole->find('rules', [ Query::equal('domain', [$host]), - Query::limit(1), + Query::limit(1) ]) )[0] ?? null; @@ -70,7 +70,6 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo // Act as API - no Proxy logic $utopia->getRoute()?->label('error', ''); - return false; } @@ -80,7 +79,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo ); if (array_key_exists('proxy', $project->getAttribute('services', []))) { $status = $project->getAttribute('services', [])['proxy']; - if (! $status) { + if (!$status) { throw new AppwriteException(AppwriteException::GENERAL_SERVICE_DISABLED); } } @@ -100,7 +99,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.'); } - return $response->redirect('https://'.$request->getHostname().$request->getURI()); + return $response->redirect('https://' . $request->getHostname() . $request->getURI()); } } @@ -109,10 +108,11 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo $path = ($swooleRequest->server['request_uri'] ?? '/'); $query = ($swooleRequest->server['query_string'] ?? ''); - if (! empty($query)) { - $path .= '?'.$query; + if (!empty($query)) { + $path .= '?' . $query; } + $body = $swooleRequest->getContent() ?? ''; $method = $swooleRequest->server['request_method']; @@ -124,7 +124,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo $function = Authorization::skip(fn () => $dbForProject->getDocument('functions', $functionId)); - if ($function->isEmpty() || ! $function->getAttribute('enabled')) { + if ($function->isEmpty() || !$function->getAttribute('enabled')) { throw new AppwriteException(AppwriteException::FUNCTION_NOT_FOUND); } @@ -134,7 +134,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo $runtime = (isset($runtimes[$function->getAttribute('runtime', '')])) ? $runtimes[$function->getAttribute('runtime', '')] : null; if (\is_null($runtime)) { - throw new AppwriteException(AppwriteException::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "'.$function->getAttribute('runtime', '').'" is not supported'); + throw new AppwriteException(AppwriteException::FUNCTION_RUNTIME_UNSUPPORTED, 'Runtime "' . $function->getAttribute('runtime', '') . '" is not supported'); } $deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $function->getAttribute('deployment', ''))); @@ -159,7 +159,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo $permissions = $function->getAttribute('execute'); - if (! (\in_array('any', $permissions)) && ! (\in_array('guests', $permissions))) { + if (!(\in_array('any', $permissions)) && !(\in_array('guests', $permissions))) { throw new AppwriteException(AppwriteException::USER_UNAUTHORIZED, 'To execute function using domain, execute permissions must include "any" or "guests"'); } @@ -172,7 +172,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo $headers['x-appwrite-continent-eu'] = 'false'; $ip = $headers['x-real-ip'] ?? ''; - if (! empty($ip)) { + if (!empty($ip)) { $record = $geodb->get($ip); if ($record) { @@ -201,7 +201,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo 'deploymentInternalId' => $deployment->getInternalId(), 'deploymentId' => $deployment->getId(), 'trigger' => 'http', // http / schedule / event - 'status' => 'processing', // waiting / processing / completed / failed + 'status' => 'processing', // waiting / processing / completed / failed 'responseStatusCode' => 0, 'responseHeaders' => [], 'requestPath' => $path, @@ -228,7 +228,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo 'APPWRITE_FUNCTION_TRIGGER' => $headers['x-appwrite-trigger'] ?? '', 'APPWRITE_FUNCTION_DATA' => $body ?? '', 'APPWRITE_FUNCTION_USER_ID' => $headers['x-appwrite-user-id'] ?? '', - 'APPWRITE_FUNCTION_JWT' => $headers['x-appwrite-user-jwt'] ?? '', + 'APPWRITE_FUNCTION_JWT' => $headers['x-appwrite-user-jwt'] ?? '' ]); } @@ -257,7 +257,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo try { $version = $function->getAttribute('version', 'v2'); $command = $runtime['startCommand']; - $command = $version === 'v2' ? '' : 'cp /tmp/code.tar.gz /mnt/code/code.tar.gz && nohup helpers/start.sh "'.$command.'"'; + $command = $version === 'v2' ? '' : 'cp /tmp/code.tar.gz /mnt/code/code.tar.gz && nohup helpers/start.sh "' . $command . '"'; $executionResponse = $executor->createExecution( projectId: $project->getId(), deploymentId: $deployment->getId(), @@ -298,7 +298,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo ->setAttribute('duration', $durationEnd - $durationStart) ->setAttribute('status', 'failed') ->setAttribute('responseStatusCode', 500) - ->setAttribute('errors', $th->getMessage().'\nError Code: '.$th->getCode()); + ->setAttribute('errors', $th->getMessage() . '\nError Code: ' . $th->getCode()); Console::error($th->getMessage()); if ($th instanceof AppwriteException) { @@ -308,10 +308,13 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo $queueForUsage ->addMetric(METRIC_EXECUTIONS, 1) ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS), 1) - ->addMetric(METRIC_EXECUTIONS_COMPUTE, (int) ($execution->getAttribute('duration') * 1000)) // per project - ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int) ($execution->getAttribute('duration') * 1000)) // per function - ->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int) (512 * $execution->getAttribute('duration', 0))) - ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int) (512 * $execution->getAttribute('duration', 0))); + ->addMetric(METRIC_EXECUTIONS_COMPUTE, (int)($execution->getAttribute('duration') * 1000)) // per project + ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function + ->addMetric(METRIC_EXECUTIONS_MB_SECONDS, (int)(512 * $execution->getAttribute('duration', 0))) + ->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_MB_SECONDS), (int)(512 * $execution->getAttribute('duration', 0))) + ->setProject($project) + ->trigger() + ; if ($function->getAttribute('logging')) { /** @var Document $execution */ @@ -356,14 +359,12 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo return true; } elseif ($type === 'api') { $utopia->getRoute()?->label('error', ''); - return false; } else { - throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Unknown resource type '.$type); + throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Unknown resource type ' . $type); } $utopia->getRoute()?->label('error', ''); - return false; } @@ -446,12 +447,12 @@ App::init() $domain = $request->getHostname(); $domains = Config::getParam('domains', []); - if (! array_key_exists($domain, $domains)) { - $domain = new Domain(! empty($domain) ? $domain : ''); + if (!array_key_exists($domain, $domains)) { + $domain = new Domain(!empty($domain) ? $domain : ''); - if (empty($domain->get()) || ! $domain->isKnown() || $domain->isTest()) { + if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) { $domains[$domain->get()] = false; - Console::warning($domain->get().' is not a publicly accessible domain. Skipping SSL certificate generation.'); + Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.'); } elseif (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) { Console::warning('Skipping SSL certificates generation on ACME challenge.'); } else { @@ -459,7 +460,7 @@ App::init() $envDomain = System::getEnv('_APP_DOMAIN', ''); $mainDomain = null; - if (! empty($envDomain) && $envDomain !== 'localhost') { + if (!empty($envDomain) && $envDomain !== 'localhost') { $mainDomain = $envDomain; } else { $domainDocument = $dbForConsole->findOne('rules', [Query::orderAsc('$id')]); @@ -467,24 +468,24 @@ App::init() } if ($mainDomain !== $domain->get()) { - Console::warning($domain->get().' is not a main domain. Skipping SSL certificate generation.'); + Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.'); } else { $domainDocument = $dbForConsole->findOne('rules', [ - Query::equal('domain', [$domain->get()]), + Query::equal('domain', [$domain->get()]) ]); - if (! $domainDocument) { + if (!$domainDocument) { $domainDocument = new Document([ 'domain' => $domain->get(), 'resourceType' => 'api', 'status' => 'verifying', 'projectId' => 'console', - 'projectInternalId' => 'console', + 'projectInternalId' => 'console' ]); $domainDocument = $dbForConsole->createDocument('rules', $domainDocument); - Console::info('Issuing a TLS certificate for the main domain ('.$domain->get().') in a few seconds...'); + Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...'); $queueForCertificates ->setDomain($domainDocument) @@ -515,14 +516,14 @@ App::init() $refDomainOrigin = $origin; } - $refDomain = (! empty($protocol) ? $protocol : $request->getProtocol()).'://'.$refDomainOrigin.(! empty($port) ? ':'.$port : ''); + $refDomain = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $refDomainOrigin . (!empty($port) ? ':' . $port : ''); - $refDomain = (! $route->getLabel('origin', false)) // This route is publicly accessible + $refDomain = (!$route->getLabel('origin', false)) // This route is publicly accessible ? $refDomain - : (! empty($protocol) ? $protocol : $request->getProtocol()).'://'.$origin.(! empty($port) ? ':'.$port : ''); + : (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : ''); $selfDomain = new Domain($request->getHostname()); - $endDomain = new Domain((string) $origin); + $endDomain = new Domain((string)$origin); Config::setParam( 'domainVerification', @@ -530,7 +531,7 @@ App::init() $endDomain->getRegisterable() !== '' ); - $isLocalHost = $request->getHostname() === 'localhost' || $request->getHostname() === 'localhost:'.$request->getPort(); + $isLocalHost = $request->getHostname() === 'localhost' || $request->getHostname() === 'localhost:' . $request->getPort(); $isIpAddress = filter_var($request->getHostname(), FILTER_VALIDATE_IP) !== false; $isConsoleProject = $project->getAttribute('$id', '') === 'console'; @@ -542,8 +543,8 @@ App::init() ? null : ( $isConsoleProject && $isConsoleRootSession - ? '.'.$selfDomain->getRegisterable() - : '.'.$request->getHostname() + ? '.' . $selfDomain->getRegisterable() + : '.' . $request->getHostname() ) ); @@ -559,7 +560,7 @@ App::init() $response->addFilter(new ResponseV17()); } if (version_compare($responseFormat, APP_VERSION_STABLE, '>')) { - $response->addHeader('X-Appwrite-Warning', 'The current SDK is built for Appwrite '.$responseFormat.'. However, the current Appwrite server version is '.APP_VERSION_STABLE.'. Please downgrade your SDK to match the Appwrite version: https://appwrite.io/docs/sdks'); + $response->addHeader('X-Appwrite-Warning', "The current SDK is built for Appwrite " . $responseFormat . ". However, the current Appwrite server version is ". APP_VERSION_STABLE . ". Please downgrade your SDK to match the Appwrite version: https://appwrite.io/docs/sdks"); } } @@ -575,12 +576,12 @@ App::init() throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.'); } - return $response->redirect('https://'.$request->getHostname().$request->getURI()); + return $response->redirect('https://' . $request->getHostname() . $request->getURI()); } } if ($request->getProtocol() === 'https') { - $response->addHeader('Strict-Transport-Security', 'max-age='.(60 * 60 * 24 * 126)); // 126 days + $response->addHeader('Strict-Transport-Security', 'max-age=' . (60 * 60 * 24 * 126)); // 126 days } $response @@ -601,7 +602,7 @@ App::init() $originValidator = new Origin(\array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', []))); if ( - ! $originValidator->isValid($origin) + !$originValidator->isValid($origin) && \in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH, Request::METHOD_DELETE]) && $route->getLabel('origin', false) !== '*' && empty($request->getHeader('x-appwrite-key', '')) @@ -665,17 +666,17 @@ App::error() $trace = $error->getTrace(); if (php_sapi_name() === 'cli') { - Console::error('[Error] Timestamp: '.date('c', time())); + Console::error('[Error] Timestamp: ' . date('c', time())); if ($route) { - Console::error('[Error] Method: '.$route->getMethod()); - Console::error('[Error] URL: '.$route->getPath()); + Console::error('[Error] Method: ' . $route->getMethod()); + Console::error('[Error] URL: ' . $route->getPath()); } - Console::error('[Error] Type: '.get_class($error)); - Console::error('[Error] Message: '.$message); - Console::error('[Error] File: '.$file); - Console::error('[Error] Line: '.$line); + Console::error('[Error] Type: ' . get_class($error)); + Console::error('[Error] Message: ' . $message); + Console::error('[Error] File: ' . $file); + Console::error('[Error] Line: ' . $line); } switch ($class) { @@ -730,12 +731,12 @@ App::error() $providerName = System::getEnv('_APP_EXPERIMENT_LOGGING_PROVIDER', ''); $providerConfig = System::getEnv('_APP_EXPERIMENT_LOGGING_CONFIG', ''); - if (! (empty($providerName) || empty($providerConfig))) { - if (! Logger::hasProvider($providerName)) { - throw new Exception('Logging provider not supported. Logging is disabled'); + if (!(empty($providerName) || empty($providerConfig))) { + if (!Logger::hasProvider($providerName)) { + throw new Exception("Logging provider not supported. Logging is disabled"); } - $classname = '\\Utopia\\Logger\\Adapter\\'.\ucfirst($providerName); + $classname = '\\Utopia\\Logger\\Adapter\\' . \ucfirst($providerName); $adapter = new $classname($providerConfig); $logger = new Logger($adapter); $logger->setSample(0.04); @@ -744,10 +745,10 @@ App::error() } if ($publish && $project->getId() !== 'console') { - if (! Auth::isPrivilegedUser(Authorization::getRoles())) { + if (!Auth::isPrivilegedUser(Authorization::getRoles())) { $fileSize = 0; $file = $request->getFiles('file'); - if (! empty($file)) { + if (!empty($file)) { $fileSize = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size']; } @@ -762,6 +763,7 @@ App::error() ->trigger(); } + if ($logger && $publish) { try { /** @var Utopia\Database\Document $user */ @@ -770,7 +772,7 @@ App::error() // All good, user is optional information for logger } - if (isset($user) && ! $user->isEmpty()) { + if (isset($user) && !$user->isEmpty()) { $log->setUser(new User($user->getId())); } @@ -778,10 +780,10 @@ App::error() $dsn = new DSN($project->getAttribute('database', 'console')); } catch (\InvalidArgumentException) { // TODO: Temporary until all projects are using shared tables - $dsn = new DSN('mysql://'.$project->getAttribute('database', 'console')); + $dsn = new DSN('mysql://' . $project->getAttribute('database', 'console')); } - $log->setNamespace('http'); + $log->setNamespace("http"); $log->setServer(\gethostname()); $log->setVersion($version); $log->setType(Log::TYPE_ERROR); @@ -794,25 +796,25 @@ App::error() $log->addTag('code', $error->getCode()); $log->addTag('projectId', $project->getId()); $log->addTag('hostname', $request->getHostname()); - $log->addTag('locale', (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', ''))); + $log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', ''))); $log->addExtra('file', $error->getFile()); $log->addExtra('line', $error->getLine()); $log->addExtra('trace', $error->getTraceAsString()); $log->addExtra('roles', Authorization::getRoles()); - $action = $route->getLabel('sdk.namespace', 'UNKNOWN_NAMESPACE').'.'.$route->getLabel('sdk.method', 'UNKNOWN_METHOD'); + $action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD"); $log->setAction($action); $isProduction = System::getEnv('_APP_ENV', 'development') === 'production'; $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); $responseCode = $logger->addLog($log); - Console::info('Log pushed with status code: '.$responseCode); + Console::info('Log pushed with status code: ' . $responseCode); } /** Wrap all exceptions inside Appwrite\Extend\Exception */ - if (! ($error instanceof AppwriteException)) { + if (!($error instanceof AppwriteException)) { $error = new AppwriteException(AppwriteException::GENERAL_UNKNOWN, $message, $code, $error); } @@ -867,7 +869,7 @@ App::error() $layout = new View($template); $layout - ->setParam('title', $project->getAttribute('name').' - Error') + ->setParam('title', $project->getAttribute('name') . ' - Error') ->setParam('development', App::isDevelopment()) ->setParam('projectName', $project->getAttribute('name')) ->setParam('projectURL', $project->getAttribute('url')) @@ -903,7 +905,7 @@ App::get('/robots.txt') $mainDomain = System::getEnv('_APP_DOMAIN', ''); if ($host === $mainDomain) { - $template = new View(__DIR__.'/../views/general/robots.phtml'); + $template = new View(__DIR__ . '/../views/general/robots.phtml'); $response->text($template->render(false)); } else { router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb); @@ -928,7 +930,7 @@ App::get('/humans.txt') $mainDomain = System::getEnv('_APP_DOMAIN', ''); if ($host === $mainDomain) { - $template = new View(__DIR__.'/../views/general/humans.phtml'); + $template = new View(__DIR__ . '/../views/general/humans.phtml'); $response->text($template->render(false)); } else { router($utopia, $dbForConsole, $getProjectDB, $swooleRequest, $request, $response, $queueForEvents, $queueForUsage, $geodb); @@ -950,43 +952,43 @@ App::get('/.well-known/acme-challenge/*') ...Text::ALPHABET_LOWER, ...Text::ALPHABET_UPPER, '-', - '_', + '_' ]); - if (! $validator->isValid($token) || \count($uriChunks) !== 4) { + if (!$validator->isValid($token) || \count($uriChunks) !== 4) { throw new AppwriteException(AppwriteException::GENERAL_ARGUMENT_INVALID, 'Invalid challenge token.'); } $base = \realpath(APP_STORAGE_CERTIFICATES); - $absolute = \realpath($base.'/.well-known/acme-challenge/'.$token); + $absolute = \realpath($base . '/.well-known/acme-challenge/' . $token); - if (! $base) { + if (!$base) { throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Storage error'); } - if (! $absolute) { + if (!$absolute) { throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND, 'Unknown path'); } - if (! \substr($absolute, 0, \strlen($base)) === $base) { + if (!\substr($absolute, 0, \strlen($base)) === $base) { throw new AppwriteException(AppwriteException::GENERAL_UNAUTHORIZED_SCOPE, 'Invalid path'); } - if (! \file_exists($absolute)) { + if (!\file_exists($absolute)) { throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND, 'Unknown path'); } $content = @\file_get_contents($absolute); - if (! $content) { + if (!$content) { throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Failed to get contents'); } $response->text($content); }); -include_once __DIR__.'/shared/api.php'; -include_once __DIR__.'/shared/api/auth.php'; +include_once __DIR__ . '/shared/api.php'; +include_once __DIR__ . '/shared/api/auth.php'; App::wildcard() ->groups(['api']) diff --git a/app/init.php b/app/init.php index 9a4de8191b..2cdaaade32 100644 --- a/app/init.php +++ b/app/init.php @@ -110,7 +110,7 @@ const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours const APP_USER_ACCCESS = 24 * 60 * 60; // 24 hours const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours -const APP_CACHE_BUSTER = 4323; +const APP_CACHE_BUSTER = 4327; const APP_VERSION_STABLE = '1.5.8'; const APP_DATABASE_ATTRIBUTE_EMAIL = 'email'; const APP_DATABASE_ATTRIBUTE_ENUM = 'enum'; diff --git a/app/realtime.php b/app/realtime.php index 28e6c1691f..9c3c2b4d6a 100644 --- a/app/realtime.php +++ b/app/realtime.php @@ -380,8 +380,10 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats, $user = $database->getDocument('users', $userId); $roles = Auth::getRoles($user); + $channels = $realtime->connections[$connection]['channels']; - $realtime->subscribe($projectId, $connection, $roles, $realtime->connections[$connection]['channels']); + $realtime->unsubscribe($connection); + $realtime->subscribe($projectId, $connection, $roles, $channels); $register->get('pools')->reclaim(); } diff --git a/composer.json b/composer.json index 25659434c9..6fa56a4a31 100644 --- a/composer.json +++ b/composer.json @@ -69,7 +69,7 @@ "utopia-php/storage": "0.18.*", "utopia-php/swoole": "0.8.*", "utopia-php/system": "0.8.*", - "utopia-php/vcs": "0.6.*", + "utopia-php/vcs": "0.8.*", "utopia-php/websocket": "0.1.*", "matomo/device-detector": "6.1.*", "dragonmantank/cron-expression": "3.3.2", diff --git a/composer.lock b/composer.lock index c44d9ad2d2..cd1cdf2a11 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "7a9af1063aac524865d2479ce3957d0e", + "content-hash": "b5c0db330bf30bd1d240b96116d96baf", "packages": [ { "name": "adhocore/jwt", @@ -2758,16 +2758,16 @@ }, { "name": "utopia-php/vcs", - "version": "0.6.7", + "version": "0.8.1", "source": { "type": "git", "url": "https://github.com/utopia-php/vcs.git", - "reference": "8d8ff1ac68e991b95adb6f91fcde8f9bb8f24974" + "reference": "3084aa93d24ed1e70f01e75f4318fc0d07f12596" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/vcs/zipball/8d8ff1ac68e991b95adb6f91fcde8f9bb8f24974", - "reference": "8d8ff1ac68e991b95adb6f91fcde8f9bb8f24974", + "url": "https://api.github.com/repos/utopia-php/vcs/zipball/3084aa93d24ed1e70f01e75f4318fc0d07f12596", + "reference": "3084aa93d24ed1e70f01e75f4318fc0d07f12596", "shasum": "" }, "require": { @@ -2801,9 +2801,9 @@ ], "support": { "issues": "https://github.com/utopia-php/vcs/issues", - "source": "https://github.com/utopia-php/vcs/tree/0.6.7" + "source": "https://github.com/utopia-php/vcs/tree/0.8.1" }, - "time": "2024-06-05T17:38:29+00:00" + "time": "2024-07-29T20:49:09+00:00" }, { "name": "utopia-php/websocket", diff --git a/src/Appwrite/Platform/Tasks/Migrate.php b/src/Appwrite/Platform/Tasks/Migrate.php index 21f83109ff..4d921ff137 100644 --- a/src/Appwrite/Platform/Tasks/Migrate.php +++ b/src/Appwrite/Platform/Tasks/Migrate.php @@ -3,8 +3,8 @@ namespace Appwrite\Platform\Tasks; use Appwrite\Migration\Migration; +use Redis; use Utopia\App; -use Utopia\Cache\Cache; use Utopia\CLI\Console; use Utopia\Database\Database; use Utopia\Database\Document; @@ -12,10 +12,13 @@ use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Platform\Action; use Utopia\Registry\Registry; +use Utopia\System\System; use Utopia\Validator\Text; class Migrate extends Action { + protected Redis $redis; + public static function getName(): string { return 'migrate'; @@ -27,34 +30,54 @@ class Migrate extends Action ->desc('Migrate Appwrite to new version') /** @TODO APP_VERSION_STABLE needs to be defined */ ->param('version', APP_VERSION_STABLE, new Text(8), 'Version to migrate to.', true) - ->inject('cache') ->inject('dbForConsole') ->inject('getProjectDB') ->inject('register') - ->callback(fn ($version, $cache, $dbForConsole, $getProjectDB, Registry $register) => $this->action($version, $cache, $dbForConsole, $getProjectDB, $register)); + ->callback(fn ($version, $dbForConsole, $getProjectDB, Registry $register) => $this->action($version, $dbForConsole, $getProjectDB, $register)); + } - private function clearProjectsCache(Cache $cache, Document $project) + private function clearProjectsCache(Document $project) { try { - $cache->purge("cache-_{$project->getInternalId()}:*"); + do { + $iterator = null; + $pattern = "default-cache-_{$project->getInternalId()}:*"; + $keys = $this->redis->scan($iterator, $pattern, 1000); + if ($keys !== false) { + foreach ($keys as $key) { + $this->redis->del($key); + } + } + } while ($iterator > 0); + } catch (\Throwable $th) { - Console::error('Failed to clear project ("' . $project->getId() . '") cache with error: ' . $th->getMessage()); + Console::error('Failed to clear project ("'.$project->getId().'") cache with error: '.$th->getMessage()); } } - public function action(string $version, Cache $cache, Database $dbForConsole, callable $getProjectDB, Registry $register) + public function action(string $version, Database $dbForConsole, callable $getProjectDB, Registry $register) { Authorization::disable(); - if (!array_key_exists($version, Migration::$versions)) { + if (! array_key_exists($version, Migration::$versions)) { Console::error("Version {$version} not found."); Console::exit(1); + return; } + $this->redis = new Redis(); + $this->redis->connect( + System::getEnv('_APP_REDIS_HOST', null), + System::getEnv('_APP_REDIS_PORT', 6379), + 3, + null, + 10 + ); + $app = new App('UTC'); - Console::success('Starting Data Migration to version ' . $version); + Console::success('Starting Data Migration to version '.$version); $console = $app->getResource('console'); @@ -74,11 +97,11 @@ class Migrate extends Action $totalProjects = $dbForConsole->count('projects') + 1; } - $class = 'Appwrite\\Migration\\Version\\' . Migration::$versions[$version]; + $class = 'Appwrite\\Migration\\Version\\'.Migration::$versions[$version]; /** @var Migration $migration */ $migration = new $class(); - while (!empty($projects)) { + while (! empty($projects)) { foreach ($projects as $project) { /** * Skip user projects with id 'console' @@ -87,7 +110,7 @@ class Migrate extends Action continue; } - $this->clearProjectsCache($cache, $project); + $this->clearProjectsCache($project); try { // TODO: Iterate through all project DBs @@ -99,11 +122,11 @@ class Migrate extends Action ->setPDO($register->get('db', true)) ->execute(); } catch (\Throwable $th) { - Console::error('Failed to update project ("' . $project->getId() . '") version with error: ' . $th->getMessage()); + Console::error('Failed to update project ("'.$project->getId().'") version with error: '.$th->getMessage()); throw $th; } - $this->clearProjectsCache($cache, $project); + $this->clearProjectsCache($project); } $sum = \count($projects); @@ -112,7 +135,7 @@ class Migrate extends Action $offset = $offset + $limit; $count = $count + $sum; - Console::log('Migrated ' . $count . '/' . $totalProjects . ' projects...'); + Console::log('Migrated '.$count.'/'.$totalProjects.' projects...'); } Console::success('Data Migration Completed'); diff --git a/src/Appwrite/Platform/Workers/Builds.php b/src/Appwrite/Platform/Workers/Builds.php index 1f2886713f..1e1486b9df 100644 --- a/src/Appwrite/Platform/Workers/Builds.php +++ b/src/Appwrite/Platform/Workers/Builds.php @@ -217,7 +217,15 @@ class Builds extends Action $branchName = $deployment->getAttribute('providerBranch'); $commitHash = $deployment->getAttribute('providerCommitHash', ''); - $gitCloneCommand = $github->generateCloneCommand($cloneOwner, $cloneRepository, $branchName, $tmpDirectory, $rootDirectory, $commitHash); + + $cloneVersion = $branchName; + $cloneType = GitHub::CLONE_TYPE_BRANCH; + if(!empty($commitHash)) { + $cloneVersion = $commitHash; + $cloneType = GitHub::CLONE_TYPE_COMMIT; + } + + $gitCloneCommand = $github->generateCloneCommand($cloneOwner, $cloneRepository, $cloneVersion, $cloneType, $tmpDirectory, $rootDirectory); $stdout = ''; $stderr = ''; Console::execute('mkdir -p /tmp/builds/' . \escapeshellcmd($buildId), '', $stdout, $stderr); @@ -240,7 +248,13 @@ class Builds extends Action if (!empty($templateRepositoryName) && !empty($templateOwnerName) && !empty($templateBranch)) { // Clone template repo $tmpTemplateDirectory = '/tmp/builds/' . \escapeshellcmd($buildId) . '/template'; - $gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateBranch, $tmpTemplateDirectory, $templateRootDirectory); + + $cloneType = GitHub::CLONE_TYPE_BRANCH; + if(\str_starts_with($templateBranch, '0.1.')) { // Temporary fix for 1.5. In future versions this only support tag names + $cloneType = GitHub::CLONE_TYPE_TAG; + } + + $gitCloneCommandForTemplate = $github->generateCloneCommand($templateOwnerName, $templateRepositoryName, $templateBranch, $cloneType, $tmpTemplateDirectory, $templateRootDirectory); $exit = Console::execute($gitCloneCommandForTemplate, '', $stdout, $stderr); if ($exit !== 0) { diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 9031faf8d0..2d9ddb04e9 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -8,6 +8,7 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideServer; +use Utopia\App; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Query; @@ -1569,6 +1570,20 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals($cookie, $response['body']); + // Await Aggregation + sleep(App::getEnv('_APP_USAGE_AGGREGATION_INTERVAL', 30)); + + $response = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/usage', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'] + ], $this->getHeaders()), [ + 'range' => '24h' + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(19, count($response['body'])); + $this->assertEquals('24h', $response['body']['range']); + // Cleanup : Delete function $response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [ 'content-type' => 'application/json',