mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d0950b8bc | ||
|
|
647b4bc632 | ||
|
|
d38c921781 | ||
|
|
32728ae4a0 |
@@ -150,16 +150,8 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: composer install --prefer-dist --no-progress --ignore-platform-reqs
|
||||
|
||||
- name: Cache PHPStan result cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: .phpstan-cache
|
||||
key: phpstan-${{ github.sha }}
|
||||
restore-keys: |
|
||||
phpstan-
|
||||
|
||||
- name: Run PHPStan
|
||||
run: composer analyze -- --no-progress
|
||||
run: composer analyze
|
||||
|
||||
locale:
|
||||
name: Checks / Locale
|
||||
@@ -188,38 +180,11 @@ jobs:
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const allDatabases = ['MariaDB', 'PostgreSQL', 'MongoDB'];
|
||||
const allModes = ['dedicated', 'shared_v1', 'shared_v2'];
|
||||
const allDatabases = ['MongoDB'];
|
||||
const allModes = ['shared_v1', 'shared_v2'];
|
||||
|
||||
const defaultDatabases = ['MongoDB'];
|
||||
const defaultModes = ['dedicated'];
|
||||
|
||||
const pr = context.payload.pull_request;
|
||||
if (!pr) {
|
||||
core.setOutput('databases', JSON.stringify(allDatabases));
|
||||
core.setOutput('modes', JSON.stringify(allModes));
|
||||
return;
|
||||
}
|
||||
|
||||
const getContent = (ref) => github.rest.repos.getContent({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
path: 'composer.lock',
|
||||
ref,
|
||||
});
|
||||
|
||||
const getDbVersion = (lock) => lock.packages?.find(p => p.name === 'utopia-php/database')?.version;
|
||||
|
||||
const [{ data: base }, { data: head }] = await Promise.all([
|
||||
getContent(pr.base.sha),
|
||||
getContent(pr.head.sha),
|
||||
]);
|
||||
|
||||
const decode = (content) => JSON.parse(Buffer.from(content, 'base64').toString());
|
||||
const databaseChanged = getDbVersion(decode(base.content)) !== getDbVersion(decode(head.content));
|
||||
|
||||
core.setOutput('databases', JSON.stringify(databaseChanged ? allDatabases : defaultDatabases));
|
||||
core.setOutput('modes', JSON.stringify(databaseChanged ? allModes : defaultModes));
|
||||
core.setOutput('databases', JSON.stringify(allDatabases));
|
||||
core.setOutput('modes', JSON.stringify(allModes));
|
||||
|
||||
build:
|
||||
name: Build
|
||||
|
||||
@@ -21,7 +21,6 @@ appwrite.config.json
|
||||
/app/config/specs/
|
||||
/docs/examples/
|
||||
.phpunit.cache
|
||||
.phpstan-cache
|
||||
playwright-report
|
||||
test-results
|
||||
docker-compose.web-installer.yml
|
||||
|
||||
@@ -72,7 +72,6 @@ Before running the installation command, make sure you have [Docker](https://www
|
||||
|
||||
```bash
|
||||
docker run -it --rm \
|
||||
--publish 20080:20080 \
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock \
|
||||
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
|
||||
--entrypoint="install" \
|
||||
@@ -85,7 +84,6 @@ docker run -it --rm \
|
||||
|
||||
```cmd
|
||||
docker run -it --rm ^
|
||||
--publish 20080:20080 ^
|
||||
--volume //var/run/docker.sock:/var/run/docker.sock ^
|
||||
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
|
||||
--entrypoint="install" ^
|
||||
@@ -96,7 +94,6 @@ docker run -it --rm ^
|
||||
|
||||
```powershell
|
||||
docker run -it --rm `
|
||||
--publish 20080:20080 `
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock `
|
||||
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
|
||||
--entrypoint="install" `
|
||||
|
||||
@@ -31,6 +31,9 @@ class FunctionUseCases
|
||||
public const DEV_TOOLS = 'dev-tools';
|
||||
public const AUTH = 'auth';
|
||||
|
||||
/**
|
||||
* @var array<string>
|
||||
*/
|
||||
public static function getAll(): array
|
||||
{
|
||||
return [
|
||||
|
||||
@@ -25,6 +25,9 @@ class SiteUseCases
|
||||
public const FORMS = 'forms';
|
||||
public const DASHBOARD = 'dashboard';
|
||||
|
||||
/**
|
||||
* @var array<string>
|
||||
*/
|
||||
public static function getAll(): array
|
||||
{
|
||||
return [
|
||||
@@ -249,7 +252,7 @@ return [
|
||||
'frameworks' => [
|
||||
getFramework('VITE', [
|
||||
'providerRootDirectory' => './vite/vitepress',
|
||||
'fallbackFile' => '404.html',
|
||||
'outputDirectory' => '404.html',
|
||||
'installCommand' => 'npm i vitepress && npm install',
|
||||
'buildCommand' => 'npm run docs:build',
|
||||
'outputDirectory' => './.vitepress/dist',
|
||||
@@ -272,7 +275,7 @@ return [
|
||||
'frameworks' => [
|
||||
getFramework('VUE', [
|
||||
'providerRootDirectory' => './vue/vuepress',
|
||||
'fallbackFile' => '404.html',
|
||||
'outputDirectory' => '404.html',
|
||||
'installCommand' => 'npm install',
|
||||
'buildCommand' => 'npm run build',
|
||||
'outputDirectory' => './src/.vuepress/dist',
|
||||
@@ -295,7 +298,7 @@ return [
|
||||
'frameworks' => [
|
||||
getFramework('REACT', [
|
||||
'providerRootDirectory' => './react/docusaurus',
|
||||
'fallbackFile' => '404.html',
|
||||
'outputDirectory' => '404.html',
|
||||
'installCommand' => 'npm install',
|
||||
'buildCommand' => 'npm run build',
|
||||
'outputDirectory' => './build',
|
||||
|
||||
@@ -695,7 +695,6 @@ Http::delete('/v1/account/sessions')
|
||||
|
||||
$protocol = $request->getProtocol();
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
$currentSession = null;
|
||||
|
||||
foreach ($sessions as $session) {/** @var Document $session */
|
||||
$dbForProject->deleteDocument('sessions', $session->getId());
|
||||
@@ -717,7 +716,6 @@ Http::delete('/v1/account/sessions')
|
||||
->addCookie($store->getKey(), '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'));
|
||||
|
||||
// Use current session for events.
|
||||
$currentSession = $session;
|
||||
$queueForEvents
|
||||
->setPayload($response->output($session, Response::MODEL_SESSION));
|
||||
|
||||
@@ -730,11 +728,9 @@ Http::delete('/v1/account/sessions')
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
if ($currentSession instanceof Document) {
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('sessionId', $currentSession->getId());
|
||||
}
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
->setParam('sessionId', $session->getId());
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
@@ -780,8 +776,7 @@ Http::get('/v1/account/sessions/:sessionId')
|
||||
->setAttribute('secret', $session->getAttribute('secret', ''))
|
||||
;
|
||||
|
||||
$response->dynamic($session, Response::MODEL_SESSION);
|
||||
return;
|
||||
return $response->dynamic($session, Response::MODEL_SESSION);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -961,7 +956,7 @@ Http::patch('/v1/account/sessions/:sessionId')
|
||||
->setPayload($response->output($session, Response::MODEL_SESSION))
|
||||
;
|
||||
|
||||
$response->dynamic($session, Response::MODEL_SESSION);
|
||||
return $response->dynamic($session, Response::MODEL_SESSION);
|
||||
});
|
||||
|
||||
Http::post('/v1/account/sessions/email')
|
||||
@@ -1993,7 +1988,7 @@ Http::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
->addCookie($store->getKey(), $encoded, (new \DateTime($expire))->getTimestamp(), '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'));
|
||||
}
|
||||
|
||||
if (isset($sessionUpgrade) && $sessionUpgrade && isset($session)) {
|
||||
if (isset($sessionUpgrade) && $sessionUpgrade) {
|
||||
foreach ($user->getAttribute('targets', []) as $target) {
|
||||
if ($target->getAttribute('providerType') !== MESSAGE_TYPE_PUSH) {
|
||||
continue;
|
||||
@@ -4720,5 +4715,5 @@ Http::delete('/v1/account/identities/:identityId')
|
||||
->setParam('identityId', $identity->getId())
|
||||
->setPayload($response->output($identity, Response::MODEL_IDENTITY));
|
||||
|
||||
$response->noContent();
|
||||
return $response->noContent();
|
||||
});
|
||||
|
||||
@@ -231,7 +231,6 @@ Http::get('/v1/locale/continents')
|
||||
->inject('locale')
|
||||
->action(function (Response $response, Locale $locale) {
|
||||
$list = array_keys(Config::getParam('locale-continents'));
|
||||
$output = [];
|
||||
|
||||
foreach ($list as $value) {
|
||||
$output[] = new Document([
|
||||
|
||||
@@ -3566,7 +3566,7 @@ Http::post('/v1/messaging/messages/push')
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$endpoint = "$protocol://{$platform['apiHostname']}/v1";
|
||||
|
||||
$scheduleTime = $scheduledAt;
|
||||
$scheduleTime = $currentScheduledAt ?? $scheduledAt;
|
||||
if (!\is_null($scheduleTime)) {
|
||||
$expiry = (new \DateTime($scheduleTime))->add(new \DateInterval('P15D'))->format('U');
|
||||
} else {
|
||||
|
||||
@@ -1570,7 +1570,7 @@ Http::post('/v1/projects/:projectId/smtp/tests')
|
||||
->trigger();
|
||||
}
|
||||
|
||||
$response->noContent();
|
||||
return $response->noContent();
|
||||
});
|
||||
|
||||
Http::get('/v1/projects/:projectId/templates/sms/:type/:locale')
|
||||
|
||||
@@ -2337,8 +2337,9 @@ Http::post('/v1/users/:userId/sessions')
|
||||
->setParam('sessionId', $session->getId())
|
||||
->setPayload($response->output($session, Response::MODEL_SESSION));
|
||||
|
||||
$response->setStatusCode(Response::STATUS_CODE_CREATED);
|
||||
$response->dynamic($session, Response::MODEL_SESSION);
|
||||
return $response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($session, Response::MODEL_SESSION);
|
||||
});
|
||||
|
||||
Http::post('/v1/users/:userId/tokens')
|
||||
@@ -2401,8 +2402,9 @@ Http::post('/v1/users/:userId/tokens')
|
||||
->setParam('tokenId', $token->getId())
|
||||
->setPayload($response->output($token, Response::MODEL_TOKEN));
|
||||
|
||||
$response->setStatusCode(Response::STATUS_CODE_CREATED);
|
||||
$response->dynamic($token, Response::MODEL_TOKEN);
|
||||
return $response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($token, Response::MODEL_TOKEN);
|
||||
});
|
||||
|
||||
Http::delete('/v1/users/:userId/sessions/:sessionId')
|
||||
@@ -2656,7 +2658,7 @@ Http::delete('/v1/users/identities/:identityId')
|
||||
->setParam('identityId', $identity->getId())
|
||||
->setPayload($response->output($identity, Response::MODEL_IDENTITY));
|
||||
|
||||
$response->noContent();
|
||||
return $response->noContent();
|
||||
});
|
||||
|
||||
Http::post('/v1/users/:userId/jwts')
|
||||
|
||||
+10
-17
@@ -166,14 +166,14 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
|
||||
if ($request->getMethod() !== Request::METHOD_GET) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.', view: $errorView);
|
||||
}
|
||||
$response->redirect('https://' . $request->getHostname() . $request->getURI());
|
||||
return false;
|
||||
return $response->redirect('https://' . $request->getHostname() . $request->getURI());
|
||||
}
|
||||
}
|
||||
|
||||
/** @var Database $dbForProject */
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
/** @var Document $deployment */
|
||||
if (!empty($rule->getAttribute('deploymentId', ''))) {
|
||||
$deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('deploymentId')));
|
||||
} else {
|
||||
@@ -244,7 +244,6 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
|
||||
if ($isPreview && $requirePreview) {
|
||||
$cookie = $request->getCookie(COOKIE_NAME_PREVIEW, '');
|
||||
$authorized = false;
|
||||
$user = new Document();
|
||||
|
||||
// Security checks to mark authorized true
|
||||
if (!empty($cookie)) {
|
||||
@@ -274,7 +273,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
|
||||
|
||||
$membershipExists = false;
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
|
||||
if (!$project->isEmpty() && !$user->isEmpty()) {
|
||||
if (!$project->isEmpty() && isset($user)) {
|
||||
$teamId = $project->getAttribute('teamId', '');
|
||||
$membership = $user->find('teamId', $teamId, 'memberships');
|
||||
if (!empty($membership)) {
|
||||
@@ -380,7 +379,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
|
||||
$executionId = ID::unique();
|
||||
|
||||
$headers = \array_merge([], $requestHeaders);
|
||||
$headers['x-appwrite-execution-id'] = $executionId;
|
||||
$headers['x-appwrite-execution-id'] = $executionId ?? '';
|
||||
$headers['x-appwrite-user-id'] = '';
|
||||
$headers['x-appwrite-country-code'] = '';
|
||||
$headers['x-appwrite-continent-code'] = '';
|
||||
@@ -460,7 +459,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
|
||||
if ($version === 'v2') {
|
||||
$vars = \array_merge($vars, [
|
||||
'APPWRITE_FUNCTION_TRIGGER' => $headers['x-appwrite-trigger'] ?? '',
|
||||
'APPWRITE_FUNCTION_DATA' => $body,
|
||||
'APPWRITE_FUNCTION_DATA' => $body ?? '',
|
||||
'APPWRITE_FUNCTION_USER_ID' => $headers['x-appwrite-user-id'] ?? '',
|
||||
'APPWRITE_FUNCTION_JWT' => $headers['x-appwrite-user-jwt'] ?? ''
|
||||
]);
|
||||
@@ -530,11 +529,6 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
|
||||
}
|
||||
|
||||
/** Execute function */
|
||||
$executionResponse = [
|
||||
'headers' => [],
|
||||
'body' => '',
|
||||
];
|
||||
|
||||
try {
|
||||
$version = match ($type) {
|
||||
'function' => $resource->getAttribute('version', 'v2'),
|
||||
@@ -740,7 +734,7 @@ function router(Http $utopia, Database $dbForPlatform, callable $getProjectDB, S
|
||||
$execution->setAttribute('responseBody', $executionResponse['body'] ?? '');
|
||||
$execution->setAttribute('responseHeaders', $headers);
|
||||
|
||||
$body = $execution['responseBody'];
|
||||
$body = $execution['responseBody'] ?? '';
|
||||
|
||||
$contentType = 'text/plain';
|
||||
foreach ($executionResponse['headers'] as $name => $values) {
|
||||
@@ -871,9 +865,9 @@ Http::init()
|
||||
Request::setRoute($route);
|
||||
|
||||
if ($route === null) {
|
||||
$response->setStatusCode(404);
|
||||
$response->send('Not Found');
|
||||
return;
|
||||
return $response
|
||||
->setStatusCode(404)
|
||||
->send('Not Found');
|
||||
}
|
||||
|
||||
$requestFormat = $request->getHeader('x-appwrite-response-format', System::getEnv('_APP_SYSTEM_RESPONSE_FORMAT', ''));
|
||||
@@ -979,8 +973,7 @@ Http::init()
|
||||
throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP. Please use HTTPS instead.');
|
||||
}
|
||||
|
||||
$response->redirect('https://' . $request->getHostname() . $request->getURI());
|
||||
return;
|
||||
return $response->redirect('https://' . $request->getHostname() . $request->getURI());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+28
-30
@@ -244,38 +244,36 @@ Http::get('/v1/mock/github/callback')
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND, $error);
|
||||
}
|
||||
|
||||
if (empty($providerInstallationId)) {
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Missing provider installation ID');
|
||||
if (!empty($providerInstallationId)) {
|
||||
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
$owner = $github->getOwnerName($providerInstallationId) ?? '';
|
||||
|
||||
$projectInternalId = $project->getSequence();
|
||||
|
||||
$teamId = $project->getAttribute('teamId', '');
|
||||
|
||||
$installation = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team(ID::custom($teamId))),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'developer')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
|
||||
],
|
||||
'providerInstallationId' => $providerInstallationId,
|
||||
'projectId' => $projectId,
|
||||
'projectInternalId' => $projectInternalId,
|
||||
'provider' => 'github',
|
||||
'organization' => $owner,
|
||||
'personal' => false
|
||||
]);
|
||||
|
||||
$installation = $dbForPlatform->createDocument('installations', $installation);
|
||||
}
|
||||
|
||||
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
$owner = $github->getOwnerName($providerInstallationId) ?? '';
|
||||
|
||||
$projectInternalId = $project->getSequence();
|
||||
|
||||
$teamId = $project->getAttribute('teamId', '');
|
||||
|
||||
$installation = new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team(ID::custom($teamId))),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::update(Role::team(ID::custom($teamId), 'developer')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
|
||||
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
|
||||
],
|
||||
'providerInstallationId' => $providerInstallationId,
|
||||
'projectId' => $projectId,
|
||||
'projectInternalId' => $projectInternalId,
|
||||
'provider' => 'github',
|
||||
'organization' => $owner,
|
||||
'personal' => false
|
||||
]);
|
||||
|
||||
$installation = $dbForPlatform->createDocument('installations', $installation);
|
||||
|
||||
$response->json([
|
||||
'installationId' => $installation->getId(),
|
||||
]);
|
||||
|
||||
+3
-4
@@ -3,8 +3,6 @@
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
require_once __DIR__ . '/init/span.php';
|
||||
|
||||
global $register;
|
||||
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Swoole\Constant;
|
||||
@@ -33,6 +31,7 @@ use Utopia\Http\Files;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Log\User;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Span\Span;
|
||||
use Utopia\System\System;
|
||||
|
||||
@@ -294,7 +293,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $tot
|
||||
|
||||
go(function () use ($register, $app) {
|
||||
$pools = $register->get('pools');
|
||||
/** @var \Utopia\Pools\Group $pools */
|
||||
/** @var Group $pools */
|
||||
Http::setResource('pools', fn () => $pools);
|
||||
|
||||
/** @var array $collections */
|
||||
@@ -656,7 +655,7 @@ $http->on(Constant::EVENT_TASK, function () use ($register) {
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
$dbForPlatform = $app->getResource('dbForPlatform');
|
||||
|
||||
/** @var \Swoole\Table $riskyDomains */
|
||||
/** @var Table $riskyDomains */
|
||||
$riskyDomains = $app->getResource('riskyDomains');
|
||||
|
||||
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $riskyDomains, &$lastSyncUpdate, $app) {
|
||||
|
||||
+36
-21
@@ -6,6 +6,7 @@ use Appwrite\Hooks\Hooks;
|
||||
use Appwrite\PubSub\Adapter\Redis as PubSub;
|
||||
use Appwrite\URL\URL as AppwriteURL;
|
||||
use MaxMind\Db\Reader;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Swoole\Database\PDOProxy;
|
||||
use Utopia\Cache\Adapter\Redis as RedisCache;
|
||||
use Utopia\Config\Config;
|
||||
@@ -24,7 +25,6 @@ use Utopia\Logger\Adapter\LogOwl;
|
||||
use Utopia\Logger\Adapter\Raygun;
|
||||
use Utopia\Logger\Adapter\Sentry;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Messaging\Adapter\Email\SMTP;
|
||||
use Utopia\Mongo\Client as MongoClient;
|
||||
use Utopia\Pools\Adapter\Stack as StackPool;
|
||||
use Utopia\Pools\Adapter\Swoole as SwoolePool;
|
||||
@@ -56,7 +56,7 @@ $register->set('logger', function () {
|
||||
}
|
||||
|
||||
try {
|
||||
$loggingProvider = new DSN($providerConfig);
|
||||
$loggingProvider = new DSN($providerConfig ?? '');
|
||||
|
||||
$providerName = $loggingProvider->getScheme();
|
||||
$providerConfig = match ($providerName) {
|
||||
@@ -76,7 +76,7 @@ $register->set('logger', function () {
|
||||
};
|
||||
}
|
||||
|
||||
if (empty($providerName)) {
|
||||
if (empty($providerName) || empty($providerConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ $register->set('realtimeLogger', function () {
|
||||
default => ['key' => $loggingProvider->getHost()],
|
||||
};
|
||||
|
||||
if (empty($providerName)) {
|
||||
if (empty($providerName) || empty($providerConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -242,8 +242,8 @@ $register->set('pools', function () {
|
||||
],
|
||||
];
|
||||
|
||||
$maxConnections = (int) System::getEnv('_APP_CONNECTIONS_MAX', 151);
|
||||
$instanceConnections = $maxConnections / (int) System::getEnv('_APP_POOL_CLIENTS', 14);
|
||||
$maxConnections = System::getEnv('_APP_CONNECTIONS_MAX', 151);
|
||||
$instanceConnections = $maxConnections / System::getEnv('_APP_POOL_CLIENTS', 14);
|
||||
|
||||
$multiprocessing = System::getEnv('_APP_SERVER_MULTIPROCESS', 'disabled') === 'enabled';
|
||||
|
||||
@@ -308,7 +308,7 @@ $register->set('pools', function () {
|
||||
]);
|
||||
});
|
||||
},
|
||||
'mongodb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase) {
|
||||
'mongodb' => function () use ($dsnHost, $dsnPort, $dsnUser, $dsnPass, $dsnDatabase, $dsn) {
|
||||
try {
|
||||
$mongo = new MongoClient($dsnDatabase, $dsnHost, (int)$dsnPort, $dsnUser, $dsnPass, false);
|
||||
@$mongo->connect();
|
||||
@@ -433,20 +433,35 @@ $register->set('db', function () {
|
||||
});
|
||||
|
||||
$register->set('smtp', function () {
|
||||
$username = System::getEnv('_APP_SMTP_USERNAME', '');
|
||||
$password = System::getEnv('_APP_SMTP_PASSWORD', '');
|
||||
return new SMTP(
|
||||
host: System::getEnv('_APP_SMTP_HOST', 'smtp'),
|
||||
port: (int) System::getEnv('_APP_SMTP_PORT', 25),
|
||||
username: $username,
|
||||
password: $password,
|
||||
smtpSecure: System::getEnv('_APP_SMTP_SECURE', ''),
|
||||
smtpAutoTLS: false,
|
||||
xMailer: 'Appwrite Mailer',
|
||||
timeout: 10,
|
||||
keepAlive: true,
|
||||
timelimit: 30,
|
||||
);
|
||||
$mail = new PHPMailer(true);
|
||||
|
||||
$mail->isSMTP();
|
||||
|
||||
$username = System::getEnv('_APP_SMTP_USERNAME');
|
||||
$password = System::getEnv('_APP_SMTP_PASSWORD');
|
||||
|
||||
$mail->XMailer = 'Appwrite Mailer';
|
||||
$mail->Host = System::getEnv('_APP_SMTP_HOST', 'smtp');
|
||||
$mail->Port = System::getEnv('_APP_SMTP_PORT', 25);
|
||||
$mail->SMTPAuth = !empty($username) && !empty($password);
|
||||
$mail->Username = $username;
|
||||
$mail->Password = $password;
|
||||
$mail->SMTPSecure = System::getEnv('_APP_SMTP_SECURE', '');
|
||||
$mail->SMTPAutoTLS = false;
|
||||
$mail->SMTPKeepAlive = true;
|
||||
$mail->CharSet = 'UTF-8';
|
||||
$mail->Timeout = 10; /* Connection timeout */
|
||||
$mail->getSMTPInstance()->Timelimit = 30; /* Timeout for each individual SMTP command (e.g. HELO, EHLO, etc.) */
|
||||
|
||||
$from = \urldecode(System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'));
|
||||
$email = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
|
||||
|
||||
$mail->setFrom($email, $from);
|
||||
$mail->addReplyTo($email, $from);
|
||||
|
||||
$mail->isHTML(true);
|
||||
|
||||
return $mail;
|
||||
});
|
||||
$register->set('geodb', function () {
|
||||
return new Reader(__DIR__ . '/../assets/dbip/dbip-country-lite-2025-12.mmdb');
|
||||
|
||||
+21
-20
@@ -696,7 +696,7 @@ Http::setResource('dbForProject', function (Group $pools, Database $dbForPlatfor
|
||||
$cacheKey = \sprintf(
|
||||
'%s-cache-%s:%s:%s:project:%s:functions:events',
|
||||
$dbForProject->getCacheName(),
|
||||
$hostname,
|
||||
$hostname ?? '',
|
||||
$dbForProject->getNamespace(),
|
||||
$dbForProject->getTenant(),
|
||||
$project->getId()
|
||||
@@ -888,7 +888,9 @@ Http::setResource('dbForPlatform', function (Group $pools, Cache $cache, Authori
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
Http::setResource('getDatabasesDB', function (Group $pools, Cache $cache, Document $project, Request $request, UsageContext $usage, Authorization $authorization) {
|
||||
return function (Document $database) use ($pools, $cache, $project, $request, $usage, $authorization): Database {
|
||||
$initializedPools = [];
|
||||
|
||||
return function (Document $database) use ($pools, $cache, $project, $request, $usage, $authorization, &$initializedPools): Database {
|
||||
$databaseDSN = $database->getAttribute('database', $project->getAttribute('database', ''));
|
||||
$databaseType = $database->getAttribute('type', '');
|
||||
|
||||
@@ -923,30 +925,29 @@ Http::setResource('getDatabasesDB', function (Group $pools, Cache $cache, Docume
|
||||
// inside pools authorization needs to be set first
|
||||
$database->getAdapter()->setSupportForAttributes($databaseType !== DOCUMENTSDB);
|
||||
|
||||
// For separate pools (documentsdb/vectorsdb), check their own shared tables config.
|
||||
// If not configured, use dedicated mode to avoid cross-engine tenant type mismatches.
|
||||
// When the database uses a separate pool (e.g. vectorsdb on PostgreSQL),
|
||||
// always use dedicated mode with namespace isolation. Shared tables mode
|
||||
// can't be used across different engines (e.g. MongoDB UUID tenants are
|
||||
// incompatible with PostgreSQL's integer _tenant column).
|
||||
if ($databaseHost !== $dsn->getHost()) {
|
||||
$dbTypeSharedTables = match ($databaseType) {
|
||||
DOCUMENTSDB => \array_filter(\explode(',', System::getEnv('_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES', ''))),
|
||||
VECTORSDB => \array_filter(\explode(',', System::getEnv('_APP_DATABASE_VECTORSDB_SHARED_TABLES', ''))),
|
||||
default => [],
|
||||
};
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $project->getSequence());
|
||||
|
||||
if (\in_array($databaseHost, $dbTypeSharedTables)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant((int)$project->getSequence())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $project->getSequence());
|
||||
$poolKey = $databaseHost . ':' . $database->getNamespace();
|
||||
if (!isset($initializedPools[$poolKey])) {
|
||||
try {
|
||||
$database->create();
|
||||
} catch (\Utopia\Database\Exception\Duplicate) {
|
||||
// Schema already exists
|
||||
}
|
||||
$initializedPools[$poolKey] = true;
|
||||
}
|
||||
} elseif (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant((int)$project->getSequence())
|
||||
->setTenant($project->getSequence())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
|
||||
+7
-6
@@ -237,6 +237,7 @@ if (!function_exists('getTelemetry')) {
|
||||
if (!function_exists('triggerStats')) {
|
||||
function triggerStats(array $event, string $projectId): void
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,14 +320,14 @@ if (!function_exists('logError')) {
|
||||
|
||||
$server->error(logError(...));
|
||||
|
||||
$server->onStart(function () use ($stats, $containerId, &$statsDocument) {
|
||||
$server->onStart(function () use ($stats, $register, $containerId, &$statsDocument) {
|
||||
sleep(5); // wait for the initial database schema to be ready
|
||||
Console::success('Server started successfully');
|
||||
|
||||
/**
|
||||
* Create document for this worker to share stats across Containers.
|
||||
*/
|
||||
go(function () use ($containerId, &$statsDocument) {
|
||||
go(function () use ($register, $containerId, &$statsDocument) {
|
||||
$attempts = 0;
|
||||
$database = getConsoleDB();
|
||||
|
||||
@@ -356,7 +357,7 @@ $server->onStart(function () use ($stats, $containerId, &$statsDocument) {
|
||||
*/
|
||||
// TODO: Remove this if check once it doesn't cause issues for cloud
|
||||
if (System::getEnv('_APP_EDITION', 'self-hosted') === 'self-hosted') {
|
||||
Timer::tick(5000, function () use ($stats, &$statsDocument) {
|
||||
Timer::tick(5000, function () use ($register, $stats, &$statsDocument) {
|
||||
$payload = [];
|
||||
foreach ($stats as $projectId => $value) {
|
||||
$payload[$projectId] = $stats->get($projectId, 'connectionsTotal');
|
||||
@@ -395,7 +396,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
$attempts = 0;
|
||||
$start = time();
|
||||
|
||||
Timer::tick(5000, function () use ($server, $realtime, $stats) {
|
||||
Timer::tick(5000, function () use ($server, $register, $realtime, $stats) {
|
||||
/**
|
||||
* Sending current connections to project channels on the console project every 5 seconds.
|
||||
*/
|
||||
@@ -797,7 +798,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
}
|
||||
});
|
||||
|
||||
$server->onMessage(function (int $connection, string $message) use ($server, $realtime, $containerId) {
|
||||
$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) {
|
||||
$project = null;
|
||||
$authorization = null;
|
||||
|
||||
@@ -809,7 +810,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
// Get authorization from connection (stored during onOpen)
|
||||
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
|
||||
if ($authorization === null) {
|
||||
$authorization = new Authorization();
|
||||
$authorization = new Authorization('');
|
||||
}
|
||||
|
||||
$database = getConsoleDB();
|
||||
|
||||
+22
-22
@@ -221,7 +221,9 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authoriza
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
Server::setResource('getDatabasesDB', function (Cache $cache, Registry $register, Document $project, Authorization $authorization) {
|
||||
return function (Document $database, ?Document $projectDocument = null) use ($cache, $register, $project, $authorization): Database {
|
||||
$initializedPools = [];
|
||||
|
||||
return function (Document $database, ?Document $projectDocument = null) use ($cache, $register, $project, $authorization, &$initializedPools): Database {
|
||||
$projectDocument ??= $project;
|
||||
$databaseDSN = $database->getAttribute('database', $project->getAttribute('database', ''));
|
||||
$databaseType = $database->getAttribute('type', '');
|
||||
@@ -258,30 +260,28 @@ Server::setResource('getDatabasesDB', function (Cache $cache, Registry $register
|
||||
|
||||
$sharedTables = \array_filter(\explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')));
|
||||
|
||||
// For separate pools (documentsdb/vectorsdb), check their own shared tables config.
|
||||
// If not configured, use dedicated mode to avoid cross-engine tenant type mismatches.
|
||||
// When using a separate pool, always use dedicated mode with namespace isolation.
|
||||
// Shared tables mode can't be used across different engines (e.g. MongoDB UUID
|
||||
// tenants are incompatible with PostgreSQL's integer _tenant column).
|
||||
if ($databaseHost !== $dsn->getHost()) {
|
||||
$dbTypeSharedTables = match ($databaseType) {
|
||||
DOCUMENTSDB => \array_filter(\explode(',', System::getEnv('_APP_DATABASE_DOCUMENTSDB_SHARED_TABLES', ''))),
|
||||
VECTORSDB => \array_filter(\explode(',', System::getEnv('_APP_DATABASE_VECTORSDB_SHARED_TABLES', ''))),
|
||||
default => [],
|
||||
};
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $projectDocument->getSequence());
|
||||
|
||||
if (\in_array($databaseHost, $dbTypeSharedTables)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant((int) $projectDocument->getSequence())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
->setSharedTables(false)
|
||||
->setTenant(null)
|
||||
->setNamespace('_' . $projectDocument->getSequence());
|
||||
$poolKey = $databaseHost . ':' . $database->getNamespace();
|
||||
if (!isset($initializedPools[$poolKey])) {
|
||||
try {
|
||||
$database->create();
|
||||
} catch (\Utopia\Database\Exception\Duplicate) {
|
||||
// Schema already exists
|
||||
}
|
||||
$initializedPools[$poolKey] = true;
|
||||
}
|
||||
} elseif (\in_array($dsn->getHost(), $sharedTables, true)) {
|
||||
$database
|
||||
->setSharedTables(true)
|
||||
->setTenant((int) $projectDocument->getSequence())
|
||||
->setTenant($projectDocument->getSequence())
|
||||
->setNamespace($dsn->getParam('namespace'));
|
||||
} else {
|
||||
$database
|
||||
@@ -301,14 +301,14 @@ Server::setResource('abuseRetention', function () {
|
||||
|
||||
Server::setResource('auditRetention', function (Document $project) {
|
||||
if ($project->getId() === 'console') {
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE', 15778800)); // 6 months
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT_CONSOLE', 15778800)); // 6 months
|
||||
}
|
||||
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600)); // 14 days
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_AUDIT', 1209600)); // 14 days
|
||||
}, ['project']);
|
||||
|
||||
Server::setResource('executionRetention', function () {
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * (int) System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600)); // 14 days
|
||||
return DateTime::addSeconds(new \DateTime(), -1 * System::getEnv('_APP_MAINTENANCE_RETENTION_EXECUTION', 1209600)); // 14 days
|
||||
});
|
||||
|
||||
Server::setResource('cache', function (Registry $register) {
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@
|
||||
"utopia-php/image": "0.8.*",
|
||||
"utopia-php/locale": "0.8.*",
|
||||
"utopia-php/logger": "0.6.*",
|
||||
"utopia-php/messaging": "0.22.*",
|
||||
"utopia-php/messaging": "0.20.*",
|
||||
"utopia-php/migration": "1.9.*",
|
||||
"utopia-php/platform": "0.7.*",
|
||||
"utopia-php/pools": "1.*",
|
||||
|
||||
Generated
+8
-8
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "4fe91e67f343fbe6deac1fdc7eda949f",
|
||||
"content-hash": "b5261855586680e467168f527e0634ae",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -4467,23 +4467,23 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/messaging",
|
||||
"version": "0.22.0",
|
||||
"version": "0.20.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/messaging.git",
|
||||
"reference": "a6ac04fd204fb6a16bf8c75a84d0b9fc10aa5030"
|
||||
"reference": "fcb4c3c46a48008a677957690bd45ec934dd33b0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/a6ac04fd204fb6a16bf8c75a84d0b9fc10aa5030",
|
||||
"reference": "a6ac04fd204fb6a16bf8c75a84d0b9fc10aa5030",
|
||||
"url": "https://api.github.com/repos/utopia-php/messaging/zipball/fcb4c3c46a48008a677957690bd45ec934dd33b0",
|
||||
"reference": "fcb4c3c46a48008a677957690bd45ec934dd33b0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-curl": "*",
|
||||
"ext-openssl": "*",
|
||||
"giggsey/libphonenumber-for-php-lite": "9.0.23",
|
||||
"php": ">=8.1.0",
|
||||
"php": ">=8.0.0",
|
||||
"phpmailer/phpmailer": "6.9.1"
|
||||
},
|
||||
"require-dev": {
|
||||
@@ -4512,9 +4512,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/messaging/issues",
|
||||
"source": "https://github.com/utopia-php/messaging/tree/0.22.0"
|
||||
"source": "https://github.com/utopia-php/messaging/tree/0.20.1"
|
||||
},
|
||||
"time": "2026-04-02T04:09:19+00:00"
|
||||
"time": "2026-02-06T09:56:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/migration",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+4
-1
@@ -1,6 +1,8 @@
|
||||
includes:
|
||||
- phpstan-baseline.neon
|
||||
|
||||
parameters:
|
||||
level: 3
|
||||
tmpDir: .phpstan-cache
|
||||
paths:
|
||||
- src
|
||||
- app
|
||||
@@ -12,3 +14,4 @@ parameters:
|
||||
- vendor/swoole/ide-helper
|
||||
excludePaths:
|
||||
- tests/resources
|
||||
|
||||
|
||||
@@ -285,7 +285,7 @@ class Event
|
||||
*
|
||||
* @param string $key
|
||||
* @param Document $context
|
||||
* @return static
|
||||
* @return self
|
||||
*/
|
||||
public function setContext(string $key, Document $context): self
|
||||
{
|
||||
@@ -309,7 +309,7 @@ class Event
|
||||
/**
|
||||
* Set class used for this event.
|
||||
* @param string $class
|
||||
* @return static
|
||||
* @return self
|
||||
*/
|
||||
public function setClass(string $class): self
|
||||
{
|
||||
@@ -648,8 +648,10 @@ class Event
|
||||
*
|
||||
* @param Event $event
|
||||
*
|
||||
* @return self
|
||||
*
|
||||
*/
|
||||
public function from(Event $event): static
|
||||
public function from(Event $event): self
|
||||
{
|
||||
$this->project = $event->getProject();
|
||||
$this->user = $event->getUser();
|
||||
|
||||
@@ -40,7 +40,6 @@ class Usage extends Base
|
||||
*/
|
||||
public static function fromArray(array $data): static
|
||||
{
|
||||
/** @phpstan-ignore new.static (subclass constructors are backwards-compatible via optional params) */
|
||||
return new static(
|
||||
project: new Document($data['project'] ?? []),
|
||||
metrics: $data['metrics'] ?? [],
|
||||
|
||||
@@ -25,7 +25,11 @@ class Resolvers
|
||||
?Route $route,
|
||||
): callable {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $route, $args) {
|
||||
function (callable $resolve, callable $reject) use ($utopia, $route, $args, $context, $info) {
|
||||
/** @var Http $utopia */
|
||||
/** @var Response $response */
|
||||
/** @var Request $request */
|
||||
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
@@ -92,7 +96,7 @@ class Resolvers
|
||||
callable $url,
|
||||
): callable {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $args) {
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $type, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
@@ -123,7 +127,7 @@ class Resolvers
|
||||
callable $params,
|
||||
): callable {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $args) {
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $type, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
@@ -159,7 +163,7 @@ class Resolvers
|
||||
callable $params,
|
||||
): callable {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $args) {
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $type, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
@@ -191,7 +195,7 @@ class Resolvers
|
||||
callable $params,
|
||||
): callable {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $args) {
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $params, $type, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
@@ -221,7 +225,7 @@ class Resolvers
|
||||
callable $url,
|
||||
): callable {
|
||||
return static fn ($type, $args, $context, $info) => new Swoole(
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $args) {
|
||||
function (callable $resolve, callable $reject) use ($utopia, $databaseId, $collectionId, $url, $type, $args) {
|
||||
$utopia = $utopia->getResource('utopia:graphql', true);
|
||||
$request = $utopia->getResource('request', true);
|
||||
$response = $utopia->getResource('response', true);
|
||||
|
||||
@@ -98,9 +98,10 @@ class Schema
|
||||
foreach ($routes as $route) {
|
||||
/** @var Route $route */
|
||||
|
||||
/** @var \Appwrite\SDK\Method $sdk */
|
||||
$sdk = $route->getLabel('sdk', false);
|
||||
|
||||
if ($sdk === false) {
|
||||
if (empty($sdk)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -176,7 +177,7 @@ class Schema
|
||||
$required = $attr['required'];
|
||||
$default = $attr['default'];
|
||||
$escapedKey = str_replace('$', '', $key);
|
||||
$collections[$databaseId][$collectionId][$escapedKey] = [
|
||||
$collections[$collectionId][$escapedKey] = [
|
||||
'type' => Mapper::attribute(
|
||||
$type,
|
||||
$array,
|
||||
@@ -186,82 +187,80 @@ class Schema
|
||||
];
|
||||
}
|
||||
|
||||
foreach ($collections as $databaseId => $databaseCollections) {
|
||||
foreach ($databaseCollections as $collectionId => $attributes) {
|
||||
$objectType = new ObjectType([
|
||||
'name' => $collectionId,
|
||||
'fields' => \array_merge(
|
||||
["_id" => ['type' => Type::string()]],
|
||||
foreach ($collections as $collectionId => $attributes) {
|
||||
$objectType = new ObjectType([
|
||||
'name' => $collectionId,
|
||||
'fields' => \array_merge(
|
||||
["_id" => ['type' => Type::string()]],
|
||||
$attributes
|
||||
),
|
||||
]);
|
||||
$attributes = \array_merge(
|
||||
$attributes,
|
||||
Mapper::args('mutate')
|
||||
);
|
||||
|
||||
$queryFields[$collectionId . 'Get'] = [
|
||||
'type' => $objectType,
|
||||
'args' => Mapper::args('id'),
|
||||
'resolve' => Resolvers::documentGet(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['get'],
|
||||
)
|
||||
];
|
||||
$queryFields[$collectionId . 'List'] = [
|
||||
'type' => Type::listOf($objectType),
|
||||
'args' => Mapper::args('list'),
|
||||
'resolve' => Resolvers::documentList(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['list'],
|
||||
$params['list'],
|
||||
),
|
||||
'complexity' => $complexity,
|
||||
];
|
||||
|
||||
$mutationFields[$collectionId . 'Create'] = [
|
||||
'type' => $objectType,
|
||||
'args' => $attributes,
|
||||
'resolve' => Resolvers::documentCreate(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['create'],
|
||||
$params['create'],
|
||||
)
|
||||
];
|
||||
$mutationFields[$collectionId . 'Update'] = [
|
||||
'type' => $objectType,
|
||||
'args' => \array_merge(
|
||||
Mapper::args('id'),
|
||||
\array_map(
|
||||
fn ($attr) => $attr['type'] = Type::getNullableType($attr['type']),
|
||||
$attributes
|
||||
),
|
||||
]);
|
||||
$attributes = \array_merge(
|
||||
$attributes,
|
||||
Mapper::args('mutate')
|
||||
);
|
||||
|
||||
$queryFields[$collectionId . 'Get'] = [
|
||||
'type' => $objectType,
|
||||
'args' => Mapper::args('id'),
|
||||
'resolve' => Resolvers::documentGet(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['get'],
|
||||
)
|
||||
];
|
||||
$queryFields[$collectionId . 'List'] = [
|
||||
'type' => Type::listOf($objectType),
|
||||
'args' => Mapper::args('list'),
|
||||
'resolve' => Resolvers::documentList(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['list'],
|
||||
$params['list'],
|
||||
),
|
||||
'complexity' => $complexity,
|
||||
];
|
||||
|
||||
$mutationFields[$collectionId . 'Create'] = [
|
||||
'type' => $objectType,
|
||||
'args' => $attributes,
|
||||
'resolve' => Resolvers::documentCreate(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['create'],
|
||||
$params['create'],
|
||||
)
|
||||
];
|
||||
$mutationFields[$collectionId . 'Update'] = [
|
||||
'type' => $objectType,
|
||||
'args' => \array_merge(
|
||||
Mapper::args('id'),
|
||||
\array_map(
|
||||
fn ($attr) => $attr['type'] = Type::getNullableType($attr['type']),
|
||||
$attributes
|
||||
)
|
||||
),
|
||||
'resolve' => Resolvers::documentUpdate(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['update'],
|
||||
$params['update'],
|
||||
)
|
||||
];
|
||||
$mutationFields[$collectionId . 'Delete'] = [
|
||||
'type' => Mapper::model('none'),
|
||||
'args' => Mapper::args('id'),
|
||||
'resolve' => Resolvers::documentDelete(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['delete'],
|
||||
)
|
||||
];
|
||||
}
|
||||
),
|
||||
'resolve' => Resolvers::documentUpdate(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['update'],
|
||||
$params['update'],
|
||||
)
|
||||
];
|
||||
$mutationFields[$collectionId . 'Delete'] = [
|
||||
'type' => Mapper::model('none'),
|
||||
'args' => Mapper::args('id'),
|
||||
'resolve' => Resolvers::documentDelete(
|
||||
$utopia,
|
||||
$databaseId,
|
||||
$collectionId,
|
||||
$urls['delete'],
|
||||
)
|
||||
];
|
||||
}
|
||||
$offset += $limit;
|
||||
}
|
||||
|
||||
@@ -15,13 +15,10 @@ class Types
|
||||
*
|
||||
* @return Json
|
||||
*/
|
||||
public static function json(): Json
|
||||
public static function json(): Type
|
||||
{
|
||||
if (Registry::has(Json::class)) {
|
||||
$type = Registry::get(Json::class);
|
||||
if ($type instanceof Json) {
|
||||
return $type;
|
||||
}
|
||||
return Registry::get(Json::class);
|
||||
}
|
||||
$type = new Json();
|
||||
Registry::set(Json::class, $type);
|
||||
@@ -31,15 +28,12 @@ class Types
|
||||
/**
|
||||
* Get the JSON type.
|
||||
*
|
||||
* @return Assoc
|
||||
* @return Json
|
||||
*/
|
||||
public static function assoc(): Assoc
|
||||
public static function assoc(): Type
|
||||
{
|
||||
if (Registry::has(Assoc::class)) {
|
||||
$type = Registry::get(Assoc::class);
|
||||
if ($type instanceof Assoc) {
|
||||
return $type;
|
||||
}
|
||||
return Registry::get(Assoc::class);
|
||||
}
|
||||
$type = new Assoc();
|
||||
Registry::set(Assoc::class, $type);
|
||||
@@ -51,13 +45,10 @@ class Types
|
||||
*
|
||||
* @return InputFile
|
||||
*/
|
||||
public static function inputFile(): InputFile
|
||||
public static function inputFile(): Type
|
||||
{
|
||||
if (Registry::has(InputFile::class)) {
|
||||
$type = Registry::get(InputFile::class);
|
||||
if ($type instanceof InputFile) {
|
||||
return $type;
|
||||
}
|
||||
return Registry::get(InputFile::class);
|
||||
}
|
||||
$type = new InputFile();
|
||||
Registry::set(InputFile::class, $type);
|
||||
|
||||
@@ -273,9 +273,11 @@ class Mapper
|
||||
case \Appwrite\Auth\Validator\Password::class:
|
||||
case \Appwrite\Event\Validator\Event::class:
|
||||
case \Appwrite\Event\Validator\FunctionEvent::class:
|
||||
case \Appwrite\Network\Validator\CNAME::class:
|
||||
case \Utopia\Emails\Validator\Email::class:
|
||||
case \Appwrite\Network\Validator\Redirect::class:
|
||||
case \Appwrite\Network\Validator\DNS::class:
|
||||
case \Appwrite\Network\Validator\Origin::class:
|
||||
case \Appwrite\Task\Validator\Cron::class:
|
||||
case \Appwrite\Utopia\Database\Validator\CustomId::class:
|
||||
case \Utopia\Database\Validator\Key::class:
|
||||
@@ -284,7 +286,7 @@ class Mapper
|
||||
case \Utopia\Validator\HexColor::class:
|
||||
case \Utopia\Validator\Host::class:
|
||||
case \Utopia\Validator\IP::class:
|
||||
case \Appwrite\Network\Validator\Origin::class:
|
||||
case \Utopia\Validator\Origin::class:
|
||||
case \Utopia\Validator\Text::class:
|
||||
case \Utopia\Validator\URL::class:
|
||||
case \Utopia\Validator\WhiteList::class:
|
||||
|
||||
@@ -13,7 +13,6 @@ use Utopia\Database\Exception\Limit;
|
||||
use Utopia\Database\Exception\Structure;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\PDO;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
|
||||
abstract class Migration
|
||||
@@ -205,30 +204,6 @@ abstract class Migration
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Query> $queries
|
||||
* @return \Generator<int, Document>
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function documentsIterator(string $collection, array $queries = []): \Generator
|
||||
{
|
||||
$offset = 0;
|
||||
|
||||
do {
|
||||
$documents = $this->dbForProject->find($collection, [
|
||||
...$queries,
|
||||
Query::limit($this->limit),
|
||||
Query::offset($offset),
|
||||
]);
|
||||
|
||||
foreach ($documents as $document) {
|
||||
yield $document;
|
||||
}
|
||||
|
||||
$offset += \count($documents);
|
||||
} while (\count($documents) === $this->limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates collection from the config collection.
|
||||
*
|
||||
|
||||
@@ -1224,7 +1224,7 @@ class V15 extends Migration
|
||||
* @param \Utopia\Database\Document $document
|
||||
* @return \Utopia\Database\Document
|
||||
*/
|
||||
protected function fixDocument(Document $document): Document
|
||||
protected function fixDocument(Document $document)
|
||||
{
|
||||
switch ($document->getCollection()) {
|
||||
case 'cache':
|
||||
@@ -1234,7 +1234,7 @@ class V15 extends Migration
|
||||
* skipping migration for 'cache' and 'variables'.
|
||||
* 'users' already migrated.
|
||||
*/
|
||||
return $document;
|
||||
return;
|
||||
|
||||
case '_metadata':
|
||||
/**
|
||||
@@ -1480,6 +1480,7 @@ class V15 extends Migration
|
||||
* Filter from the 'encrypt' filter.
|
||||
*
|
||||
* @param string $value
|
||||
* @return string|false
|
||||
*/
|
||||
protected function encryptFilter(string $value): string
|
||||
{
|
||||
@@ -1491,8 +1492,8 @@ class V15 extends Migration
|
||||
'data' => OpenSSL::encrypt($value, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
|
||||
'method' => OpenSSL::CIPHER_AES_128_GCM,
|
||||
'iv' => \bin2hex($iv),
|
||||
'tag' => \bin2hex($tag),
|
||||
'tag' => \bin2hex($tag ?? ''),
|
||||
'version' => '1',
|
||||
]) ?: '';
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,7 +452,7 @@ class V20 extends Migration
|
||||
Query::equal('period', ['1d']),
|
||||
]);
|
||||
|
||||
$value = $query;
|
||||
$value = $query ?? 0;
|
||||
$this->createInfMetric($to, $value);
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ final class Cors
|
||||
/**
|
||||
* Build CORS headers for a given request origin.
|
||||
*
|
||||
* @return array<string, int|string>
|
||||
* @return array<string,string>
|
||||
*/
|
||||
public function headers(string $origin): array
|
||||
{
|
||||
|
||||
@@ -18,7 +18,7 @@ class OpenSSL
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function encrypt($data, $method, $key, $options = 0, $iv = '', ?string &$tag = null, $aad = '', $tag_length = 16)
|
||||
public static function encrypt($data, $method, $key, $options = 0, $iv = '', &$tag = null, $aad = '', $tag_length = 16)
|
||||
{
|
||||
return \openssl_encrypt($data, $method, $key, $options, $iv, $tag, $aad, $tag_length);
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ class Action extends PlatformAction
|
||||
|
||||
$image = new Image(\file_get_contents($path));
|
||||
$image->crop((int) $width, (int) $height);
|
||||
$output = (empty($output)) ? $type : $output;
|
||||
$data = $image->output($output, $quality);
|
||||
$response
|
||||
->addHeader('Cache-Control', 'private, max-age=2592000') // 30 days
|
||||
|
||||
@@ -204,6 +204,7 @@ class Get extends Action
|
||||
|
||||
$image = new Image($data);
|
||||
$image->crop((int) $width, (int) $height);
|
||||
$output = (empty($output)) ? $type : $output;
|
||||
$data = $image->output($output, $quality);
|
||||
|
||||
$response
|
||||
|
||||
@@ -95,6 +95,7 @@ class Get extends Action
|
||||
}
|
||||
|
||||
$image->crop((int) $width, (int) $height);
|
||||
$output = (empty($output)) ? $type : $output;
|
||||
$data = $image->output($output, $quality);
|
||||
|
||||
$response
|
||||
|
||||
@@ -90,7 +90,7 @@ class Get extends Action
|
||||
}
|
||||
}
|
||||
|
||||
$rand = (int) \substr((string) $code, -1);
|
||||
$rand = \substr($code, -1);
|
||||
|
||||
$rand = ($rand > \count($themes) - 1) ? $rand % \count($themes) : $rand;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ class Action extends AppwriteAction
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
public function setHttpPath(string $path): self
|
||||
public function setHttpPath(string $path): AppwriteAction
|
||||
{
|
||||
if (\str_contains($path, '/tablesdb')) {
|
||||
$this->context = DATABASE_TYPE_TABLESDB;
|
||||
@@ -28,8 +28,7 @@ class Action extends AppwriteAction
|
||||
if (\str_contains($path, '/vectorsdb')) {
|
||||
$this->context = DATABASE_TYPE_VECTORSDB;
|
||||
}
|
||||
parent::setHttpPath($path);
|
||||
return $this;
|
||||
return parent::setHttpPath($path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+4
-3
@@ -24,7 +24,7 @@ abstract class Action extends DatabasesAction
|
||||
*/
|
||||
abstract protected function getResponseModel(): string;
|
||||
|
||||
public function setHttpPath(string $path): self
|
||||
public function setHttpPath(string $path): DatabasesAction
|
||||
{
|
||||
if (str_contains($path, '/tablesdb/')) {
|
||||
$this->context = ROWS;
|
||||
@@ -47,8 +47,7 @@ abstract class Action extends DatabasesAction
|
||||
],
|
||||
];
|
||||
|
||||
parent::setHttpPath($path);
|
||||
return $this;
|
||||
return parent::setHttpPath($path);
|
||||
}
|
||||
|
||||
protected function getDatabasesOperationReadMetric(): string
|
||||
@@ -407,6 +406,8 @@ abstract class Action extends DatabasesAction
|
||||
|
||||
if (\is_array($related)) {
|
||||
$document->setAttribute($relationship->getAttribute('key'), \array_values($relations));
|
||||
} elseif (empty($relations)) {
|
||||
$document->setAttribute($relationship->getAttribute('key'), null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -209,7 +209,7 @@ class Create extends Action
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Bulk create is not supported for ' . $this->getSDKNamespace() . ' with relationship ' . $this->getStructureContext());
|
||||
}
|
||||
|
||||
$setPermissions = function (Document $document, ?array $permissions) use ($user, $isAPIKey, $isPrivilegedUser, $isBulk, $authorization) {
|
||||
$setPermissions = function (Document $document, ?array $permissions) use ($user, $isAPIKey, $isPrivilegedUser, $isBulk, $dbForProject, $authorization) {
|
||||
$allowedPermissions = [
|
||||
Database::PERMISSION_READ,
|
||||
Database::PERMISSION_UPDATE,
|
||||
|
||||
+1
@@ -122,6 +122,7 @@ class Update extends Action
|
||||
|
||||
$dbForDatabases = $getDatabasesDB($database);
|
||||
// Read permission should not be required for update
|
||||
/** @var Document $document */
|
||||
$collectionTableId = 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence();
|
||||
|
||||
if ($transactionId !== null) {
|
||||
|
||||
+1
-1
@@ -147,7 +147,7 @@ class XList extends Action
|
||||
$cacheKeyBase = \sprintf(
|
||||
'%s-cache-%s:%s:%s:collection:%s:%s:user:%s:%s',
|
||||
$dbForProject->getCacheName(),
|
||||
$hostname,
|
||||
$hostname ?? '',
|
||||
$dbForProject->getNamespace(),
|
||||
$dbForProject->getTenant(),
|
||||
$collectionId,
|
||||
|
||||
@@ -99,6 +99,8 @@ class Update extends Action
|
||||
// Map aggregate permissions into the multiple permissions they represent.
|
||||
$permissions = Permission::aggregate($permissions);
|
||||
|
||||
$enabled ??= $collection->getAttribute('enabled', true);
|
||||
|
||||
$collection = $dbForProject->updateDocument(
|
||||
'database_' . $database->getSequence(),
|
||||
$collectionId,
|
||||
|
||||
@@ -103,9 +103,6 @@ class XList extends Action
|
||||
$os = $detector->getOS();
|
||||
$client = $detector->getClient();
|
||||
$device = $detector->getDevice();
|
||||
$deviceName = \is_array($device) ? ($device['deviceName'] ?? '') : '';
|
||||
$deviceBrand = \is_array($device) ? ($device['deviceBrand'] ?? '') : '';
|
||||
$deviceModel = \is_array($device) ? ($device['deviceModel'] ?? '') : '';
|
||||
|
||||
$output[$i] = new Document([
|
||||
'event' => $log['event'],
|
||||
@@ -124,9 +121,9 @@ class XList extends Action
|
||||
'clientVersion' => $client['clientVersion'],
|
||||
'clientEngine' => $client['clientEngine'],
|
||||
'clientEngineVersion' => $client['clientEngineVersion'],
|
||||
'deviceName' => $deviceName,
|
||||
'deviceBrand' => $deviceBrand,
|
||||
'deviceModel' => $deviceModel,
|
||||
'deviceName' => $device['deviceName'],
|
||||
'deviceBrand' => $device['deviceBrand'],
|
||||
'deviceModel' => $device['deviceModel'],
|
||||
]);
|
||||
|
||||
$record = $geodb->get($log['ip']);
|
||||
|
||||
@@ -33,7 +33,7 @@ abstract class Action extends DatabasesAction
|
||||
return $this->databaseType.'.'.METRIC_DATABASE_ID_OPERATIONS_WRITES;
|
||||
}
|
||||
|
||||
public function setHttpPath(string $path): self
|
||||
public function setHttpPath(string $path): DatabasesAction
|
||||
{
|
||||
switch (true) {
|
||||
case str_contains($path, '/tablesdb'):
|
||||
@@ -50,8 +50,7 @@ abstract class Action extends DatabasesAction
|
||||
$this->databaseType = VECTORSDB;
|
||||
break;
|
||||
}
|
||||
parent::setHttpPath($path);
|
||||
return $this;
|
||||
return parent::setHttpPath($path);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -239,7 +239,7 @@ class Create extends Action
|
||||
}
|
||||
}
|
||||
|
||||
$transaction = $authorization->skip(fn () => $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged, $operations) {
|
||||
$transaction = $authorization->skip(fn () => $dbForProject->withTransaction(function () use ($dbForProject, $transactionId, $staged, $existing, $operations) {
|
||||
$dbForProject->createDocuments('transactionLogs', $staged);
|
||||
return $dbForProject->increaseDocumentAttribute(
|
||||
'transactions',
|
||||
|
||||
@@ -105,7 +105,8 @@ class Update extends Action
|
||||
* @throws Exception
|
||||
* @throws \Throwable
|
||||
* @throws \Utopia\Database\Exception
|
||||
* @throws StructureException
|
||||
* @throws Authorization
|
||||
* @throws Structure
|
||||
* @throws \Utopia\Http\Exception
|
||||
*/
|
||||
public function action(string $transactionId, bool $commit, bool $rollback, Document $project, UtopiaResponse $response, Database $dbForProject, callable $getDatabasesDB, User $user, TransactionState $transactionState, Delete $queueForDeletes, Event $queueForEvents, Context $usage, Event $queueForRealtime, Event $queueForFunctions, Event $queueForWebhooks, Authorization $authorization, EventProcessor $eventProcessor): void
|
||||
@@ -182,7 +183,7 @@ class Update extends Action
|
||||
$dbForDatabases = $getDatabasesDB($databaseDoc);
|
||||
|
||||
try {
|
||||
$dbForDatabases->withTransaction(function () use ($dbForDatabases, $dbForProject, $transactionState, $queueForDeletes, $transactionId, &$transaction, &$operations, &$totalOperations, &$databaseOperations, &$currentDocumentId, $authorization) {
|
||||
$dbForDatabases->withTransaction(function () use ($dbForDatabases, $dbForProject, $transactionState, $queueForDeletes, $transactionId, &$transaction, &$operations, &$totalOperations, &$databaseOperations, &$currentDocumentId, $queueForEvents, $usage, $queueForRealtime, $queueForFunctions, $queueForWebhooks, $authorization) {
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('transactions', $transactionId, new Document([
|
||||
'status' => 'committing',
|
||||
])));
|
||||
|
||||
@@ -97,9 +97,6 @@ class XList extends Action
|
||||
$os = $detector->getOS();
|
||||
$client = $detector->getClient();
|
||||
$device = $detector->getDevice();
|
||||
$deviceName = \is_array($device) ? ($device['deviceName'] ?? '') : '';
|
||||
$deviceBrand = \is_array($device) ? ($device['deviceBrand'] ?? '') : '';
|
||||
$deviceModel = \is_array($device) ? ($device['deviceModel'] ?? '') : '';
|
||||
|
||||
$output[$i] = new Document([
|
||||
'event' => $log['event'],
|
||||
@@ -118,9 +115,9 @@ class XList extends Action
|
||||
'clientVersion' => $client['clientVersion'],
|
||||
'clientEngine' => $client['clientEngine'],
|
||||
'clientEngineVersion' => $client['clientEngineVersion'],
|
||||
'deviceName' => $deviceName,
|
||||
'deviceBrand' => $deviceBrand,
|
||||
'deviceModel' => $deviceModel,
|
||||
'deviceName' => $device['deviceName'],
|
||||
'deviceBrand' => $device['deviceBrand'],
|
||||
'deviceModel' => $device['deviceModel'],
|
||||
]);
|
||||
|
||||
$record = $geodb->get($log['ip']);
|
||||
|
||||
@@ -130,11 +130,9 @@ class Create extends CollectionAction
|
||||
$indexes[] = new Document($index);
|
||||
}
|
||||
try {
|
||||
// passing null in creates only creates the metadata collection
|
||||
if (!$dbForDatabases->exists(null, Database::METADATA)) {
|
||||
try {
|
||||
$dbForDatabases->create();
|
||||
} catch (DuplicateException) {
|
||||
}
|
||||
$dbForDatabases->create();
|
||||
}
|
||||
$dbForDatabases->createCollection(
|
||||
id: 'database_' . $database->getSequence() . '_collection_' . $collection->getSequence(),
|
||||
|
||||
@@ -88,11 +88,16 @@ class Get extends Action
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
[$path, $device] = match ($type) {
|
||||
'output' => [$deployment->getAttribute('buildPath', ''), $deviceForBuilds],
|
||||
'source' => [$deployment->getAttribute('sourcePath', ''), $deviceForFunctions],
|
||||
default => throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid deployment download type.'),
|
||||
};
|
||||
switch ($type) {
|
||||
case 'output':
|
||||
$path = $deployment->getAttribute('buildPath', '');
|
||||
$device = $deviceForBuilds;
|
||||
break;
|
||||
case 'source':
|
||||
$path = $deployment->getAttribute('sourcePath', '');
|
||||
$device = $deviceForFunctions;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$device->exists($path)) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
|
||||
@@ -213,10 +213,7 @@ class Create extends Base
|
||||
$current = new Document();
|
||||
|
||||
foreach ($sessions as $session) {
|
||||
if (!$session instanceof Document) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var Utopia\Database\Document $session */
|
||||
if ($proofForToken->verify($store->getProperty('secret', ''), $session->getAttribute('secret'))) { // Find most recent active session for user ID and JWT headers
|
||||
$current = $session;
|
||||
}
|
||||
@@ -240,11 +237,11 @@ class Create extends Base
|
||||
]);
|
||||
|
||||
$executionId = ID::unique();
|
||||
$headers['x-appwrite-execution-id'] = $executionId;
|
||||
$headers['x-appwrite-execution-id'] = $executionId ?? '';
|
||||
$headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $apiKey;
|
||||
$headers['x-appwrite-trigger'] = 'http';
|
||||
$headers['x-appwrite-user-id'] = $user->getId();
|
||||
$headers['x-appwrite-user-jwt'] = $jwt;
|
||||
$headers['x-appwrite-user-id'] = $user->getId() ?? '';
|
||||
$headers['x-appwrite-user-jwt'] = $jwt ?? '';
|
||||
$headers['x-appwrite-country-code'] = '';
|
||||
$headers['x-appwrite-continent-code'] = '';
|
||||
$headers['x-appwrite-continent-eu'] = 'false';
|
||||
@@ -353,18 +350,16 @@ class Create extends Base
|
||||
}
|
||||
}
|
||||
|
||||
if ($executionsRetentionCount > 0 && ENABLE_EXECUTIONS_LIMIT_ON_ROUTE) {
|
||||
$this->enqueueDeletes(
|
||||
$project,
|
||||
$function->getSequence(),
|
||||
$executionsRetentionCount,
|
||||
$queueForDeletes
|
||||
->setProject($project)
|
||||
->setResource($function->getSequence())
|
||||
->setResourceType(RESOURCE_TYPE_FUNCTIONS)
|
||||
->setType(DELETE_TYPE_EXECUTIONS_LIMIT)
|
||||
->trigger();
|
||||
}
|
||||
);
|
||||
|
||||
$response->setStatusCode(Response::STATUS_CODE_ACCEPTED);
|
||||
$response->dynamic($execution, Response::MODEL_EXECUTION);
|
||||
return;
|
||||
return $response
|
||||
->setStatusCode(Response::STATUS_CODE_ACCEPTED)
|
||||
->dynamic($execution, Response::MODEL_EXECUTION);
|
||||
}
|
||||
|
||||
$durationStart = \microtime(true);
|
||||
@@ -375,7 +370,7 @@ class Create extends Base
|
||||
if ($version === 'v2') {
|
||||
$vars = \array_merge($vars, [
|
||||
'APPWRITE_FUNCTION_TRIGGER' => $headers['x-appwrite-trigger'] ?? '',
|
||||
'APPWRITE_FUNCTION_DATA' => $body,
|
||||
'APPWRITE_FUNCTION_DATA' => $body ?? '',
|
||||
'APPWRITE_FUNCTION_USER_ID' => $headers['x-appwrite-user-id'] ?? '',
|
||||
'APPWRITE_FUNCTION_JWT' => $headers['x-appwrite-user-jwt'] ?? ''
|
||||
]);
|
||||
@@ -542,18 +537,32 @@ class Create extends Base
|
||||
}
|
||||
}
|
||||
|
||||
if ($executionsRetentionCount > 0 && ENABLE_EXECUTIONS_LIMIT_ON_ROUTE) {
|
||||
$this->enqueueDeletes(
|
||||
$project,
|
||||
$function->getSequence(),
|
||||
$executionsRetentionCount,
|
||||
$queueForDeletes
|
||||
->setProject($project)
|
||||
->setResource($function->getSequence())
|
||||
->setResourceType(RESOURCE_TYPE_FUNCTIONS)
|
||||
->setType(DELETE_TYPE_EXECUTIONS_LIMIT)
|
||||
->trigger();
|
||||
}
|
||||
);
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
->dynamic($execution, Response::MODEL_EXECUTION);
|
||||
}
|
||||
|
||||
private function enqueueDeletes(
|
||||
Document $project,
|
||||
string $resourceId,
|
||||
int $executionsRetentionCount,
|
||||
DeleteEvent $queueForDeletes
|
||||
): void {
|
||||
/* cleanup */
|
||||
if ($executionsRetentionCount > 0 && ENABLE_EXECUTIONS_LIMIT_ON_ROUTE) {
|
||||
$queueForDeletes
|
||||
->setProject($project)
|
||||
->setResource($resourceId)
|
||||
->setResourceType(RESOURCE_TYPE_FUNCTIONS)
|
||||
->setType(DELETE_TYPE_EXECUTIONS_LIMIT)
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,8 +424,8 @@ class Create extends Base
|
||||
|
||||
/** Trigger Realtime Events */
|
||||
$queueForRealtime
|
||||
->setSubscribers(['console', $project->getId()])
|
||||
->from($ruleCreate)
|
||||
->setSubscribers(['console', $project->getId()])
|
||||
->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +170,8 @@ class Update extends Base
|
||||
$runtime = $function->getAttribute('runtime');
|
||||
}
|
||||
|
||||
$enabled ??= $function->getAttribute('enabled', true);
|
||||
|
||||
$repositoryId = $function->getAttribute('repositoryId', '');
|
||||
$repositoryInternalId = $function->getAttribute('repositoryInternalId', '');
|
||||
|
||||
|
||||
@@ -450,7 +450,7 @@ class Builds extends Action
|
||||
|
||||
$providerCommitHash = \trim($stdout);
|
||||
|
||||
$deployment->setAttribute('providerCommitHash', $providerCommitHash);
|
||||
$deployment->setAttribute('providerCommitHash', $providerCommitHash ?? '');
|
||||
$deployment->setAttribute('providerCommitAuthorUrl', APP_VCS_GITHUB_URL);
|
||||
$deployment->setAttribute('providerCommitAuthor', APP_VCS_GITHUB_USERNAME);
|
||||
$deployment->setAttribute('providerCommitMessage', "Create '" . $resource->getAttribute('name', '') . "' function");
|
||||
@@ -862,7 +862,7 @@ class Builds extends Action
|
||||
if (\str_contains($logs, '{APPWRITE_DETECTION_SEPARATOR_START}')) {
|
||||
[$logsBefore, $detectionLogsStart] = \explode('{APPWRITE_DETECTION_SEPARATOR_START}', $logs, 2);
|
||||
[$detectionLogs, $logsAfter] = \explode('{APPWRITE_DETECTION_SEPARATOR_END}', $detectionLogsStart, 2);
|
||||
$logs = $logsBefore . $logsAfter;
|
||||
$logs = ($logsBefore ?? '') . ($logsAfter ?? '');
|
||||
}
|
||||
|
||||
$deployment->setAttribute('buildLogs', $logs);
|
||||
@@ -1203,8 +1203,6 @@ class Builds extends Action
|
||||
protected function sendUsage(Document $resource, Document $deployment, Document $project, Context $usage, UsagePublisher $publisherForUsage): void
|
||||
{
|
||||
$spec = Config::getParam('specifications')[$resource->getAttribute('buildSpecification', APP_COMPUTE_SPECIFICATION_DEFAULT)];
|
||||
$cpus = (int) ($spec['cpus'] ?? APP_COMPUTE_CPUS_DEFAULT);
|
||||
$memory = (int) ($spec['memory'] ?? APP_COMPUTE_MEMORY_DEFAULT);
|
||||
|
||||
switch ($deployment->getAttribute('status')) {
|
||||
case 'ready':
|
||||
@@ -1366,8 +1364,6 @@ class Builds extends Action
|
||||
Realtime $queueForRealtime,
|
||||
array $platform
|
||||
): void {
|
||||
$deployment = new Document();
|
||||
|
||||
try {
|
||||
if ($resource->getAttribute('providerSilentMode', false) === true) {
|
||||
return;
|
||||
@@ -1448,7 +1444,7 @@ class Builds extends Action
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https';
|
||||
$previewUrl = match ($resource->getCollection()) {
|
||||
'functions' => '',
|
||||
'sites' => !$rule->isEmpty() ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : '',
|
||||
'sites' => ! empty($rule) ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : '',
|
||||
default => throw new \Exception('Invalid resource type')
|
||||
};
|
||||
|
||||
|
||||
@@ -83,8 +83,7 @@ class Update extends Action
|
||||
|
||||
// If rule is already verified or in certificate generation state, don't queue for verification again
|
||||
if ($rule->getAttribute('status') === RULE_STATUS_VERIFIED || $rule->getAttribute('status') === RULE_STATUS_CERTIFICATE_GENERATING) {
|
||||
$response->dynamic($rule, Response::MODEL_PROXY_RULE);
|
||||
return;
|
||||
return $response->dynamic($rule, Response::MODEL_PROXY_RULE);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -87,11 +87,16 @@ class Get extends Action
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
[$path, $device] = match ($type) {
|
||||
'output' => [$deployment->getAttribute('buildPath', ''), $deviceForBuilds],
|
||||
'source' => [$deployment->getAttribute('sourcePath', ''), $deviceForSites],
|
||||
default => throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Invalid deployment download type.'),
|
||||
};
|
||||
switch ($type) {
|
||||
case 'output':
|
||||
$path = $deployment->getAttribute('buildPath', '');
|
||||
$device = $deviceForBuilds;
|
||||
break;
|
||||
case 'source':
|
||||
$path = $deployment->getAttribute('sourcePath', '');
|
||||
$device = $deviceForSites;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$device->exists($path)) {
|
||||
throw new Exception(Exception::DEPLOYMENT_NOT_FOUND);
|
||||
|
||||
@@ -172,6 +172,8 @@ class Update extends Base
|
||||
$framework = $site->getAttribute('framework');
|
||||
}
|
||||
|
||||
$enabled ??= $site->getAttribute('enabled', true);
|
||||
|
||||
$repositoryId = $site->getAttribute('repositoryId', '');
|
||||
$repositoryInternalId = $site->getAttribute('repositoryInternalId', '');
|
||||
|
||||
|
||||
@@ -286,8 +286,6 @@ class Create extends Action
|
||||
$mimeType = $deviceForFiles->getFileMimeType($path); // Get mime-type before compression and encryption
|
||||
$fileHash = $deviceForFiles->getFileHash($path); // Get file hash before compression and encryption
|
||||
$data = '';
|
||||
$iv = '';
|
||||
$tag = null;
|
||||
// Compression
|
||||
$algorithm = $bucket->getAttribute('compression', Compression::NONE);
|
||||
if ($fileSize <= APP_STORAGE_READ_BUFFER && $algorithm != Compression::NONE) {
|
||||
|
||||
@@ -99,8 +99,12 @@ class Update extends Action
|
||||
|
||||
$permissions ??= $bucket->getPermissions();
|
||||
$maximumFileSize ??= $bucket->getAttribute('maximumFileSize', (int) System::getEnv('_APP_STORAGE_LIMIT', 0));
|
||||
$allowedFileExtensions ??= $bucket->getAttribute('allowedFileExtensions', []);
|
||||
$enabled ??= $bucket->getAttribute('enabled', true);
|
||||
$encryption ??= $bucket->getAttribute('encryption', true);
|
||||
$antivirus ??= $bucket->getAttribute('antivirus', true);
|
||||
$compression ??= $bucket->getAttribute('compression', Compression::NONE);
|
||||
$transformations ??= $bucket->getAttribute('transformations', true);
|
||||
|
||||
// Map aggregate permissions into the multiple permissions they represent.
|
||||
$permissions = Permission::aggregate($permissions);
|
||||
|
||||
@@ -17,7 +17,6 @@ use Appwrite\Utopia\Database\Documents\User;
|
||||
use Appwrite\Utopia\Response;
|
||||
use libphonenumber\NumberParseException;
|
||||
use libphonenumber\PhoneNumberUtil;
|
||||
use Throwable;
|
||||
use Utopia\Auth\Proofs\Password;
|
||||
use Utopia\Auth\Proofs\Token;
|
||||
use Utopia\Database\Database;
|
||||
@@ -103,8 +102,6 @@ class Create extends Action
|
||||
{
|
||||
$isAppUser = $user->isApp($authorization->getRoles());
|
||||
$isPrivilegedUser = $user->isPrivileged($authorization->getRoles());
|
||||
$invitee = new Document();
|
||||
$hash = '';
|
||||
|
||||
if (empty($url)) {
|
||||
if (! $isAppUser && ! $isPrivilegedUser) {
|
||||
@@ -148,6 +145,9 @@ class Create extends Action
|
||||
}
|
||||
} elseif (! empty($phone)) {
|
||||
$invitee = $dbForProject->findOne('users', [Query::equal('phone', [$phone])]);
|
||||
if (! $invitee->isEmpty() && ! empty($email) && $invitee->getAttribute('email', '') !== $email) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given phone and email doesn\'t match', 409);
|
||||
}
|
||||
}
|
||||
|
||||
if ($invitee->isEmpty()) { // Create new user if no user with same email found
|
||||
|
||||
+4
-1
@@ -110,7 +110,10 @@ class Update extends Action
|
||||
$providerRepositoryId = $repository->getAttribute('providerRepositoryId');
|
||||
|
||||
try {
|
||||
$providerRepositoryName = $github->getRepositoryName($providerRepositoryId);
|
||||
$providerRepositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($providerRepositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,6 @@ class Get extends Action
|
||||
}
|
||||
|
||||
$state = \json_decode($state, true);
|
||||
$redirectFailure = $state['failure'] ?? '';
|
||||
$projectId = $state['projectId'] ?? '';
|
||||
|
||||
$project = $dbForPlatform->getDocument('projects', $projectId);
|
||||
@@ -75,11 +74,10 @@ class Get extends Action
|
||||
|
||||
if (!empty($redirectFailure)) {
|
||||
$separator = \str_contains($redirectFailure, '?') ? '&' : ':';
|
||||
$response
|
||||
return $response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->redirect($redirectFailure . $separator . \http_build_query(['error' => $error]));
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND, $error);
|
||||
@@ -167,11 +165,10 @@ class Get extends Action
|
||||
|
||||
if (!empty($redirectFailure)) {
|
||||
$separator = \str_contains($redirectFailure, '?') ? '&' : ':';
|
||||
$response
|
||||
return $response
|
||||
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->addHeader('Pragma', 'no-cache')
|
||||
->redirect($redirectFailure . $separator . \http_build_query(['error' => $error]));
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, $error);
|
||||
|
||||
@@ -49,8 +49,6 @@ trait Deployment
|
||||
) {
|
||||
$errors = [];
|
||||
foreach ($repositories as $repository) {
|
||||
$logBase = 'vcs.github.event.repo.unknown';
|
||||
|
||||
try {
|
||||
$repositoryId = $repository->getId();
|
||||
$projectId = $repository->getAttribute('projectId');
|
||||
@@ -109,11 +107,18 @@ trait Deployment
|
||||
|
||||
$owner = $github->getOwnerName($providerInstallationId) ?? '';
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId);
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
|
||||
$isAuthorized = !$external;
|
||||
|
||||
if (!$isAuthorized && !empty($providerPullRequestId)) {
|
||||
@@ -286,7 +291,10 @@ trait Deployment
|
||||
|
||||
$providerRepositoryId = $repository->getAttribute('providerRepositoryId');
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId);
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
@@ -493,7 +501,7 @@ trait Deployment
|
||||
$rule = $authorization->skip(fn () => $dbForPlatform->getDocument('rules', $previewRuleId));
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$previewUrl = !$rule->isEmpty() ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : '';
|
||||
$previewUrl = !empty($rule) ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : '';
|
||||
|
||||
if (!empty($previewUrl)) {
|
||||
$comment = new Comment($platform);
|
||||
@@ -516,7 +524,10 @@ trait Deployment
|
||||
|
||||
$providerRepositoryId = $repository->getAttribute('providerRepositoryId');
|
||||
try {
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId);
|
||||
$repositoryName = $github->getRepositoryName($providerRepositoryId) ?? '';
|
||||
if (empty($repositoryName)) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
} catch (RepositoryNotFound $e) {
|
||||
throw new Exception(Exception::PROVIDER_REPOSITORY_NOT_FOUND);
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ class Create extends Action
|
||||
default => null,
|
||||
};
|
||||
|
||||
$response->json($parsedPayload);
|
||||
return $response->json($parsedPayload);
|
||||
}
|
||||
|
||||
protected function preprocessEvent(Request $request)
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\ClamAV\Network;
|
||||
use Appwrite\PubSub\Adapter\Pool as PubSubPool;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Utopia\Cache\Adapter\Pool as CachePool;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Console;
|
||||
@@ -12,8 +13,6 @@ use Utopia\Domains\Domain;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Http\Http;
|
||||
use Utopia\Logger\Logger;
|
||||
use Utopia\Messaging\Adapter\Email as EmailAdapter;
|
||||
use Utopia\Messaging\Messages\Email as EmailMessage;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Queue\Broker\Pool as BrokerPool;
|
||||
@@ -125,7 +124,7 @@ class Doctor extends Action
|
||||
$providerConfig = System::getEnv('_APP_LOGGING_CONFIG', '');
|
||||
|
||||
try {
|
||||
$loggingProvider = new DSN($providerConfig);
|
||||
$loggingProvider = new DSN($providerConfig ?? '');
|
||||
|
||||
$providerName = $loggingProvider->getScheme();
|
||||
|
||||
@@ -213,18 +212,15 @@ class Doctor extends Action
|
||||
}
|
||||
|
||||
try {
|
||||
/** @var EmailAdapter $smtp */
|
||||
$smtp = $register->get('smtp');
|
||||
/* @var PHPMailer $mail */
|
||||
$mail = $register->get('smtp');
|
||||
|
||||
$emailMessage = new EmailMessage(
|
||||
to: ['demo@example.com'],
|
||||
subject: 'Test SMTP Connection',
|
||||
content: 'Hello World',
|
||||
fromName: \urldecode(System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server')),
|
||||
fromEmail: System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM),
|
||||
);
|
||||
$mail->addAddress('demo@example.com', 'Example.com');
|
||||
$mail->Subject = 'Test SMTP Connection';
|
||||
$mail->Body = 'Hello World';
|
||||
$mail->AltBody = 'Hello World';
|
||||
|
||||
$smtp->send($emailMessage);
|
||||
$mail->send();
|
||||
Console::success('🟢 ' . str_pad("SMTP", 50, '.') . 'connected');
|
||||
} catch (\Throwable) {
|
||||
Console::error('🔴 ' . str_pad("SMTP", 47, '.') . 'disconnected');
|
||||
|
||||
@@ -227,7 +227,7 @@ class Install extends Action
|
||||
// Fall back to CLI mode
|
||||
$enableAssistant = false;
|
||||
$assistantExistsInOldCompose = false;
|
||||
if ($existingInstallation) {
|
||||
if ($existingInstallation && isset($compose)) {
|
||||
try {
|
||||
$assistantService = $compose->getService('appwrite-assistant');
|
||||
$assistantExistsInOldCompose = $assistantService !== null;
|
||||
|
||||
@@ -566,8 +566,6 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
|
||||
$repoBranch = $language['repoBranch'] ?? 'main';
|
||||
if ($git && !empty($gitUrl)) {
|
||||
$prUrls = [];
|
||||
|
||||
// Generate commit message: use provided message, AI changelog, or fallback
|
||||
if (! empty($message)) {
|
||||
$commitMessage = $message;
|
||||
|
||||
@@ -60,7 +60,7 @@ class StatsResources extends Action
|
||||
|
||||
$interval = (int) System::getEnv('_APP_STATS_RESOURCES_INTERVAL', '3600');
|
||||
|
||||
Console::loop(function () use ($queueForStatsResources) {
|
||||
Console::loop(function () use ($queueForStatsResources, $dbForPlatform) {
|
||||
|
||||
$last24Hours = (new \DateTime())->sub(\DateInterval::createFromDateString('24 hours'));
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@ use Exception;
|
||||
use Throwable;
|
||||
use Utopia\Console;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Authorization;
|
||||
use Utopia\Database\Exception\Structure;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Message;
|
||||
@@ -50,11 +51,13 @@ class Audits extends Action
|
||||
|
||||
/**
|
||||
* @param Message $message
|
||||
* @param callable $getProjectDB
|
||||
* @param Document $project
|
||||
* @param callable(Document): \Utopia\Audit\Audit $getAudit
|
||||
* @param callable $getAudit
|
||||
* @return Commit|NoCommit
|
||||
* @throws Throwable
|
||||
* @throws \Utopia\Database\Exception
|
||||
* @throws Authorization
|
||||
* @throws Structure
|
||||
*/
|
||||
public function action(Message $message, Document $project, callable $getAudit): Commit|NoCommit
|
||||
|
||||
@@ -24,6 +24,7 @@ use Utopia\Database\Exception\Conflict;
|
||||
use Utopia\Database\Exception\Restricted;
|
||||
use Utopia\Database\Exception\Structure;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Platform\Action;
|
||||
@@ -362,6 +363,7 @@ class Deletes extends Action
|
||||
/**
|
||||
* @param Document $project
|
||||
* @param callable $getProjectDB
|
||||
* @param Document $target
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
@@ -436,6 +438,7 @@ class Deletes extends Action
|
||||
* @param string $resource
|
||||
* @param string|null $resourceType
|
||||
* @return void
|
||||
* @throws Authorization
|
||||
* @throws Exception
|
||||
*/
|
||||
private function deleteCacheByResource(Document $project, callable $getProjectDB, string $resource, ?string $resourceType = null): void
|
||||
@@ -515,6 +518,7 @@ class Deletes extends Action
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Database $dbForPlatform
|
||||
* @param callable $getProjectDB
|
||||
* @param string $hourlyUsageRetentionDatetime
|
||||
* @return void
|
||||
@@ -582,6 +586,7 @@ class Deletes extends Action
|
||||
* @param Database $dbForPlatform
|
||||
* @param Document $document
|
||||
* @return void
|
||||
* @throws Authorization
|
||||
* @throws DatabaseException
|
||||
* @throws Conflict
|
||||
* @throws Restricted
|
||||
@@ -618,6 +623,7 @@ class Deletes extends Action
|
||||
* @param Document $document
|
||||
* @return void
|
||||
* @throws Exception
|
||||
* @throws Authorization
|
||||
* @throws DatabaseException
|
||||
*/
|
||||
protected function deleteProject(Database $dbForPlatform, callable $getProjectDB, callable $getDatabasesDB, Device $deviceForFiles, Device $deviceForSites, Device $deviceForFunctions, Device $deviceForBuilds, Device $deviceForCache, CertificatesAdapter $certificates, Document $document): void
|
||||
@@ -946,7 +952,7 @@ class Deletes extends Action
|
||||
// fast path, no need to list anything!
|
||||
$delete($dbForProject, $resourceInternalId, $resourceType);
|
||||
} else {
|
||||
$processResource = function (string $type) use ($dbForProject, $delete) {
|
||||
$processResource = function (string $type) use ($dbForProject, $delete, $resourceType) {
|
||||
$this->listByGroup(
|
||||
collection: $type,
|
||||
queries: [Query::select(['$id', '$sequence'])],
|
||||
@@ -1103,7 +1109,7 @@ class Deletes extends Action
|
||||
Query::equal('resourceInternalId', [$siteInternalId]),
|
||||
Query::equal('resourceType', ['sites']),
|
||||
Query::orderAsc()
|
||||
], $dbForProject, function (Document $document) use ($deviceForSites, $deviceForBuilds, $deviceForFiles, $dbForPlatform, &$deploymentInternalIds, &$deploymentIds) {
|
||||
], $dbForProject, function (Document $document) use ($project, $certificates, $deviceForSites, $deviceForBuilds, $deviceForFiles, $dbForPlatform, &$deploymentInternalIds) {
|
||||
$deploymentInternalIds[] = $document->getSequence();
|
||||
$deploymentIds[] = $document->getId();
|
||||
$this->deleteBuildFiles($deviceForBuilds, $document);
|
||||
@@ -1166,7 +1172,7 @@ class Deletes extends Action
|
||||
Query::equal('deploymentResourceInternalId', [$functionInternalId]),
|
||||
Query::equal('projectInternalId', [$project->getSequence()]),
|
||||
Query::orderAsc()
|
||||
], $dbForPlatform, function (Document $document) use ($dbForPlatform, $certificates) {
|
||||
], $dbForPlatform, function (Document $document) use ($project, $dbForPlatform, $certificates) {
|
||||
$this->deleteRule($dbForPlatform, $document, $certificates);
|
||||
});
|
||||
|
||||
@@ -1190,7 +1196,7 @@ class Deletes extends Action
|
||||
Query::equal('resourceInternalId', [$functionInternalId]),
|
||||
Query::equal('resourceType', ['functions']),
|
||||
Query::orderAsc()
|
||||
], $dbForProject, function (Document $document) use ($deviceForFunctions, $deviceForBuilds, &$deploymentInternalIds) {
|
||||
], $dbForProject, function (Document $document) use ($dbForPlatform, $project, $certificates, $deviceForFunctions, $deviceForBuilds, &$deploymentInternalIds) {
|
||||
$deploymentInternalIds[] = $document->getSequence();
|
||||
$this->deleteDeploymentFiles($deviceForFunctions, $document);
|
||||
$this->deleteBuildFiles($deviceForBuilds, $document);
|
||||
@@ -1315,7 +1321,7 @@ class Deletes extends Action
|
||||
|
||||
/**
|
||||
* @param Device $device
|
||||
* @param Document $deployment
|
||||
* @param Document $build
|
||||
* @return void
|
||||
*/
|
||||
private function deleteBuildFiles(Device $device, Document $deployment): void
|
||||
@@ -1625,9 +1631,9 @@ class Deletes extends Action
|
||||
try {
|
||||
$dbForProject->deleteDocuments('transactions', [
|
||||
Query::lessThan('expiresAt', DateTime::format(new \DateTime())),
|
||||
], onNext: function (Document $transaction) use (&$transactionInternalIds) {
|
||||
], onNext: function (Document $transaction) use ($dbForProject, $project, &$transactionInternalIds) {
|
||||
$transactionInternalIds[] = $transaction->getSequence();
|
||||
}, onError: function (Throwable $th) {
|
||||
}, onError: function (Throwable $th) use ($project) {
|
||||
// Swallow errors to avoid breaking the cleanup process
|
||||
});
|
||||
} catch (Throwable $th) {
|
||||
@@ -1640,7 +1646,7 @@ class Deletes extends Action
|
||||
|
||||
$dbForProject->deleteDocuments('transactionLogs', [
|
||||
Query::equal('transactionInternalId', $transactionInternalIds),
|
||||
], onError: function (Throwable $th) {
|
||||
], onError: function (Throwable $th) use ($project) {
|
||||
// Swallow errors to avoid breaking the cleanup process
|
||||
});
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class Functions extends Action
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
@@ -256,7 +256,7 @@ class Functions extends Action
|
||||
* @param Document $user
|
||||
* @param string|null $jwt
|
||||
* @param string|null $event
|
||||
* @throws \Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
private function fail(
|
||||
string $message,
|
||||
@@ -271,10 +271,10 @@ class Functions extends Action
|
||||
?string $event = null,
|
||||
): void {
|
||||
$executionId = ID::unique();
|
||||
$headers['x-appwrite-execution-id'] = $executionId;
|
||||
$headers['x-appwrite-execution-id'] = $executionId ?? '';
|
||||
$headers['x-appwrite-trigger'] = $trigger;
|
||||
$headers['x-appwrite-event'] = $event ?? '';
|
||||
$headers['x-appwrite-user-id'] = $user->getId();
|
||||
$headers['x-appwrite-user-id'] = $user->getId() ?? '';
|
||||
$headers['x-appwrite-user-jwt'] = $jwt ?? '';
|
||||
|
||||
$headersFiltered = [];
|
||||
@@ -458,8 +458,8 @@ class Functions extends Action
|
||||
if ($version === 'v2') {
|
||||
$vars = \array_merge($vars, [
|
||||
'APPWRITE_FUNCTION_TRIGGER' => $headers['x-appwrite-trigger'] ?? '',
|
||||
'APPWRITE_FUNCTION_DATA' => $body,
|
||||
'APPWRITE_FUNCTION_EVENT_DATA' => $body,
|
||||
'APPWRITE_FUNCTION_DATA' => $body ?? '',
|
||||
'APPWRITE_FUNCTION_EVENT_DATA' => $body ?? '',
|
||||
'APPWRITE_FUNCTION_EVENT' => $headers['x-appwrite-event'] ?? '',
|
||||
'APPWRITE_FUNCTION_USER_ID' => $headers['x-appwrite-user-id'] ?? '',
|
||||
'APPWRITE_FUNCTION_JWT' => $headers['x-appwrite-user-jwt'] ?? ''
|
||||
@@ -508,9 +508,6 @@ class Functions extends Action
|
||||
]);
|
||||
|
||||
/** Execute function */
|
||||
$error = null;
|
||||
$errorCode = 0;
|
||||
|
||||
try {
|
||||
$version = $function->getAttribute('version', 'v2');
|
||||
$command = $runtime['startCommand'];
|
||||
|
||||
@@ -4,13 +4,10 @@ namespace Appwrite\Platform\Workers;
|
||||
|
||||
use Appwrite\Template\Template;
|
||||
use Exception;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Swoole\Runtime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Messaging\Adapter\Email as EmailAdapter;
|
||||
use Utopia\Messaging\Adapter\Email\SMTP;
|
||||
use Utopia\Messaging\Messages\Email as EmailMessage;
|
||||
use Utopia\Messaging\Messages\Email\Attachment;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Queue\Message;
|
||||
use Utopia\Registry\Registry;
|
||||
@@ -52,9 +49,9 @@ class Mails extends Action
|
||||
|
||||
/**
|
||||
* @param Message $message
|
||||
* @param Document $project
|
||||
* @param Registry $register
|
||||
* @param Log $log
|
||||
* @throws \PHPMailer\PHPMailer\Exception
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
@@ -135,38 +132,36 @@ class Mails extends Action
|
||||
// render() will return the subject in <p> tags, so use strip_tags() to remove them
|
||||
$subject = \strip_tags($subjectTemplate->render());
|
||||
|
||||
/** @var EmailAdapter $adapter */
|
||||
$adapter = empty($smtp)
|
||||
/** @var PHPMailer $mail */
|
||||
$mail = empty($smtp)
|
||||
? $register->get('smtp')
|
||||
: new SMTP(
|
||||
host: $smtp['host'],
|
||||
port: (int) $smtp['port'],
|
||||
username: $smtp['username'] ?? '',
|
||||
password: $smtp['password'] ?? '',
|
||||
smtpSecure: $smtp['secure'] ?? '',
|
||||
smtpAutoTLS: false,
|
||||
xMailer: 'Appwrite Mailer',
|
||||
timeout: 10,
|
||||
keepAlive: true,
|
||||
timelimit: 30,
|
||||
);
|
||||
: $this->getMailer($smtp);
|
||||
|
||||
// Resolve from/replyTo using fallback hierarchy: Custom options > SMTP config > Defaults
|
||||
$defaultFromEmail = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
|
||||
$defaultFromName = \urldecode(System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'));
|
||||
$mail->clearAddresses();
|
||||
$mail->clearAllRecipients();
|
||||
$mail->clearReplyTos();
|
||||
$mail->clearAttachments();
|
||||
$mail->clearBCCs();
|
||||
$mail->clearCCs();
|
||||
$mail->addAddress($recipient, $name);
|
||||
$mail->Subject = $subject;
|
||||
$mail->Body = $body;
|
||||
|
||||
$fromEmail = !empty($smtp) ? ($smtp['senderEmail'] ?? $defaultFromEmail) : $defaultFromEmail;
|
||||
$fromName = !empty($smtp) ? ($smtp['senderName'] ?? $defaultFromName) : $defaultFromName;
|
||||
$replyTo = $defaultFromEmail;
|
||||
$replyToName = $defaultFromName;
|
||||
$mail->AltBody = $body;
|
||||
$mail->AltBody = preg_replace('/<style\b[^>]*>(.*?)<\/style>/is', '', $mail->AltBody);
|
||||
$mail->AltBody = \strip_tags($mail->AltBody);
|
||||
$mail->AltBody = \trim($mail->AltBody);
|
||||
|
||||
$replyTo = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
|
||||
$replyToName = \urldecode(System::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'));
|
||||
|
||||
$customMailOptions = $payload['customMailOptions'] ?? [];
|
||||
|
||||
if (!empty($customMailOptions['senderEmail'])) {
|
||||
$fromEmail = $customMailOptions['senderEmail'];
|
||||
}
|
||||
if (!empty($customMailOptions['senderName'])) {
|
||||
$fromName = $customMailOptions['senderName'];
|
||||
// fallback hierarchy: Custom options > SMTP config > Defaults.
|
||||
if (!empty($customMailOptions['senderEmail']) || !empty($customMailOptions['senderName'])) {
|
||||
$fromEmail = $customMailOptions['senderEmail'] ?? $mail->From;
|
||||
$fromName = $customMailOptions['senderName'] ?? $mail->FromName;
|
||||
$mail->setFrom($fromEmail, $fromName);
|
||||
}
|
||||
|
||||
if (!empty($customMailOptions['replyToEmail']) || !empty($customMailOptions['replyToName'])) {
|
||||
@@ -177,32 +172,18 @@ class Mails extends Action
|
||||
$replyToName = $smtp['senderName'] ?? $replyToName;
|
||||
}
|
||||
|
||||
$attachments = null;
|
||||
$mail->addReplyTo($replyTo, $replyToName);
|
||||
if (!empty($attachment['content'] ?? '')) {
|
||||
$attachments = [
|
||||
new Attachment(
|
||||
name: $attachment['filename'] ?? 'unknown.file',
|
||||
path: '',
|
||||
type: $attachment['type'] ?? 'plain/text',
|
||||
content: \base64_decode($attachment['content']),
|
||||
),
|
||||
];
|
||||
$mail->AddStringAttachment(
|
||||
base64_decode($attachment['content']),
|
||||
$attachment['filename'] ?? 'unknown.file',
|
||||
$attachment['encoding'] ?? PHPMailer::ENCODING_BASE64,
|
||||
$attachment['type'] ?? 'plain/text'
|
||||
);
|
||||
}
|
||||
|
||||
$emailMessage = new EmailMessage(
|
||||
to: [['email' => $recipient, 'name' => $name]],
|
||||
subject: $subject,
|
||||
content: $body,
|
||||
fromName: $fromName,
|
||||
fromEmail: $fromEmail,
|
||||
replyToName: $replyToName,
|
||||
replyToEmail: $replyTo,
|
||||
attachments: $attachments,
|
||||
html: true,
|
||||
);
|
||||
|
||||
try {
|
||||
$adapter->send($emailMessage);
|
||||
$mail->send();
|
||||
} catch (\Throwable $error) {
|
||||
if ($type === 'smtp') {
|
||||
throw new Exception('Error sending mail: ' . $error->getMessage(), 401);
|
||||
@@ -210,4 +191,38 @@ class Mails extends Action
|
||||
throw new Exception('Error sending mail: ' . $error->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $smtp
|
||||
* @return PHPMailer
|
||||
* @throws \PHPMailer\PHPMailer\Exception
|
||||
*/
|
||||
protected function getMailer(array $smtp): PHPMailer
|
||||
{
|
||||
$mail = new PHPMailer(true);
|
||||
|
||||
$mail->isSMTP();
|
||||
|
||||
$username = $smtp['username'];
|
||||
$password = $smtp['password'];
|
||||
|
||||
$mail->XMailer = 'Appwrite Mailer';
|
||||
$mail->Host = $smtp['host'];
|
||||
$mail->Port = $smtp['port'];
|
||||
$mail->SMTPAuth = (!empty($username) && !empty($password));
|
||||
$mail->Username = $username;
|
||||
$mail->Password = $password;
|
||||
$mail->SMTPSecure = $smtp['secure'];
|
||||
$mail->SMTPAutoTLS = false;
|
||||
$mail->SMTPKeepAlive = true;
|
||||
$mail->CharSet = 'UTF-8';
|
||||
$mail->Timeout = 10; /* Connection timeout */
|
||||
$mail->getSMTPInstance()->Timelimit = 30; /* Timeout for each individual SMTP command (e.g. HELO, EHLO, etc.) */
|
||||
|
||||
$mail->setFrom($smtp['senderEmail'], $smtp['senderName']);
|
||||
|
||||
$mail->isHTML();
|
||||
|
||||
return $mail;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,7 +285,7 @@ class Messaging extends Action
|
||||
|
||||
try {
|
||||
$response = $adapter->send($data);
|
||||
$deliveredTotal += (int) $response['deliveredTo'];
|
||||
$deliveredTotal += $response['deliveredTo'];
|
||||
foreach ($response['results'] as $result) {
|
||||
if ($result['status'] === 'failure') {
|
||||
$deliveryErrors[] = "Failed sending to target {$result['recipient']} with error: {$result['error']}";
|
||||
@@ -380,7 +380,7 @@ class Messaging extends Action
|
||||
]));
|
||||
|
||||
// Delete any attachments that were downloaded to local storage
|
||||
if ($providerType === MESSAGE_TYPE_EMAIL) {
|
||||
if ($provider->getAttribute('type') === MESSAGE_TYPE_EMAIL) {
|
||||
if ($deviceForFiles->getType() === Storage::DEVICE_LOCAL) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -408,7 +408,6 @@ class Migrations extends Action
|
||||
$tempAPIKey = $this->generateAPIKey($project);
|
||||
|
||||
$transfer = $source = $destination = null;
|
||||
$aggregatedResources = [];
|
||||
|
||||
$host = System::getEnv('_APP_MIGRATION_HOST');
|
||||
if (empty($host)) {
|
||||
@@ -445,6 +444,7 @@ class Migrations extends Action
|
||||
$destination
|
||||
);
|
||||
|
||||
$aggregatedResources = [];
|
||||
/** Start Transfer */
|
||||
if (empty($source->getErrors())) {
|
||||
$migration->setAttribute('stage', 'migrating');
|
||||
|
||||
@@ -208,7 +208,7 @@ class StatsResources extends Action
|
||||
{
|
||||
$totalFiles = 0;
|
||||
$totalStorage = 0;
|
||||
$this->foreachDocument($dbForProject, 'buckets', [], function ($bucket) use ($dbForProject, $region, &$totalFiles, &$totalStorage) {
|
||||
$this->foreachDocument($dbForProject, 'buckets', [], function ($bucket) use ($dbForProject, $dbForLogs, $region, &$totalFiles, &$totalStorage) {
|
||||
try {
|
||||
$files = $dbForProject->count('bucket_' . $bucket->getSequence());
|
||||
} catch (Throwable $th) {
|
||||
|
||||
@@ -140,7 +140,7 @@ class StatsUsage extends Action
|
||||
|
||||
/**
|
||||
* @param Message $message
|
||||
* @param callable(Document): Database $getProjectDB
|
||||
* @param callable(): Database $getProjectDB
|
||||
* @param callable(): Database $getLogsDB
|
||||
* @param Registry $register
|
||||
* @return void
|
||||
@@ -212,7 +212,7 @@ class StatsUsage extends Action
|
||||
* @param Document $project
|
||||
* @param Document $document
|
||||
* @param array $metrics
|
||||
* @param callable(Document): Database $getProjectDB
|
||||
* @param callable(): Database $getProjectDB
|
||||
* @param string $databaseType Database type from context
|
||||
* @return void
|
||||
*/
|
||||
@@ -394,7 +394,7 @@ class StatsUsage extends Action
|
||||
|
||||
/**
|
||||
* Commit stats to DB
|
||||
* @param callable(Document): Database $getProjectDB
|
||||
* @param callable(): Database $getProjectDB
|
||||
* @return void
|
||||
*/
|
||||
public function commitToDb(callable $getProjectDB): void
|
||||
@@ -459,7 +459,7 @@ class StatsUsage extends Action
|
||||
/**
|
||||
* Sort by unique index key reduce locks/deadlocks
|
||||
*/
|
||||
usort($projectStats['stats'], function ($a, $b) {
|
||||
usort($projectStats['stats'], function ($a, $b) use ($sequence) {
|
||||
// Metric DESC
|
||||
$cmp = strcmp($b['metric'], $a['metric']);
|
||||
if ($cmp !== 0) {
|
||||
|
||||
@@ -233,7 +233,7 @@ class Webhooks extends Action
|
||||
$template->setParam('{{webhook}}', $webhook->getAttribute('name'));
|
||||
$template->setParam('{{project}}', $project->getAttribute('name'));
|
||||
$template->setParam('{{url}}', $webhook->getAttribute('url'));
|
||||
$template->setParam('{{error}}', 'The server returned ' . $statusCode . ' status code');
|
||||
$template->setParam('{{error}}', $curlError ?? 'The server returned ' . $statusCode . ' status code');
|
||||
$template->setParam('{{path}}', "/console/project-$region-$projectId/settings/webhooks/$webhookId");
|
||||
$template->setParam('{{attempts}}', $attempts);
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace Appwrite\Promises;
|
||||
|
||||
/** @phpstan-consistent-constructor */
|
||||
abstract class Promise
|
||||
{
|
||||
protected const STATE_PENDING = 1;
|
||||
|
||||
@@ -197,7 +197,7 @@ class Method
|
||||
|
||||
public function isHidden(): bool|array
|
||||
{
|
||||
return $this->hide;
|
||||
return $this->hide ?? false;
|
||||
}
|
||||
|
||||
public function isPackaging(): bool
|
||||
|
||||
@@ -204,6 +204,9 @@ abstract class Format
|
||||
*
|
||||
* Get services value
|
||||
*
|
||||
* @param array $services
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function getServices(): array
|
||||
{
|
||||
|
||||
@@ -79,8 +79,8 @@ class OpenAPI3 extends Format
|
||||
$output['components']['securitySchemes']['Key']['x-appwrite'] = ['demo' => '<YOUR_API_KEY>'];
|
||||
}
|
||||
|
||||
if (isset($output['components']['securitySchemes']['JWT'])) {
|
||||
$output['components']['securitySchemes']['JWT']['x-appwrite'] = ['demo' => '<YOUR_JWT>'];
|
||||
if (isset($output['securityDefinitions']['JWT'])) {
|
||||
$output['securityDefinitions']['JWT']['x-appwrite'] = ['demo' => '<YOUR_JWT>'];
|
||||
}
|
||||
|
||||
if (isset($output['components']['securitySchemes']['Locale'])) {
|
||||
@@ -99,7 +99,7 @@ class OpenAPI3 extends Format
|
||||
|
||||
$sdk = $route->getLabel('sdk', false);
|
||||
|
||||
if ($sdk === false) {
|
||||
if (empty($sdk)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -125,9 +125,7 @@ class OpenAPI3 extends Format
|
||||
|
||||
$namespace = $sdk->getNamespace() ?? 'default';
|
||||
|
||||
if ($desc === null) {
|
||||
$desc = '';
|
||||
}
|
||||
$desc ??= '';
|
||||
$descContents = \str_ends_with($desc, '.md') ? \file_get_contents($desc) : $desc;
|
||||
|
||||
$temp = [
|
||||
@@ -165,7 +163,7 @@ class OpenAPI3 extends Format
|
||||
];
|
||||
}
|
||||
|
||||
if (\is_array($additionalMethods) && \count($additionalMethods) > 0) {
|
||||
if (!empty($additionalMethods)) {
|
||||
$temp['x-appwrite']['methods'] = [];
|
||||
foreach ($additionalMethods as $methodObj) {
|
||||
/** @var Method $methodObj */
|
||||
@@ -331,7 +329,7 @@ class OpenAPI3 extends Format
|
||||
|
||||
if (($response->getCode() ?? 500) === 204) {
|
||||
$temp['responses'][(string)$response->getCode() ?? '500']['description'] = 'No content';
|
||||
unset($temp['responses'][(string)$response->getCode() ?? '500']['content']);
|
||||
unset($temp['responses'][(string)$response->getCode() ?? '500']['schema']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,7 +383,7 @@ class OpenAPI3 extends Format
|
||||
$validator = $validator->getValidator();
|
||||
}
|
||||
|
||||
$class = $validator instanceof Validator
|
||||
$class = !empty($validator)
|
||||
? \get_class($validator)
|
||||
: '';
|
||||
|
||||
@@ -433,7 +431,7 @@ class OpenAPI3 extends Format
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['x-example'] = ($param['example'] ?? '') ?: '<' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . '>';
|
||||
break;
|
||||
case \Utopia\Database\Validator\Datetime::class:
|
||||
case \Utopia\Database\Validator\DatetimeValidator::class:
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
$node['schema']['format'] = 'datetime';
|
||||
$node['schema']['x-example'] = ($param['example'] ?? '') ?: Model::TYPE_DATETIME_EXAMPLE;
|
||||
@@ -465,6 +463,7 @@ class OpenAPI3 extends Format
|
||||
$node['schema']['x-example'] = ($param['example'] ?? '') ?: 'https://example.com';
|
||||
break;
|
||||
case \Utopia\Validator\JSON::class:
|
||||
case \Utopia\Validator\Mock::class:
|
||||
case \Utopia\Validator\Assoc::class:
|
||||
$param['default'] = (empty($param['default'])) ? new \stdClass() : $param['default'];
|
||||
$node['schema']['type'] = 'object';
|
||||
@@ -564,6 +563,12 @@ class OpenAPI3 extends Format
|
||||
$node['schema']['x-example'] = $param['example'];
|
||||
}
|
||||
break;
|
||||
case \Utopia\Validator\Length::class:
|
||||
$node['schema']['type'] = $validator->getType();
|
||||
if (!empty($param['example'])) {
|
||||
$node['schema']['x-example'] = $param['example'];
|
||||
}
|
||||
break;
|
||||
case \Utopia\Validator\WhiteList::class:
|
||||
if ($array) {
|
||||
$validator = $validator->getValidator();
|
||||
|
||||
@@ -96,9 +96,10 @@ class Swagger2 extends Format
|
||||
|
||||
$scope = $route->getLabel('scope', '');
|
||||
|
||||
/** @var Method $sdk */
|
||||
$sdk = $route->getLabel('sdk', false);
|
||||
|
||||
if ($sdk === false) {
|
||||
if (empty($sdk)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -126,9 +127,7 @@ class Swagger2 extends Format
|
||||
$sdkPlatforms = array_values(array_unique($sdkPlatforms));
|
||||
$namespace = $sdk->getNamespace() ?? 'default';
|
||||
|
||||
if ($desc === null) {
|
||||
$desc = '';
|
||||
}
|
||||
$desc ??= '';
|
||||
$descContents = \str_ends_with($desc, '.md') ? \file_get_contents($desc) : $desc;
|
||||
|
||||
$temp = [
|
||||
@@ -172,7 +171,7 @@ class Swagger2 extends Format
|
||||
$temp['produces'][] = $produces;
|
||||
}
|
||||
|
||||
if (\is_array($additionalMethods) && \count($additionalMethods) > 0) {
|
||||
if (!empty($additionalMethods)) {
|
||||
$temp['x-appwrite']['methods'] = [];
|
||||
foreach ($additionalMethods as $methodObj) {
|
||||
/** @var Method $methodObj */
|
||||
@@ -389,7 +388,7 @@ class Swagger2 extends Format
|
||||
$validator = $validator->getValidator();
|
||||
}
|
||||
|
||||
$class = $validator instanceof Validator
|
||||
$class = !empty($validator)
|
||||
? \get_class($validator)
|
||||
: '';
|
||||
|
||||
@@ -437,7 +436,7 @@ class Swagger2 extends Format
|
||||
$node['type'] = $validator->getType();
|
||||
$node['x-example'] = ($param['example'] ?? '') ?: '<' . \strtoupper(Template::fromCamelCaseToSnake($node['name'])) . '>';
|
||||
break;
|
||||
case \Utopia\Database\Validator\Datetime::class:
|
||||
case \Utopia\Database\Validator\DatetimeValidator::class:
|
||||
$node['type'] = $validator->getType();
|
||||
$node['format'] = 'datetime';
|
||||
$node['x-example'] = ($param['example'] ?? '') ?: Model::TYPE_DATETIME_EXAMPLE;
|
||||
@@ -480,6 +479,7 @@ class Swagger2 extends Format
|
||||
}
|
||||
break;
|
||||
case \Utopia\Validator\JSON::class:
|
||||
case \Utopia\Validator\Mock::class:
|
||||
case \Utopia\Validator\Assoc::class:
|
||||
$node['type'] = 'object';
|
||||
$node['default'] = (empty($param['default'])) ? new \stdClass() : $param['default'];
|
||||
@@ -552,6 +552,12 @@ class Swagger2 extends Format
|
||||
$node['x-example'] = $param['example'];
|
||||
}
|
||||
break;
|
||||
case \Utopia\Validator\Length::class:
|
||||
$node['type'] = $validator->getType();
|
||||
if (!empty($param['example'])) {
|
||||
$node['x-example'] = $param['example'];
|
||||
}
|
||||
break;
|
||||
case \Utopia\Validator\WhiteList::class:
|
||||
if ($array) {
|
||||
$validator = $validator->getValidator();
|
||||
|
||||
@@ -86,6 +86,7 @@ class User extends Document
|
||||
/**
|
||||
* Check if user is anonymous.
|
||||
*
|
||||
* @param Document $this
|
||||
* @return bool
|
||||
*/
|
||||
public function isAnonymous(): bool
|
||||
@@ -152,6 +153,7 @@ class User extends Document
|
||||
/**
|
||||
* Verify session and check that its not expired.
|
||||
*
|
||||
* @param array<Document> $sessions
|
||||
* @param string $secret
|
||||
*
|
||||
* @return bool|string
|
||||
|
||||
@@ -29,8 +29,8 @@ class DatabasesStringTypesTest extends Scope
|
||||
protected function setupDatabaseAndCollection(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(self::$setupCache[$cacheKey])) {
|
||||
return self::$setupCache[$cacheKey];
|
||||
if (!empty(static::$setupCache[$cacheKey])) {
|
||||
return static::$setupCache[$cacheKey];
|
||||
}
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
@@ -135,12 +135,12 @@ class DatabasesStringTypesTest extends Scope
|
||||
// Wait for all attributes to be available
|
||||
$this->waitForAllAttributes($databaseId, $collectionId);
|
||||
|
||||
self::$setupCache[$cacheKey] = [
|
||||
static::$setupCache[$cacheKey] = [
|
||||
'databaseId' => $databaseId,
|
||||
'collectionId' => $collectionId,
|
||||
];
|
||||
|
||||
return self::$setupCache[$cacheKey];
|
||||
return static::$setupCache[$cacheKey];
|
||||
}
|
||||
|
||||
public function testCreateDatabase(): void
|
||||
|
||||
@@ -45,12 +45,6 @@ trait DatabasesPermissionsBase
|
||||
return $recordId ? "{$base}/{$recordId}" : $base;
|
||||
}
|
||||
|
||||
protected function getIndexUrl(string $databaseId, string $containerId, string $indexId = ''): string
|
||||
{
|
||||
$base = "{$this->getContainerUrl($databaseId, $containerId)}/indexes";
|
||||
return $indexId ? "{$base}/{$indexId}" : $base;
|
||||
}
|
||||
|
||||
// User Management Methods
|
||||
public function createUser(string $id, string $email, string $password = 'test123!'): array
|
||||
{
|
||||
|
||||
@@ -414,7 +414,7 @@ class FunctionsCustomClientTest extends Scope
|
||||
'offset' => 2
|
||||
]);
|
||||
$this->assertEquals(200, $templatesOffset['headers']['status-code']);
|
||||
$this->addToAssertionCount(1);
|
||||
$this->addToAssertionCount(1, $templatesOffset['body']['templates']);
|
||||
$this->assertEquals($templates['body']['templates'][2]['id'], $templatesOffset['body']['templates'][0]['id']);
|
||||
|
||||
// List templates with filters
|
||||
|
||||
@@ -1003,7 +1003,6 @@ class FunctionsCustomServerTest extends Scope
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
];
|
||||
$id = '';
|
||||
$largeTag = null;
|
||||
while (!feof($handle)) {
|
||||
$curlFile = new \CURLFile('data://' . $mimeType . ';base64,' . base64_encode(@fread($handle, $chunkSize)), $mimeType, 'large-fx.tar.gz');
|
||||
$headers['content-range'] = 'bytes ' . ($counter * $chunkSize) . '-' . min(((($counter * $chunkSize) + $chunkSize) - 1), $size - 1) . '/' . $size;
|
||||
|
||||
@@ -102,7 +102,7 @@ class AbuseTest extends Scope
|
||||
$maxQueries = System::getEnv('_APP_GRAPHQL_MAX_QUERIES', 10);
|
||||
|
||||
$query = [];
|
||||
for ($i = 0; $i <= ((int) $maxQueries) + 1; $i++) {
|
||||
for ($i = 0; $i <= $maxQueries + 1; $i++) {
|
||||
$query[] = ['query' => $this->getQuery(self::LIST_COUNTRIES)];
|
||||
}
|
||||
|
||||
|
||||
@@ -409,7 +409,7 @@ class MessagingTest extends Scope
|
||||
$apiKey = $emailDSN->getPassword();
|
||||
$domain = $emailDSN->getUser();
|
||||
|
||||
if (empty($to) || empty($fromName) || empty($fromEmail) || empty($apiKey) || empty($domain) || empty($isEuRegion)) {
|
||||
if (empty($to) || empty($from) || empty($apiKey) || empty($domain) || empty($isEuRegion)) {
|
||||
$this->markTestSkipped('Email provider not configured');
|
||||
}
|
||||
|
||||
|
||||
@@ -229,8 +229,6 @@ class StorageClientTest extends Scope
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertEquals(47218, \strlen($file['body']));
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -291,8 +291,6 @@ class StorageServerTest extends Scope
|
||||
], $this->getHeaders()), $gqlPayload);
|
||||
|
||||
$this->assertEquals(47218, \strlen($file['body']));
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -102,7 +102,7 @@ class AbuseTest extends Scope
|
||||
$maxQueries = System::getEnv('_APP_GRAPHQL_MAX_QUERIES', 10);
|
||||
|
||||
$query = [];
|
||||
for ($i = 0; $i <= ((int) $maxQueries) + 1; $i++) {
|
||||
for ($i = 0; $i <= $maxQueries + 1; $i++) {
|
||||
$query[] = ['query' => $this->getQuery(self::LIST_COUNTRIES)];
|
||||
}
|
||||
|
||||
|
||||
@@ -2241,7 +2241,7 @@ trait MessagingBase
|
||||
$authKey = $smsDSN->getPassword();
|
||||
$templateId = $smsDSN->getParam('templateId');
|
||||
|
||||
if (empty($to) || empty($senderId) || empty($authKey)) {
|
||||
if (empty($to) || empty($from) || empty($senderId) || empty($authKey)) {
|
||||
$this->markTestSkipped('SMS provider not configured');
|
||||
}
|
||||
|
||||
|
||||
@@ -64,8 +64,8 @@ trait MigrationsBase
|
||||
*/
|
||||
protected function setupMigrationDatabase(): array
|
||||
{
|
||||
if (!empty(self::$cachedDatabaseData)) {
|
||||
return self::$cachedDatabaseData;
|
||||
if (!empty(static::$cachedDatabaseData)) {
|
||||
return static::$cachedDatabaseData;
|
||||
}
|
||||
|
||||
$response = $this->client->call(Client::METHOD_POST, '/databases', [
|
||||
@@ -81,11 +81,11 @@ trait MigrationsBase
|
||||
$this->assertNotEmpty($response['body']);
|
||||
$this->assertNotEmpty($response['body']['$id']);
|
||||
|
||||
self::$cachedDatabaseData = [
|
||||
static::$cachedDatabaseData = [
|
||||
'databaseId' => $response['body']['$id'],
|
||||
];
|
||||
|
||||
return self::$cachedDatabaseData;
|
||||
return static::$cachedDatabaseData;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,8 +94,8 @@ trait MigrationsBase
|
||||
*/
|
||||
protected function setupMigrationTable(): array
|
||||
{
|
||||
if (!empty(self::$cachedTableData)) {
|
||||
return self::$cachedTableData;
|
||||
if (!empty(static::$cachedTableData)) {
|
||||
return static::$cachedTableData;
|
||||
}
|
||||
|
||||
// Ensure database exists first
|
||||
@@ -141,12 +141,12 @@ trait MigrationsBase
|
||||
$this->assertEquals('available', $response['body']['status']);
|
||||
}, 5000, 500);
|
||||
|
||||
self::$cachedTableData = [
|
||||
static::$cachedTableData = [
|
||||
'databaseId' => $databaseId,
|
||||
'tableId' => $tableId,
|
||||
];
|
||||
|
||||
return self::$cachedTableData;
|
||||
return static::$cachedTableData;
|
||||
}
|
||||
|
||||
public function performMigrationSync(array $body): array
|
||||
@@ -670,7 +670,7 @@ trait MigrationsBase
|
||||
]);
|
||||
|
||||
// Clear the cache since we cleaned up
|
||||
self::$cachedDatabaseData = [];
|
||||
static::$cachedDatabaseData = [];
|
||||
}
|
||||
|
||||
public function testAppwriteMigrationDatabasesRow(): void
|
||||
@@ -757,8 +757,8 @@ trait MigrationsBase
|
||||
]);
|
||||
|
||||
// Clear the caches since we cleaned up
|
||||
self::$cachedDatabaseData = [];
|
||||
self::$cachedTableData = [];
|
||||
static::$cachedDatabaseData = [];
|
||||
static::$cachedTableData = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1331,7 +1331,7 @@ trait MigrationsBase
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertEventually(function () use ($missingColumn) {
|
||||
$this->assertEventually(function () use ($missingColumn, $databaseId, $tableId) {
|
||||
$migrationId = $missingColumn['body']['$id'];
|
||||
$migration = $this->client->call(Client::METHOD_GET, '/migrations/'.$migrationId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
@@ -1363,7 +1363,7 @@ trait MigrationsBase
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertEventually(function () use ($missingColumn) {
|
||||
$this->assertEventually(function () use ($missingColumn, $databaseId, $tableId) {
|
||||
$migrationId = $missingColumn['body']['$id'];
|
||||
$migration = $this->client->call(Client::METHOD_GET, '/migrations/'.$migrationId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
@@ -1395,7 +1395,7 @@ trait MigrationsBase
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertEventually(function () use ($irrelevantColumn) {
|
||||
$this->assertEventually(function () use ($irrelevantColumn, $databaseId, $tableId) {
|
||||
$migrationId = $irrelevantColumn['body']['$id'];
|
||||
$migration = $this->client->call(Client::METHOD_GET, '/migrations/'.$migrationId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
@@ -1422,7 +1422,7 @@ trait MigrationsBase
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertEventually(function () use ($migration) {
|
||||
$this->assertEventually(function () use ($migration, $databaseId, $tableId) {
|
||||
$migrationId = $migration['body']['$id'];
|
||||
$migration = $this->client->call(Client::METHOD_GET, '/migrations/'.$migrationId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
@@ -1464,7 +1464,7 @@ trait MigrationsBase
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertEventually(function () use ($migration) {
|
||||
$this->assertEventually(function () use ($migration, $databaseId, $tableId) {
|
||||
$migrationId = $migration['body']['$id'];
|
||||
$migration = $this->client->call(Client::METHOD_GET, '/migrations/'.$migrationId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
@@ -4018,7 +4018,7 @@ trait MigrationsBase
|
||||
]
|
||||
);
|
||||
|
||||
$this->assertEventually(function () use ($migration) {
|
||||
$this->assertEventually(function () use ($migration, $databaseId, $tableId) {
|
||||
$migrationId = $migration['body']['$id'];
|
||||
$migration = $this->client->call(Client::METHOD_GET, '/migrations/'.$migrationId, array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
||||
@@ -97,7 +97,6 @@ trait StorageBase
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
];
|
||||
$id = '';
|
||||
$largeFile = null;
|
||||
while (!feof($handle)) {
|
||||
$curlFile = new \CURLFile('data://' . $mimeType . ';base64,' . base64_encode(@fread($handle, $chunkSize)), $mimeType, 'large-file.mp4');
|
||||
$headers['content-range'] = 'bytes ' . ($counter * $chunkSize) . '-' . min(((($counter * $chunkSize) + $chunkSize) - 1), $size - 1) . '/' . $size;
|
||||
@@ -133,7 +132,7 @@ trait StorageBase
|
||||
self::$cachedBucketFile[$cacheKey] = [
|
||||
'bucketId' => $bucketId,
|
||||
'fileId' => $file['body']['$id'],
|
||||
'largeFileId' => $largeFile['body']['$id'] ?? '',
|
||||
'largeFileId' => $largeFile['body']['$id'],
|
||||
'largeBucketId' => $bucket2['body']['$id'],
|
||||
'webpFileId' => $webpFile['body']['$id']
|
||||
];
|
||||
@@ -262,7 +261,6 @@ trait StorageBase
|
||||
'x-appwrite-project' => $this->getProject()['$id']
|
||||
];
|
||||
$id = '';
|
||||
$largeFile = null;
|
||||
while (!feof($handle)) {
|
||||
$curlFile = new \CURLFile('data://' . $mimeType . ';base64,' . base64_encode(@fread($handle, $chunkSize)), $mimeType, 'large-file.mp4');
|
||||
$headers['content-range'] = 'bytes ' . ($counter * $chunkSize) . '-' . min(((($counter * $chunkSize) + $chunkSize) - 1), $size - 1) . '/' . $size;
|
||||
|
||||
@@ -29,8 +29,8 @@ class DatabasesStringTypesTest extends Scope
|
||||
protected function setupDatabaseAndTable(): array
|
||||
{
|
||||
$cacheKey = $this->getProject()['$id'] ?? 'default';
|
||||
if (!empty(self::$setupCache[$cacheKey])) {
|
||||
return self::$setupCache[$cacheKey];
|
||||
if (!empty(static::$setupCache[$cacheKey])) {
|
||||
return static::$setupCache[$cacheKey];
|
||||
}
|
||||
|
||||
$projectId = $this->getProject()['$id'];
|
||||
@@ -131,7 +131,7 @@ class DatabasesStringTypesTest extends Scope
|
||||
|
||||
// Cache before waiting so that if waitForAllAttributes times out,
|
||||
// subsequent calls don't try to re-create the same columns (causing 409)
|
||||
self::$setupCache[$cacheKey] = [
|
||||
static::$setupCache[$cacheKey] = [
|
||||
'databaseId' => $databaseId,
|
||||
'tableId' => $tableId,
|
||||
];
|
||||
@@ -139,7 +139,7 @@ class DatabasesStringTypesTest extends Scope
|
||||
// Wait for all columns to be available
|
||||
$this->waitForAllAttributes($databaseId, $tableId);
|
||||
|
||||
return self::$setupCache[$cacheKey];
|
||||
return static::$setupCache[$cacheKey];
|
||||
}
|
||||
|
||||
public function testCreateDatabase(): void
|
||||
|
||||
@@ -28,8 +28,8 @@ trait UsersBase
|
||||
protected function setupUser(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
if (!empty(self::$cachedUser[$projectId])) {
|
||||
return self::$cachedUser[$projectId];
|
||||
if (!empty(static::$cachedUser[$projectId])) {
|
||||
return static::$cachedUser[$projectId];
|
||||
}
|
||||
|
||||
$user = $this->client->call(Client::METHOD_POST, '/users', array_merge([
|
||||
@@ -52,16 +52,16 @@ trait UsersBase
|
||||
]);
|
||||
|
||||
if (!empty($response['body']['users'])) {
|
||||
self::$cachedUser[$projectId] = ['userId' => $response['body']['users'][0]['$id']];
|
||||
return self::$cachedUser[$projectId];
|
||||
static::$cachedUser[$projectId] = ['userId' => $response['body']['users'][0]['$id']];
|
||||
return static::$cachedUser[$projectId];
|
||||
}
|
||||
}
|
||||
|
||||
if ($user['headers']['status-code'] === 201) {
|
||||
self::$cachedUser[$projectId] = ['userId' => $user['body']['$id']];
|
||||
static::$cachedUser[$projectId] = ['userId' => $user['body']['$id']];
|
||||
}
|
||||
|
||||
return self::$cachedUser[$projectId];
|
||||
return static::$cachedUser[$projectId];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +90,7 @@ trait UsersBase
|
||||
protected function setupHashedPasswordUsers(): void
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
if (!empty(self::$cachedHashedPasswordUsers[$projectId])) {
|
||||
if (!empty(static::$cachedHashedPasswordUsers[$projectId])) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ trait UsersBase
|
||||
'passwordSignerKey' => 'XyEKE9RcTDeLEsL/RjwPDBv/RqDl8fb3gpYEOQaPihbxf1ZAtSOHCjuAAa7Q3oHpCYhXSN9tizHgVOwn6krflQ==',
|
||||
]);
|
||||
|
||||
self::$cachedHashedPasswordUsers[$projectId] = true;
|
||||
static::$cachedHashedPasswordUsers[$projectId] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,8 +189,8 @@ trait UsersBase
|
||||
protected function setupUserTarget(): array
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
if (!empty(self::$cachedUserTarget[$projectId])) {
|
||||
return self::$cachedUserTarget[$projectId];
|
||||
if (!empty(static::$cachedUserTarget[$projectId])) {
|
||||
return static::$cachedUserTarget[$projectId];
|
||||
}
|
||||
|
||||
$data = $this->setupUser();
|
||||
@@ -233,10 +233,10 @@ trait UsersBase
|
||||
]);
|
||||
|
||||
if ($response['headers']['status-code'] === 201) {
|
||||
self::$cachedUserTarget[$projectId] = $response['body'];
|
||||
static::$cachedUserTarget[$projectId] = $response['body'];
|
||||
}
|
||||
|
||||
return self::$cachedUserTarget[$projectId] ?? [];
|
||||
return static::$cachedUserTarget[$projectId] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,7 +247,7 @@ trait UsersBase
|
||||
$data = $this->setupUser();
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
||||
if (self::$userNameUpdated) {
|
||||
if (static::$userNameUpdated) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -258,7 +258,7 @@ trait UsersBase
|
||||
'name' => 'Updated name',
|
||||
]);
|
||||
|
||||
self::$userNameUpdated = true;
|
||||
static::$userNameUpdated = true;
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ trait UsersBase
|
||||
$data = $this->setupUser();
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
||||
if (self::$userEmailUpdated) {
|
||||
if (static::$userEmailUpdated) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ trait UsersBase
|
||||
'email' => 'users.service@updated.com',
|
||||
]);
|
||||
|
||||
self::$userEmailUpdated = true;
|
||||
static::$userEmailUpdated = true;
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ trait UsersBase
|
||||
$data = $this->setupUser();
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
||||
if (self::$userNumberUpdated) {
|
||||
if (static::$userNumberUpdated) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ trait UsersBase
|
||||
'number' => '+910000000000',
|
||||
]);
|
||||
|
||||
self::$userNumberUpdated = true;
|
||||
static::$userNumberUpdated = true;
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -474,7 +474,7 @@ trait UsersBase
|
||||
|
||||
// Cache the user ID for other tests
|
||||
$projectId = $this->getProject()['$id'];
|
||||
self::$cachedUser[$projectId] = ['userId' => $body['$id']];
|
||||
static::$cachedUser[$projectId] = ['userId' => $body['$id']];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1274,7 +1274,7 @@ trait UsersBase
|
||||
$this->assertEquals($user['body']['name'], 'Updated name');
|
||||
|
||||
// Mark name as updated for search tests
|
||||
self::$userNameUpdated = true;
|
||||
static::$userNameUpdated = true;
|
||||
}
|
||||
|
||||
public function testUpdateUserNameSearch(): void
|
||||
@@ -1357,7 +1357,7 @@ trait UsersBase
|
||||
$this->assertEquals($user['body']['email'], 'users.service@updated.com');
|
||||
|
||||
// Mark email as updated for search tests
|
||||
self::$userEmailUpdated = true;
|
||||
static::$userEmailUpdated = true;
|
||||
}
|
||||
|
||||
public function testUpdateUserEmailSearch(): void
|
||||
@@ -1645,7 +1645,7 @@ trait UsersBase
|
||||
$this->assertEquals($response['body']['type'], $errorType);
|
||||
|
||||
// Mark phone as updated for search tests
|
||||
self::$userNumberUpdated = true;
|
||||
static::$userNumberUpdated = true;
|
||||
}
|
||||
|
||||
public function testUpdateTwoUsersPhoneToEmpty(): void
|
||||
@@ -1954,7 +1954,7 @@ trait UsersBase
|
||||
|
||||
// Cache for other tests
|
||||
$projectId = $this->getProject()['$id'];
|
||||
self::$cachedUserTarget[$projectId] = $response['body'];
|
||||
static::$cachedUserTarget[$projectId] = $response['body'];
|
||||
}
|
||||
|
||||
public function testUpdateUserTarget(): void
|
||||
@@ -1973,7 +1973,7 @@ trait UsersBase
|
||||
|
||||
// Update cache with new data
|
||||
$projectId = $this->getProject()['$id'];
|
||||
self::$cachedUserTarget[$projectId] = $response['body'];
|
||||
static::$cachedUserTarget[$projectId] = $response['body'];
|
||||
}
|
||||
|
||||
public function testListUserTarget(): void
|
||||
@@ -2014,7 +2014,7 @@ trait UsersBase
|
||||
|
||||
// Clear cached target since it was deleted
|
||||
$projectId = $this->getProject()['$id'];
|
||||
unset(self::$cachedUserTarget[$projectId]);
|
||||
unset(static::$cachedUserTarget[$projectId]);
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/users/' . $data['userId'] . '/targets', array_merge([
|
||||
'content-type' => 'application/json',
|
||||
|
||||
@@ -37,8 +37,8 @@ class VCSConsoleClientTest extends Scope
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
||||
if (!empty(self::$cachedInstallationId[$projectId])) {
|
||||
return self::$cachedInstallationId[$projectId];
|
||||
if (!empty(static::$cachedInstallationId[$projectId])) {
|
||||
return static::$cachedInstallationId[$projectId];
|
||||
}
|
||||
|
||||
$response = $this->client->call(Client::METHOD_GET, '/mock/github/callback', array_merge([
|
||||
@@ -48,8 +48,8 @@ class VCSConsoleClientTest extends Scope
|
||||
'projectId' => $projectId,
|
||||
]);
|
||||
|
||||
self::$cachedInstallationId[$projectId] = $response['body']['installationId'];
|
||||
return self::$cachedInstallationId[$projectId];
|
||||
static::$cachedInstallationId[$projectId] = $response['body']['installationId'];
|
||||
return static::$cachedInstallationId[$projectId];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,8 +60,8 @@ class VCSConsoleClientTest extends Scope
|
||||
{
|
||||
$projectId = $this->getProject()['$id'];
|
||||
|
||||
if (!empty(self::$cachedFunctionData[$projectId])) {
|
||||
return self::$cachedFunctionData[$projectId];
|
||||
if (!empty(static::$cachedFunctionData[$projectId])) {
|
||||
return static::$cachedFunctionData[$projectId];
|
||||
}
|
||||
|
||||
$installationId = $this->setupInstallation();
|
||||
@@ -86,12 +86,12 @@ class VCSConsoleClientTest extends Scope
|
||||
'providerBranch' => 'main',
|
||||
]);
|
||||
|
||||
self::$cachedFunctionData[$projectId] = [
|
||||
static::$cachedFunctionData[$projectId] = [
|
||||
'installationId' => $installationId,
|
||||
'functionId' => $function['body']['$id']
|
||||
];
|
||||
|
||||
return self::$cachedFunctionData[$projectId];
|
||||
return static::$cachedFunctionData[$projectId];
|
||||
}
|
||||
|
||||
public function testGitHubAuthorize(): void
|
||||
|
||||
@@ -25,7 +25,7 @@ class KeyTest extends TestCase
|
||||
$roleScopes = Config::getParam('roles', [])[User::ROLE_APPS]['scopes'];
|
||||
$guestRoleScopes = Config::getParam('roles', [])[User::ROLE_GUESTS]['scopes'];
|
||||
|
||||
$key = self::generateKey($projectId, $usage, $scopes);
|
||||
$key = static::generateKey($projectId, $usage, $scopes);
|
||||
$decoded = Key::decode(
|
||||
project: new Document(['$id' => $projectId]),
|
||||
team: new Document(),
|
||||
@@ -50,7 +50,7 @@ class KeyTest extends TestCase
|
||||
'previewAuthDisabled' => true,
|
||||
'deploymentStatusIgnored' => true,
|
||||
];
|
||||
$key = self::generateKey($projectId, $usage, $scopes, extra: $extra);
|
||||
$key = static::generateKey($projectId, $usage, $scopes, extra: $extra);
|
||||
$decoded = Key::decode(
|
||||
project: new Document(['$id' => $projectId]),
|
||||
team: new Document(),
|
||||
@@ -88,7 +88,7 @@ class KeyTest extends TestCase
|
||||
$this->assertEquals('UNKNOWN', $decoded->getName());
|
||||
|
||||
// Decode expired dynamic key
|
||||
$expiredKey = self::generateKey($projectId, $usage, $scopes, maxAge: 1, timestamp: time() - 60);
|
||||
$expiredKey = static::generateKey($projectId, $usage, $scopes, maxAge: 1, timestamp: time() - 60);
|
||||
\sleep(2);
|
||||
$decoded = Key::decode(
|
||||
project: new Document(['$id' => $projectId]),
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace Tests\Unit\Event;
|
||||
use Appwrite\Event\Event;
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Utopia\Queue\Publisher;
|
||||
|
||||
require_once __DIR__ . '/../../../app/init.php';
|
||||
|
||||
@@ -12,7 +13,7 @@ class EventTest extends TestCase
|
||||
{
|
||||
protected ?Event $object = null;
|
||||
protected string $queue = '';
|
||||
protected MockPublisher $publisher;
|
||||
protected Publisher $publisher;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
|
||||
@@ -76,6 +76,11 @@ class HeadersTest extends TestCase
|
||||
];
|
||||
$this->assertFalse($this->object->isValid($headers));
|
||||
|
||||
$headers = [
|
||||
null => 'value',
|
||||
];
|
||||
$this->assertFalse($this->object->isValid($headers));
|
||||
|
||||
$headers = [
|
||||
'X-Header' => null,
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user