1
0
Fork 0
mirror of synced 2024-06-14 00:34:51 +12:00

Merge remote-tracking branch 'origin/feat-db-pools-master' into feat-db-pools-db-pools-master-sync

This commit is contained in:
Damodar Lohani 2023-07-10 03:06:48 +00:00
commit f59d3563f4
90 changed files with 4065 additions and 2030 deletions

3
.env
View file

@ -41,6 +41,7 @@ _APP_SMTP_PORT=1025
_APP_SMTP_SECURE=
_APP_SMTP_USERNAME=
_APP_SMTP_PASSWORD=
_APP_USERS_STATS_RECIPIENTS=
_APP_HAMSTER_INTERVAL=86400
_APP_HAMSTER_TIME=21:00
_APP_MIXPANEL_TOKEN=
@ -76,3 +77,5 @@ _APP_GRAPHQL_MAX_DEPTH=3
_APP_REGION=default
_APP_DOCKER_HUB_USERNAME=
_APP_DOCKER_HUB_PASSWORD=
_APP_CONSOLE_GITHUB_SECRET=
_APP_CONSOLE_GITHUB_APP_ID=

46
.github/workflows/publish.yml vendored Normal file
View file

@ -0,0 +1,46 @@
name: "Build and Publish"
on:
push:
tags:
- appwrite-*
jobs:
build-publish:
name: Build and Publish
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 2
submodules: recursive
ref: feat-db-pools-master
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: appwrite/cloud
tags: |
type=ref,event=tag
- name: Build & Publish to DockerHub
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64
build-args: |
VERSION=${{ steps.meta.outputs.version }}
VITE_APPWRITE_GROWTH_ENDPOINT=https://growth.appwrite.io/v1
VITE_GA_PROJECT=G-L7G2B6PLDS
VITE_CONSOLE_MODE=cloud
push: true
tags: ${{ steps.meta.outputs.tags }}

2
.gitmodules vendored
View file

@ -1,4 +1,4 @@
[submodule "app/console"]
path = app/console
url = https://github.com/appwrite/console
branch = 2.2.2
branch = cloud-cards

View file

@ -29,7 +29,7 @@ ENV VITE_APPWRITE_GROWTH_ENDPOINT=$VITE_APPWRITE_GROWTH_ENDPOINT
RUN npm ci
RUN npm run build
FROM appwrite/base:0.2.0 as final
FROM appwrite/base:0.2.2 as final
LABEL maintainer="team@appwrite.io"
@ -91,6 +91,7 @@ COPY --from=node /usr/local/src/console/build /usr/src/code/console
# Add Source Code
COPY ./app /usr/src/code/app
COPY ./public /usr/src/code/public
COPY ./bin /usr/local/bin
COPY ./docs /usr/src/code/docs
COPY ./src /usr/src/code/src
@ -112,7 +113,11 @@ RUN mkdir -p /storage/uploads && \
# Executables
RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/patch-delete-schedule-updated-at-attribute && \
chmod +x /usr/local/bin/maintenance && \
chmod +x /usr/local/bin/clear-card-cache && \
chmod +x /usr/local/bin/calc-users-stats && \
chmod +x /usr/local/bin/calc-tier-stats && \
chmod +x /usr/local/bin/patch-delete-project-collections && \
chmod +x /usr/local/bin/maintenance && \
chmod +x /usr/local/bin/volume-sync && \
chmod +x /usr/local/bin/install && \
chmod +x /usr/local/bin/migrate && \

View file

@ -61,7 +61,7 @@ CLI::setResource('dbForConsole', function ($pools, $cache) {
$dbForConsole->setNamespace('console');
// Ensure tables exist
$collections = Config::getParam('collections', []);
$collections = Config::getParam('collections', [])['console'];
$last = \array_key_last($collections);
if (!($dbForConsole->exists($dbForConsole->getDefaultDatabase(), $last))) { /** TODO cache ready variable using registry */

View file

@ -0,0 +1 @@
[1297371,1759475,6360216,20852629,5857008,19310830,9708641,26739219,23742426,22174310,77877486,1477010,29069505,62933155,45863583,42211,176163,58045728,7091609,31401437,1911066,66096031,22432834,43054051,79051850,22895284,100597998,91385411,49699333,7818620,11004008,54898623,27856297,33250853,49375670,30630364,50206,47822499,7481165,52557347,13681567,20492520,51369094,51821861,50256986,28431370,13692220,24373771,15938422,27148250,14805534,36137226,36071208,40193621,56179878,53281158,48085134,19358691,32528768,19733683,37348419,13719696,18309412,42106787,53618500,5306011,4408379,35486736,18586611,15062564,27011453,56090587,44623032,25107942,81188,33922418,16717633,32809211,69401139,25815659,4104127,20889958,3102249,12476526,2635185,28495651,50957556,2791280,31023616,20955511,835733,471907,69008866,44906587,45271396,76054330,45748739,41908747,23402178,47356149,8921,36632821,3668741,71702982,29725587,26303198,785830,51410502,60089135,2847349,43172716,48546075,8216525,41161981,51828039,4334997,80918302,38534289,47860497,80036766,41341387,49818988,58487637,29237374,46913894,5148229,4377199,29686102,26272249,75117692,4090256,27357868,33062368,38664231,46695441,743291,22633385,6368283,11593067,45097959,43381712,3284228,1972717,33012425,61755381,25405707,3144291,44156359,5497267,7423905,20716175,28586681,5975506,23518097,22187384,24191952,7768078,971530,51240166,55633427,34207400,77061285,11719476,35950229,66742927,34406802,802933,50047839,39148877,26602940,9693472,44273767,19362725,31209978,30521594,686298,6237394,35039730,42580581,36671793,8502129,8466918,81866614,54903252,28373606,13381361,72331432,30694270,5355510,8209163,86675510,9453522,42496309,56145786,2149381,393945,22084723,52621436,8872447,5575392,29619660,5547479,8852116,11151445,4717349,17725274,65615065,18537755,29292618,53044263,26597930,10313411,55998629,77529288,17404636,33729848,19422168,17916404,66111735,10329006,33502846,398230,81643826,105039167,47522632,91655303,9774614,10603631,284924,60857954,22885912,116552306,36103454,794606,27729549,1754457,36594527,13899668,78664749,47406531,27698189,5305654,53345517,6756412,29176704,77790497,47504894,37251540,52361778,52200375,1351177,66022861,73975409,25745396,31433638,37118134,43210805,20317665,11923975,47187468,16362381,36751163,14959876,32362757,65529384,52352285,74085816,3628535,43902034,75667593,26132902,466713,617558,96806061,33605526,11290524,43621940,12446314,17146935,55018955,56096559,79797000,40014186,34449936,58387964,23368207,42414965,44056349,33743031,12294525,58251592,33755729,9021747,932084,11428067,97121933,80122730,60894542,58583793,56051809,32243289,9934371,90936802,74638775,65399526,77604,64524822,47782249,43633955,42793632,55969597,72334601,82395440,92818577,60866204,65016769,23725091,45892107,55308895,86314140,82756460,47685349,63562160,73419211,1613216,50882624,91469717,46166258,60927324,41763158,83607556,2171717,50497814,39427312,61322830,40076195,39419448,29397545,55090719,53259730,20885012,64558515,69677883,55741087,72426535,46033036,68477507,30376878,73700530,25518600,29922887,36229969,47573417,40424087,49054503,16880385,22801227,72848513,64347914,814402,49149679,55017867,49481876,67067955,31439735,63878173,80322286,43746210,17332970,22702905,62476876,89888292,75736952,54059881,90782137,63588969,57111920,63330165,70258211,46371923,17837758,59364507,52203828,60147326,18481195,74822422,9803078,67309607,60410049,47360939,19922556,90848252,24698014,58886915,63579762,96648934,68523530,60518745,37345795,3929651,54993657,52061363,43019989,5787917,94674993,71593494,17143469,10288548,1830380,71510505,59124772,2335145,70798495,46474346,49263351,52062536,63151043,65248303,26071571,53626355,43992469,60785452,63467479,71837281,19490891,58628586,38250310,7271718,1110414,57227290,11625672,85063520,88965873,70096901,42029519,85363195,64471630,69353350,66922161,2221746,100430077,12299813,62690310,68282006,99184676,2450,22989561,22212661,59973863,11232940,76688923,22321353,77732479,84286404,32268377,34828782,23068019,57074509,24620969,20735983,26173690,75809937,49760818,86646105,52617262]

View file

@ -0,0 +1,33 @@
{
"eldad@appwrite.io": { "memberSince": "2020-10-15", "spot": "0", "gitHub": "eldadfux" },
"christy@appwrite.io": { "memberSince": "2020-12-01", "spot": "1", "gitHub": "christyjacob4" },
"torsten@appwrite.io": { "memberSince": "2020-12-28", "spot": "2", "gitHub": "torstendittmann" },
"damodar@appwrite.io": { "memberSince": "2021-01-02", "spot": "3", "gitHub": "lohanidamodar" },
"bradley@appwrite.io": { "memberSince": "2021-05-21", "spot": "5", "gitHub": "PineappleIOnic" },
"jake@appwrite.io": { "memberSince": "2021-06-28", "spot": "6", "gitHub": "abnegate" },
"sara@appwrite.io": { "memberSince": "2021-08-16", "spot": "7", "gitHub": "sarakaandorp" },
"matej@appwrite.io": { "memberSince": "2021-08-23", "spot": "8", "gitHub": "meldiron" },
"aditya@appwrite.io": { "memberSince": "2021-09-01", "spot": "9", "gitHub": "adityaoberai" },
"wess@appwrite.io": { "memberSince": "2021-11-08", "spot": "12", "gitHub": "wess" },
"may@appwrite.io": { "memberSince": "2021-11-28", "spot": "14", "gitHub": "MayEnder" },
"elad@appwrite.io": { "memberSince": "2021-12-19", "spot": "15", "gitHub": "elad2412" },
"vincent@appwrite.io": { "memberSince": "2022-01-01", "spot": "16", "gitHub": "gewenyu99" },
"haimantika@appwrite.io": { "memberSince": "2022-04-01", "spot": "18", "gitHub": "Haimantika" },
"chen@appwrite.io": { "memberSince": "2022-01-24", "spot": "19", "gitHub": "chenparnasa" },
"tessa@appwrite.io": { "memberSince": "2022-04-21", "spot": "20", "gitHub": "tessamero" },
"shimon@appwrite.io": { "memberSince": "2022-05-01", "spot": "23", "gitHub": "shimonewman" },
"shmuel@appwrite.io": { "memberSince": "2022-03-20", "spot": "24", "gitHub": "fogelito" },
"arman@appwrite.io": { "memberSince": "2022-04-04", "spot": "25", "gitHub": "ArmanNik" },
"carla@appwrite.io": { "memberSince": "2022-04-04", "spot": "26", "gitHub": "heyCarla" },
"emma@appwrite.io": { "memberSince": "2022-05-08", "spot": "27", "gitHub": "emmacarpagnano1" },
"dylan@appwrite.io": { "memberSince": "2022-05-09", "spot": "28", "gitHub": "DylanG-64" },
"steven@appwrite.io": { "memberSince": "2022-07-01", "spot": "30", "gitHub": "stnguyen90" },
"jyoti@appwrite.io": { "memberSince": "2022-10-24", "spot": "31", "gitHub": "joeyouss" },
"jade@appwrite.io": { "memberSince": "2022-10-31", "spot": "32", "gitHub": "dajebp" },
"khushboo@appwrite.io": { "memberSince": "2021-11-08", "spot": "13", "gitHub": "vermakhushboo" },
"thomas@appwrite.io": { "memberSince": "2022-11-03", "spot": "34", "gitHub": "TGlide" },
"holly@appwrite.io": { "memberSince": "2022-12-05", "spot": "35", "gitHub": "HollyBarclay" },
"laura@appwrite.io": { "memberSince": "2023-01-25", "spot": "36", "gitHub": "LauraDuRy" },
"caio@appwrite.io": { "memberSince": "2023-03-27", "spot": "37", "gitHub": "ariascaio" },
"luke@appwrite.io": { "memberSince": "2023-05-04", "spot": "38", "gitHub": "loks0n" }
}

View file

@ -0,0 +1,9 @@
{
"bishwajeet.techmaster@gmail.com": { "memberSince": "2023-02-07" },
"lucasaudart@gmail.com": { "memberSince": "2023-02-07" },
"tkarmakar27112000@gmail.com": { "memberSince": "2023-02-07" },
"alves.mckl@gmail.com": { "memberSince": "2023-02-07" },
"dpns_nampula@rnlay.com": { "memberSince": "2023-02-07" },
"a.stephensimon@outlook.com": { "memberSince": "2023-02-07" },
"hidianapham@gmail.com": { "memberSince": "2023-02-07" }
}

File diff suppressed because it is too large Load diff

View file

@ -112,7 +112,7 @@ return [
],
Exception::USER_BLOCKED => [
'name' => Exception::USER_BLOCKED,
'description' => 'The current user has been blocked. You can unblock the user from the Appwrite console.',
'description' => 'The current user has been blocked.',
'code' => 401,
],
Exception::USER_INVALID_TOKEN => [
@ -484,6 +484,11 @@ return [
'description' => 'Project with the requested ID could not be found. Please check the value of the X-Appwrite-Project header to ensure the correct project ID is being used.',
'code' => 404,
],
Exception::PROJECT_ALREADY_EXISTS => [
'name' => Exception::PROJECT_ALREADY_EXISTS,
'description' => 'Project with the requested ID already exists.',
'code' => 409,
],
Exception::PROJECT_UNKNOWN => [
'name' => Exception::PROJECT_UNKNOWN,
'description' => 'The project ID is either missing or not valid. Please check the value of the X-Appwrite-Project header to ensure the correct project ID is being used.',
@ -541,9 +546,14 @@ return [
],
Exception::DOMAIN_ALREADY_EXISTS => [
'name' => Exception::DOMAIN_ALREADY_EXISTS,
'description' => 'A Domain with the requested ID already exists.',
'description' => 'The requested domain is currently in use by a project.',
'code' => 409,
],
Exception::DOMAIN_FORBIDDEN => [
'name' => Exception::DOMAIN_FORBIDDEN,
'description' => 'The requested domain cannot be used as a custom domain.',
'code' => 403,
],
Exception::VARIABLE_NOT_FOUND => [
'name' => Exception::VARIABLE_NOT_FOUND,
'description' => 'Variable with the requested ID could not be found.',

View file

@ -572,6 +572,10 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$name = $oauth2->getUserName($accessToken);
$email = $oauth2->getUserEmail($accessToken);
if (empty($email)) {
throw new Exception(Exception::USER_UNAUTHORIZED, 'OAuth provider failed to return email.');
}
/**
* Is verified is not used yet, since we don't know after an accout is created anymore if it was verified or not.
*/
@ -1813,6 +1817,12 @@ App::patch('/v1/account/status')
$response->addHeader('X-Fallback-Cookies', \json_encode([]));
}
$protocol = $request->getProtocol();
$response
->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
;
$response->dynamic($user, Response::MODEL_ACCOUNT);
});

View file

@ -7,9 +7,16 @@ use Appwrite\Utopia\Response;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Image\Image;
use Utopia\Logger\Log;
use Utopia\Logger\Logger;
use Utopia\Validator\Boolean;
use Utopia\Validator\HexColor;
use Utopia\Validator\Range;
@ -49,11 +56,139 @@ $avatarCallback = function (string $type, string $code, int $width, int $height,
$response
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
->setContentType('image/png')
->file($data)
;
->file($data);
unset($image);
};
$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForConsole, ?Logger $logger) {
try {
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
$sessions = $user->getAttribute('sessions', []);
$gitHubSession = null;
foreach ($sessions as $session) {
if ($session->getAttribute('provider', '') === 'github') {
$gitHubSession = $session;
break;
}
}
if (empty($gitHubSession)) {
throw new Exception(Exception::GENERAL_UNKNOWN, 'GitHub session not found.');
}
$provider = $gitHubSession->getAttribute('provider', '');
$accessToken = $gitHubSession->getAttribute('providerAccessToken');
$accessTokenExpiry = $gitHubSession->getAttribute('providerAccessTokenExpiry');
$refreshToken = $gitHubSession->getAttribute('providerRefreshToken');
$appId = $project->getAttribute('authProviders', [])[$provider . 'Appid'] ?? '';
$appSecret = $project->getAttribute('authProviders', [])[$provider . 'Secret'] ?? '{}';
$className = 'Appwrite\\Auth\\OAuth2\\' . \ucfirst($provider);
if (!\class_exists($className)) {
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
}
$oauth2 = new $className($appId, $appSecret, '', [], []);
$isExpired = new \DateTime($accessTokenExpiry) < new \DateTime('now');
if ($isExpired) {
try {
$oauth2->refreshTokens($refreshToken);
$accessToken = $oauth2->getAccessToken('');
$refreshToken = $oauth2->getRefreshToken('');
$verificationId = $oauth2->getUserID($accessToken);
if (empty($verificationId)) {
throw new \Exception("Locked tokens."); // Race codition, handeled in catch
}
$gitHubSession
->setAttribute('providerAccessToken', $accessToken)
->setAttribute('providerRefreshToken', $refreshToken)
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry('')));
Authorization::skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession));
$dbForProject->deleteCachedDocument('users', $user->getId());
} catch (Throwable $err) {
$index = 0;
do {
$previousAccessToken = $gitHubSession->getAttribute('providerAccessToken');
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
$sessions = $user->getAttribute('sessions', []);
$gitHubSession = new Document();
foreach ($sessions as $session) {
if ($session->getAttribute('provider', '') === 'github') {
$gitHubSession = $session;
break;
}
}
$accessToken = $gitHubSession->getAttribute('providerAccessToken');
if ($accessToken !== $previousAccessToken) {
break;
}
$index++;
\usleep(500000);
} while ($index < 10);
}
}
$oauth2 = new $className($appId, $appSecret, '', [], []);
$githubUser = $oauth2->getUserSlug($accessToken);
$githubId = $oauth2->getUserID($accessToken);
return [
'name' => $githubUser,
'id' => $githubId
];
} catch (Exception $error) {
if ($logger) {
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
$log = new Log();
$log->setNamespace('console');
$log->setServer(\gethostname());
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
$log->addTag('code', $error->getCode());
$log->addTag('verboseType', get_class($error));
$log->addExtra('file', $error->getFile());
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
$log->setAction('avatarsGetGitHub');
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
$responseCode = $logger->addLog($log);
Console::info('GitHub error log pushed with status code: ' . $responseCode);
}
Console::warning("Failed: {$error->getMessage()}");
Console::warning($error->getTraceAsString());
return [];
}
return [];
};
App::get('/v1/avatars/credit-cards/:code')
->desc('Get Credit Card Icon')
->groups(['api', 'avatars'])
@ -160,8 +295,7 @@ App::get('/v1/avatars/image')
$response
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
->setContentType('image/png')
->file($data)
;
->file($data);
unset($image);
});
@ -274,8 +408,7 @@ App::get('/v1/avatars/favicon')
$response
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
->setContentType('image/x-icon')
->file($data)
;
->file($data);
}
$fetch = @\file_get_contents($outputHref, false);
@ -292,8 +425,7 @@ App::get('/v1/avatars/favicon')
$response
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
->setContentType('image/png')
->file($data)
;
->file($data);
unset($image);
});
@ -334,8 +466,7 @@ App::get('/v1/avatars/qr')
$response
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->setContentType('image/png')
->send($image->output('png', 9))
;
->send($image->output('png', 9));
});
App::get('/v1/avatars/initials')
@ -419,6 +550,680 @@ App::get('/v1/avatars/initials')
$response
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->setContentType('image/png')
->file($image->getImageBlob())
;
->file($image->getImageBlob());
});
App::get('/v1/cards/cloud')
->desc('Get Front Of Cloud Card')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
->label('cache', true)
->label('cache.resourceType', 'cards/cloud')
->label('cache.resource', 'card/{request.userId}')
->label('docs', false)
->label('origin', '*')
->param('userId', '', new UID(), 'User ID.', true)
->param('mock', '', new WhiteList(['employee', 'employee-2digit', 'hero', 'contributor', 'normal', 'platinum', 'normal-no-github', 'normal-long']), 'Mocking behaviour.', true)
->param('width', 0, new Range(0, 512), 'Resize image width, Pass an integer between 0 to 512.', true)
->param('height', 0, new Range(0, 320), 'Resize image height, Pass an integer between 0 to 320.', true)
->inject('user')
->inject('project')
->inject('dbForProject')
->inject('dbForConsole')
->inject('response')
->inject('heroes')
->inject('contributors')
->inject('employees')
->inject('logger')
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
if ($user->isEmpty() && empty($mock)) {
throw new Exception(Exception::USER_NOT_FOUND);
}
if (!$mock) {
$name = $user->getAttribute('name', 'Anonymous');
$email = $user->getAttribute('email', '');
$createdAt = new \DateTime($user->getCreatedAt());
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
$githubName = $gitHub['name'] ?? '';
$githubId = $gitHub['id'] ?? '';
$isHero = \array_key_exists($email, $heroes);
$isContributor = \in_array($githubId, $contributors);
$isEmployee = \array_key_exists($email, $employees);
$employeeNumber = $isEmployee ? $employees[$email]['spot'] : '';
if ($isHero) {
$createdAt = new \DateTime($heroes[$email]['memberSince'] ?? '');
} elseif ($isEmployee) {
$createdAt = new \DateTime($employees[$email]['memberSince'] ?? '');
}
if (!$isEmployee && !empty($githubName)) {
$employeeGitHub = \array_search(\strtolower($githubName), \array_map(fn ($employee) => \strtolower($employee['gitHub']) ?? '', $employees));
if (!empty($employeeGitHub)) {
$isEmployee = true;
$employeeNumber = $isEmployee ? $employees[$employeeGitHub]['spot'] : '';
$createdAt = new \DateTime($employees[$employeeGitHub]['memberSince'] ?? '');
}
}
$isPlatinum = $user->getInternalId() % 100 === 0;
} else {
$name = $mock === 'normal-long' ? 'Sir First Walter O\'Brian Junior' : 'Walter O\'Brian';
$createdAt = new \DateTime('now');
$githubName = $mock === 'normal-no-github' ? '' : ($mock === 'normal-long' ? 'sir-first-walterobrian-junior' : 'walterobrian');
$isHero = $mock === 'hero';
$isContributor = $mock === 'contributor';
$isEmployee = \str_starts_with($mock, 'employee');
$employeeNumber = match ($mock) {
'employee' => '1',
'employee-2digit' => '18',
default => ''
};
$isPlatinum = $mock === 'platinum';
}
if ($isEmployee) {
$isContributor = false;
$isHero = false;
}
if ($isHero) {
$isContributor = false;
$isEmployee = false;
}
if ($isContributor) {
$isHero = false;
$isEmployee = false;
}
$isGolden = $isEmployee || $isHero || $isContributor;
$isPlatinum = $isGolden ? false : $isPlatinum;
$memberSince = \strtoupper('Member since ' . $createdAt->format('M') . ' ' . $createdAt->format('d') . ', ' . $createdAt->format('o'));
$imagePath = $isGolden ? 'front-golden.png' : ($isPlatinum ? 'front-platinum.png' : 'front.png');
$baseImage = new \Imagick(__DIR__ . '/../../../public/images/cards/cloud/' . $imagePath);
if ($isEmployee) {
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/employee.png');
$image->setGravity(Imagick::GRAVITY_CENTER);
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 793, 35);
$text = new \ImagickDraw();
$text->setTextAlignment(Imagick::ALIGN_CENTER);
$text->setFont(__DIR__ . '/../../../public/fonts/Inter-Bold.ttf');
$text->setFillColor(new \ImagickPixel('#FFFADF'));
$text->setFontSize(\strlen($employeeNumber) <= 2 ? 54 : 48);
$text->setFontWeight(700);
$metricsText = $baseImage->queryFontMetrics($text, $employeeNumber);
$hashtag = new \ImagickDraw();
$hashtag->setTextAlignment(Imagick::ALIGN_CENTER);
$hashtag->setFont(__DIR__ . '/../../../public/fonts/Inter-Bold.ttf');
$hashtag->setFillColor(new \ImagickPixel('#FFFADF'));
$hashtag->setFontSize(28);
$hashtag->setFontWeight(700);
$metricsHashtag = $baseImage->queryFontMetrics($hashtag, '#');
$startX = 898;
$totalWidth = $metricsHashtag['textWidth'] + 12 + $metricsText['textWidth'];
$hashtagX = ($metricsHashtag['textWidth'] / 2);
$textX = $hashtagX + 12 + ($metricsText['textWidth'] / 2);
$hashtagX -= $totalWidth / 2;
$textX -= $totalWidth / 2;
$hashtagX += $startX;
$textX += $startX;
$baseImage->annotateImage($hashtag, $hashtagX, 150, 0, '#');
$baseImage->annotateImage($text, $textX, 150, 0, $employeeNumber);
}
if ($isContributor) {
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/contributor.png');
$image->setGravity(Imagick::GRAVITY_CENTER);
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 793, 34);
}
if ($isHero) {
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/hero.png');
$image->setGravity(Imagick::GRAVITY_CENTER);
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 793, 34);
}
setlocale(LC_ALL, "en_US.utf8");
// $name = \iconv("utf-8", "ascii//TRANSLIT", $name);
// $memberSince = \iconv("utf-8", "ascii//TRANSLIT", $memberSince);
// $githubName = \iconv("utf-8", "ascii//TRANSLIT", $githubName);
$text = new \ImagickDraw();
$text->setTextAlignment(Imagick::ALIGN_CENTER);
$text->setFont(__DIR__ . '/../../../public/fonts/Poppins-Bold.ttf');
$text->setFillColor(new \ImagickPixel('#FFFFFF'));
if (\strlen($name) > 32) {
$name = \substr($name, 0, 32);
}
if (\strlen($name) <= 23) {
$text->setFontSize(80);
$scalingDown = false;
} else {
$text->setFontSize(54);
$scalingDown = true;
}
$text->setFontWeight(700);
$baseImage->annotateImage($text, 512, 477, 0, $name);
$text = new \ImagickDraw();
$text->setTextAlignment(Imagick::ALIGN_CENTER);
$text->setFont(__DIR__ . '/../../../public/fonts/Inter-SemiBold.ttf');
$text->setFillColor(new \ImagickPixel($isGolden || $isPlatinum ? '#FFFFFF' : '#FFB9CC'));
$text->setFontSize(27);
$text->setFontWeight(600);
$text->setTextKerning(1.08);
$baseImage->annotateImage($text, 512, 541, 0, \strtoupper($memberSince));
if (!empty($githubName)) {
$text = new \ImagickDraw();
$text->setTextAlignment(Imagick::ALIGN_CENTER);
$text->setFont(__DIR__ . '/../../../public/fonts/Inter-Regular.ttf');
$text->setFillColor(new \ImagickPixel('#FFFFFF'));
$text->setFontSize($scalingDown ? 28 : 32);
$text->setFontWeight(400);
$metrics = $baseImage->queryFontMetrics($text, $githubName);
$baseImage->annotateImage($text, 512 + 20 + 4, 373 + ($scalingDown ? 2 : 0), 0, $githubName);
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/github.png');
$image->setGravity(Imagick::GRAVITY_CENTER);
$precisionFix = 5;
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 512 - ($metrics['textWidth'] / 2) - 20 - 4, 373 - ($metrics['textHeight'] - $precisionFix));
}
if (!empty($width) || !empty($height)) {
$baseImage->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1);
}
$response
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->setContentType('image/png')
->file($baseImage->getImageBlob());
});
App::get('/v1/cards/cloud-back')
->desc('Get Back Of Cloud Card')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
->label('cache', true)
->label('cache.resourceType', 'cards/cloud-back')
->label('cache.resource', 'card-back/{request.userId}')
->label('docs', false)
->label('origin', '*')
->param('userId', '', new UID(), 'User ID.', true)
->param('mock', '', new WhiteList(['golden', 'normal', 'platinum']), 'Mocking behaviour.', true)
->param('width', 0, new Range(0, 512), 'Resize image width, Pass an integer between 0 to 512.', true)
->param('height', 0, new Range(0, 320), 'Resize image height, Pass an integer between 0 to 320.', true)
->inject('user')
->inject('project')
->inject('dbForProject')
->inject('dbForConsole')
->inject('response')
->inject('heroes')
->inject('contributors')
->inject('employees')
->inject('logger')
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
if ($user->isEmpty() && empty($mock)) {
throw new Exception(Exception::USER_NOT_FOUND);
}
if (!$mock) {
$userId = $user->getId();
$email = $user->getAttribute('email', '');
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
$githubId = $gitHub['id'] ?? '';
$isHero = \array_key_exists($email, $heroes);
$isContributor = \in_array($githubId, $contributors);
$isEmployee = \array_key_exists($email, $employees);
$isGolden = $isEmployee || $isHero || $isContributor;
$isPlatinum = $user->getInternalId() % 100 === 0;
} else {
$userId = '63e0bcf3c3eb803ba530';
$isGolden = $mock === 'golden';
$isPlatinum = $mock === 'platinum';
}
$userId = 'UID ' . $userId;
$isPlatinum = $isGolden ? false : $isPlatinum;
$imagePath = $isGolden ? 'back-golden.png' : ($isPlatinum ? 'back-platinum.png' : 'back.png');
$baseImage = new \Imagick(__DIR__ . '/../../../public/images/cards/cloud/' . $imagePath);
setlocale(LC_ALL, "en_US.utf8");
// $userId = \iconv("utf-8", "ascii//TRANSLIT", $userId);
$text = new \ImagickDraw();
$text->setTextAlignment(Imagick::ALIGN_CENTER);
$text->setFont(__DIR__ . '/../../../public/fonts/SourceCodePro-Regular.ttf');
$text->setFillColor(new \ImagickPixel($isGolden ? '#664A1E' : ($isPlatinum ? '#555555' : '#E8E9F0')));
$text->setFontSize(28);
$text->setFontWeight(400);
$baseImage->annotateImage($text, 512, 596, 0, $userId);
if (!empty($width) || !empty($height)) {
$baseImage->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1);
}
$response
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->setContentType('image/png')
->file($baseImage->getImageBlob());
});
App::get('/v1/cards/cloud-og')
->desc('Get OG Image From Cloud Card')
->groups(['api', 'avatars'])
->label('scope', 'avatars.read')
->label('cache', true)
->label('cache.resourceType', 'cards/cloud-og')
->label('cache.resource', 'card-og/{request.userId}')
->label('docs', false)
->label('origin', '*')
->param('userId', '', new UID(), 'User ID.', true)
->param('mock', '', new WhiteList(['employee', 'employee-2digit', 'hero', 'contributor', 'normal', 'platinum', 'normal-no-github', 'normal-long', 'normal-long-right', 'normal-long-middle', 'normal-bg2', 'normal-bg3', 'normal-right', 'normal-middle', 'platinum-right', 'platinum-middle', 'hero-middle', 'hero-right', 'contributor-right', 'employee-right', 'contributor-middle', 'employee-middle', 'employee-2digit-middle', 'employee-2digit-right']), 'Mocking behaviour.', true)
->param('width', 0, new Range(0, 1024), 'Resize image card width, Pass an integer between 0 to 1024.', true)
->param('height', 0, new Range(0, 1024), 'Resize image card height, Pass an integer between 0 to 1024.', true)
->inject('user')
->inject('project')
->inject('dbForProject')
->inject('dbForConsole')
->inject('response')
->inject('heroes')
->inject('contributors')
->inject('employees')
->inject('logger')
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
$user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId));
if ($user->isEmpty() && empty($mock)) {
throw new Exception(Exception::USER_NOT_FOUND);
}
if (!$mock) {
$internalId = $user->getInternalId();
$bgVariation = $internalId % 3 === 0 ? '1' : ($internalId % 3 === 1 ? '2' : '3');
$cardVariation = $internalId % 3 === 0 ? '1' : ($internalId % 3 === 1 ? '2' : '3');
$name = $user->getAttribute('name', 'Anonymous');
$email = $user->getAttribute('email', '');
$createdAt = new \DateTime($user->getCreatedAt());
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger);
$githubName = $gitHub['name'] ?? '';
$githubId = $gitHub['id'] ?? '';
$isHero = \array_key_exists($email, $heroes);
$isContributor = \in_array($githubId, $contributors);
$isEmployee = \array_key_exists($email, $employees);
$employeeNumber = $isEmployee ? $employees[$email]['spot'] : '';
if ($isHero) {
$createdAt = new \DateTime($heroes[$email]['memberSince'] ?? '');
} elseif ($isEmployee) {
$createdAt = new \DateTime($employees[$email]['memberSince'] ?? '');
}
if (!$isEmployee && !empty($githubName)) {
$employeeGitHub = \array_search(\strtolower($githubName), \array_map(fn ($employee) => \strtolower($employee['gitHub']) ?? '', $employees));
if (!empty($employeeGitHub)) {
$isEmployee = true;
$employeeNumber = $isEmployee ? $employees[$employeeGitHub]['spot'] : '';
$createdAt = new \DateTime($employees[$employeeGitHub]['memberSince'] ?? '');
}
}
$isPlatinum = $user->getInternalId() % 100 === 0;
} else {
$bgVariation = \str_ends_with($mock, '-bg2') ? '2' : (\str_ends_with($mock, '-bg3') ? '3' : '1');
$cardVariation = \str_ends_with($mock, '-right') ? '2' : (\str_ends_with($mock, '-middle') ? '3' : '1');
$name = \str_starts_with($mock, 'normal-long') ? 'Sir First Walter O\'Brian Junior' : 'Walter O\'Brian';
$createdAt = new \DateTime('now');
$githubName = $mock === 'normal-no-github' ? '' : (\str_starts_with($mock, 'normal-long') ? 'sir-first-walterobrian-junior' : 'walterobrian');
$isHero = \str_starts_with($mock, 'hero');
$isContributor = \str_starts_with($mock, 'contributor');
$isEmployee = \str_starts_with($mock, 'employee');
$employeeNumber = match ($mock) {
'employee' => '1',
'employee-right' => '1',
'employee-middle' => '1',
'employee-2digit' => '18',
'employee-2digit-right' => '18',
'employee-2digit-middle' => '18',
default => ''
};
$isPlatinum = \str_starts_with($mock, 'platinum');
}
if ($isEmployee) {
$isContributor = false;
$isHero = false;
}
if ($isHero) {
$isContributor = false;
$isEmployee = false;
}
if ($isContributor) {
$isHero = false;
$isEmployee = false;
}
$isGolden = $isEmployee || $isHero || $isContributor;
$isPlatinum = $isGolden ? false : $isPlatinum;
$memberSince = \strtoupper('Member since ' . $createdAt->format('M') . ' ' . $createdAt->format('d') . ', ' . $createdAt->format('o'));
$baseImage = new \Imagick(__DIR__ . "/../../../public/images/cards/cloud/og-background{$bgVariation}.png");
$cardType = $isGolden ? '-golden' : ($isPlatinum ? '-platinum' : '');
$image = new Imagick(__DIR__ . "/../../../public/images/cards/cloud/og-card{$cardType}{$cardVariation}.png");
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 1008 / 2 - $image->getImageWidth() / 2, 1008 / 2 - $image->getImageHeight() / 2);
$imageLogo = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/og-background-logo.png');
$imageShadow = new Imagick(__DIR__ . "/../../../public/images/cards/cloud/og-shadow{$cardType}.png");
if ($cardVariation === '1') {
$baseImage->compositeImage($imageLogo, Imagick::COMPOSITE_OVER, 32, 1008 - $imageLogo->getImageHeight() - 32);
$baseImage->compositeImage($imageShadow, Imagick::COMPOSITE_OVER, -450, 700);
} elseif ($cardVariation === '2') {
$baseImage->compositeImage($imageLogo, Imagick::COMPOSITE_OVER, 1008 - $imageLogo->getImageWidth() - 32, 1008 - $imageLogo->getImageHeight() - 32);
$baseImage->compositeImage($imageShadow, Imagick::COMPOSITE_OVER, -20, 710);
} else {
$baseImage->compositeImage($imageLogo, Imagick::COMPOSITE_OVER, 1008 - $imageLogo->getImageWidth() - 32, 1008 - $imageLogo->getImageHeight() - 32);
$baseImage->compositeImage($imageShadow, Imagick::COMPOSITE_OVER, -135, 710);
}
if ($isEmployee) {
$file = $cardVariation === '3' ? 'employee-skew.png' : 'employee.png';
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/' . $file);
$image->setGravity(Imagick::GRAVITY_CENTER);
$hashtag = new \ImagickDraw();
$hashtag->setTextAlignment(Imagick::ALIGN_LEFT);
$hashtag->setFont(__DIR__ . '/../../../public/fonts/Inter-Bold.ttf');
$hashtag->setFillColor(new \ImagickPixel('#FFFADF'));
$hashtag->setFontSize(20);
$hashtag->setFontWeight(700);
$text = new \ImagickDraw();
$text->setTextAlignment(Imagick::ALIGN_LEFT);
$text->setFont(__DIR__ . '/../../../public/fonts/Inter-Bold.ttf');
$text->setFillColor(new \ImagickPixel('#FFFADF'));
$text->setFontSize(\strlen($employeeNumber) <= 1 ? 36 : 28);
$text->setFontWeight(700);
if ($cardVariation === '3') {
$hashtag->setFontSize(16);
$text->setFontSize(\strlen($employeeNumber) <= 1 ? 30 : 26);
$hashtag->skewY(20);
$hashtag->skewX(20);
$text->skewY(20);
$text->skewX(20);
}
$metricsHashtag = $baseImage->queryFontMetrics($hashtag, '#');
$metricsText = $baseImage->queryFontMetrics($text, $employeeNumber);
$group = new Imagick();
$groupWidth = $metricsHashtag['textWidth'] + 6 + $metricsText['textWidth'];
if ($cardVariation === '1') {
$group->newImage($groupWidth, $metricsText['textHeight'], '#00000000');
$group->annotateImage($hashtag, 0, $metricsText['textHeight'], 0, '#');
$group->annotateImage($text, $metricsHashtag['textWidth'] + 6, $metricsText['textHeight'], 0, $employeeNumber);
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
$image->rotateImage(new ImagickPixel('#00000000'), -20);
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 612, 203);
$group->rotateImage(new ImagickPixel('#00000000'), -22);
if (\strlen($employeeNumber) <= 1) {
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 660, 245);
} else {
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 655, 247);
}
} elseif ($cardVariation === '2') {
$group->newImage($groupWidth, $metricsText['textHeight'], '#00000000');
$group->annotateImage($hashtag, 0, $metricsText['textHeight'], 0, '#');
$group->annotateImage($text, $metricsHashtag['textWidth'] + 6, $metricsText['textHeight'], 0, $employeeNumber);
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
$image->rotateImage(new ImagickPixel('#00000000'), 30);
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 715, 425);
$group->rotateImage(new ImagickPixel('#00000000'), 32);
if (\strlen($employeeNumber) <= 1) {
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 775, 465);
} else {
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 767, 470);
}
} else {
$group->newImage(300, 300, '#00000000');
$hashtag->annotation(0, $metricsText['textHeight'], '#');
$text->annotation($metricsHashtag['textWidth'] + 2, $metricsText['textHeight'], $employeeNumber);
$group->drawImage($hashtag);
$group->drawImage($text);
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 640, 293);
if (\strlen($employeeNumber) <= 1) {
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 670, 317);
} else {
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 663, 322);
}
}
}
if ($isContributor) {
$file = $cardVariation === '3' ? 'contributor-skew.png' : 'contributor.png';
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/' . $file);
$image->setGravity(Imagick::GRAVITY_CENTER);
if ($cardVariation === '1') {
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
$image->rotateImage(new ImagickPixel('#00000000'), -20);
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 612, 203);
} elseif ($cardVariation === '2') {
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
$image->rotateImage(new ImagickPixel('#00000000'), 30);
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 715, 425);
} else {
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 640, 293);
}
}
if ($isHero) {
$file = $cardVariation === '3' ? 'hero-skew.png' : 'hero.png';
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/' . $file);
$image->setGravity(Imagick::GRAVITY_CENTER);
if ($cardVariation === '1') {
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
$image->rotateImage(new ImagickPixel('#00000000'), -20);
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 612, 203);
} elseif ($cardVariation === '2') {
$image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1);
$image->rotateImage(new ImagickPixel('#00000000'), 30);
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 715, 425);
} else {
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 640, 293);
}
}
setlocale(LC_ALL, "en_US.utf8");
// $name = \iconv("utf-8", "ascii//TRANSLIT", $name);
// $memberSince = \iconv("utf-8", "ascii//TRANSLIT", $memberSince);
// $githubName = \iconv("utf-8", "ascii//TRANSLIT", $githubName);
$textName = new \ImagickDraw();
$textName->setTextAlignment(Imagick::ALIGN_CENTER);
$textName->setFont(__DIR__ . '/../../../public/fonts/Poppins-Bold.ttf');
$textName->setFillColor(new \ImagickPixel('#FFFFFF'));
if (\strlen($name) > 32) {
$name = \substr($name, 0, 32);
}
if ($cardVariation === '1') {
if (\strlen($name) <= 23) {
$scalingDown = false;
$textName->setFontSize(54);
} else {
$scalingDown = true;
$textName->setFontSize(36);
}
} elseif ($cardVariation === '2') {
if (\strlen($name) <= 23) {
$scalingDown = false;
$textName->setFontSize(50);
} else {
$scalingDown = true;
$textName->setFontSize(34);
}
} else {
if (\strlen($name) <= 23) {
$scalingDown = false;
$textName->setFontSize(44);
} else {
$scalingDown = true;
$textName->setFontSize(32);
}
}
$textName->setFontWeight(700);
$textMember = new \ImagickDraw();
$textMember->setTextAlignment(Imagick::ALIGN_CENTER);
$textMember->setFont(__DIR__ . '/../../../public/fonts/Inter-Medium.ttf');
$textMember->setFillColor(new \ImagickPixel($isGolden || $isPlatinum ? '#FFFFFF' : '#FFB9CC'));
$textMember->setFontWeight(500);
$textMember->setTextKerning(1.12);
if ($cardVariation === '1') {
$textMember->setFontSize(21);
$baseImage->annotateImage($textName, 550, 600, -22, $name);
$baseImage->annotateImage($textMember, 585, 635, -22, $memberSince);
} elseif ($cardVariation === '2') {
$textMember->setFontSize(20);
$baseImage->annotateImage($textName, 435, 590, 31.37, $name);
$baseImage->annotateImage($textMember, 412, 628, 31.37, $memberSince);
} else {
$textMember->setFontSize(16);
$textName->skewY(20);
$textName->skewX(20);
$textName->annotation(320, 700, $name);
$textMember->skewY(20);
$textMember->skewX(20);
$textMember->annotation(330, 735, $memberSince);
$baseImage->drawImage($textName);
$baseImage->drawImage($textMember);
}
if (!empty($githubName)) {
$text = new \ImagickDraw();
$text->setTextAlignment(Imagick::ALIGN_LEFT);
$text->setFont(__DIR__ . '/../../../public/fonts/Inter-Regular.ttf');
$text->setFillColor(new \ImagickPixel('#FFFFFF'));
$text->setFontSize($scalingDown ? 16 : 20);
$text->setFontWeight(400);
if ($cardVariation === '1') {
$metrics = $baseImage->queryFontMetrics($text, $githubName);
$group = new Imagick();
$groupWidth = $metrics['textWidth'] + 32 + 4;
$group->newImage($groupWidth, $metrics['textHeight'] + 10, '#00000000');
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/github.png');
$image->setGravity(Imagick::GRAVITY_CENTER);
$image->resizeImage(32, 32, Imagick::FILTER_LANCZOS, 1);
$precisionFix = -1;
$group->compositeImage($image, Imagick::COMPOSITE_OVER, 0, 0);
$group->annotateImage($text, 32 + 4, $metrics['textHeight'] - $precisionFix, 0, $githubName);
$group->rotateImage(new ImagickPixel('#00000000'), -22);
$x = 510 - $group->getImageWidth() / 2;
$y = 530 - $group->getImageHeight() / 2;
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, $x, $y);
} elseif ($cardVariation === '2') {
$metrics = $baseImage->queryFontMetrics($text, $githubName);
$group = new Imagick();
$groupWidth = $metrics['textWidth'] + 32 + 4;
$group->newImage($groupWidth, $metrics['textHeight'] + 10, '#00000000');
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/github.png');
$image->setGravity(Imagick::GRAVITY_CENTER);
$image->resizeImage(32, 32, Imagick::FILTER_LANCZOS, 1);
$precisionFix = -1;
$group->compositeImage($image, Imagick::COMPOSITE_OVER, 0, 0);
$group->annotateImage($text, 32 + 4, $metrics['textHeight'] - $precisionFix, 0, $githubName);
$group->rotateImage(new ImagickPixel('#00000000'), 31.11);
$x = 485 - $group->getImageWidth() / 2;
$y = 530 - $group->getImageHeight() / 2;
$baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, $x, $y);
} else {
$text->skewY(20);
$text->skewX(20);
$text->setTextAlignment(\Imagick::ALIGN_CENTER);
$text->annotation(320 + 15 + 2, 640, $githubName);
$metrics = $baseImage->queryFontMetrics($text, $githubName);
$image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/github-skew.png');
$image->setGravity(Imagick::GRAVITY_CENTER);
$baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 512 - ($metrics['textWidth'] / 2), 518 + \strlen($githubName) * 1.3);
$baseImage->drawImage($text);
}
}
if (!empty($width) || !empty($height)) {
$baseImage->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1);
}
$response
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
->setContentType('image/png')
->file($baseImage->getImageBlob());
});

View file

@ -176,7 +176,7 @@ App::post('/v1/databases')
]));
$database = $dbForProject->getDocument('databases', $databaseId);
$collections = Config::getParam('collections', [])['collections'] ?? [];
$collections = (Config::getParam('collections', [])['databases'] ?? [])['collections'] ?? [];
if (empty($collections)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'The "collections" collection is not configured.');
}

View file

@ -18,17 +18,18 @@ use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\DateTime;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Query;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\DatetimeValidator;
use Utopia\Database\DateTime;
use Utopia\Database\Validator\UID;
use Utopia\Domains\Domain;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Database\Validator\Queries\Projects;
use Utopia\Cache\Cache;
use Utopia\Pools\Group;
use Utopia\Database\Exception\Duplicate;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
use Utopia\Validator\Hostname;
@ -88,46 +89,81 @@ App::post('/v1/projects')
}
$projectId = ($projectId == 'unique()') ? ID::unique() : $projectId;
$backups['database_db_fra1_02'] = ['from' => '7:30', 'to' => '8:15'];
$backups['database_db_fra1_03'] = ['from' => '10:30', 'to' => '11:15'];
$backups['database_db_fra1_04'] = ['from' => '13:30', 'to' => '14:15'];
$backups['database_db_fra1_05'] = ['from' => '4:30', 'to' => '5:15'];
$databases = Config::getParam('pools-database', []);
$database = $databases[array_rand($databases)];
/**
* Extract db from list while backing
*/
if (count($databases) > 1) {
$now = new \DateTime();
foreach ($databases as $index => $database) {
if (empty($backups[$database])) {
continue;
}
$backup = $backups[$database];
$from = \DateTime::createFromFormat('H:i', $backup['from']);
$to = \DateTime::createFromFormat('H:i', $backup['to']);
if ($now >= $from && $now <= $to) {
unset($databases[$index]);
break;
}
}
}
if ($index = array_search('database_db_fra1_05', $databases)) {
$database = $databases[$index];
} else {
$database = $databases[array_rand($databases)];
}
if ($projectId === 'console') {
throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project.");
}
$project = $dbForConsole->createDocument('projects', new Document([
'$id' => $projectId,
'$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')),
],
'name' => $name,
'teamInternalId' => $team->getInternalId(),
'teamId' => $team->getId(),
'region' => $region,
'description' => $description,
'logo' => $logo,
'url' => $url,
'version' => APP_VERSION_STABLE,
'legalName' => $legalName,
'legalCountry' => $legalCountry,
'legalState' => $legalState,
'legalCity' => $legalCity,
'legalAddress' => $legalAddress,
'legalTaxId' => ID::custom($legalTaxId),
'services' => new stdClass(),
'platforms' => null,
'authProviders' => [],
'webhooks' => null,
'keys' => null,
'domains' => null,
'auths' => $auths,
'search' => implode(' ', [$projectId, $name]),
'database' => $database,
]));
try {
$project = $dbForConsole->createDocument('projects', new Document([
'$id' => $projectId,
'$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')),
],
'name' => $name,
'teamInternalId' => $team->getInternalId(),
'teamId' => $team->getId(),
'region' => $region,
'description' => $description,
'logo' => $logo,
'url' => $url,
'version' => APP_VERSION_STABLE,
'legalName' => $legalName,
'legalCountry' => $legalCountry,
'legalState' => $legalState,
'legalCity' => $legalCity,
'legalAddress' => $legalAddress,
'legalTaxId' => ID::custom($legalTaxId),
'services' => new stdClass(),
'platforms' => null,
'authProviders' => [],
'webhooks' => null,
'keys' => null,
'domains' => null,
'auths' => $auths,
'search' => implode(' ', [$projectId, $name]),
'database' => $database
]));
} catch (Duplicate $th) {
throw new Exception(Exception::PROJECT_ALREADY_EXISTS);
}
$dbForProject = new Database($pools->get($database)->pop()->getResource(), $cache);
$dbForProject->setNamespace("_{$project->getInternalId()}");
@ -140,7 +176,7 @@ App::post('/v1/projects')
$adapter->setup();
/** @var array $collections */
$collections = Config::getParam('collections', []);
$collections = Config::getParam('collections', [])['projects'] ?? [];
foreach ($collections as $key => $collection) {
if (($collection['$collection'] ?? '') !== Database::METADATA) {
@ -1221,9 +1257,12 @@ App::post('/v1/projects/:projectId/domains')
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
if ($domain === App::getEnv('_APP_DOMAIN', '') || $domain === App::getEnv('_APP_DOMAIN_TARGET', '')) {
throw new Exception(Exception::DOMAIN_FORBIDDEN);
}
$document = $dbForConsole->findOne('domains', [
Query::equal('domain', [$domain]),
Query::equal('projectInternalId', [$project->getInternalId()]),
Query::equal('domain', [$domain])
]);
if ($document && !$document->isEmpty()) {
@ -1421,6 +1460,10 @@ App::delete('/v1/projects/:projectId/domains/:domainId')
throw new Exception(Exception::DOMAIN_NOT_FOUND);
}
if ($domain->getAttribute('domain') === App::getEnv('_APP_DOMAIN', '') || $domain->getAttribute('domain') === App::getEnv('_APP_DOMAIN_TARGET', '')) {
throw new Exception(Exception::DOMAIN_FORBIDDEN);
}
$dbForConsole->deleteDocument('domains', $domain->getId());
$dbForConsole->deleteCachedDocument('projects', $project->getId());

View file

@ -79,7 +79,7 @@ App::post('/v1/storage/buckets')
$permissions = Permission::aggregate($permissions);
try {
$files = Config::getParam('collections', [])['files'] ?? [];
$files = (Config::getParam('collections', [])['buckets'] ?? [])['files'] ?? [];
if (empty($files)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Files collection is not configured.');
}
@ -352,8 +352,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
->inject('mode')
->inject('deviceFiles')
->inject('deviceLocal')
->inject('deletes')
->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal, Delete $deletes) {
->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) {
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
@ -649,11 +648,6 @@ App::post('/v1/storage/buckets/:bucketId/files')
->setContext('bucket', $bucket)
;
$deletes
->setType(DELETE_TYPE_CACHE_BY_RESOURCE)
->setResource('file/' . $file->getId())
;
$metadata = null; // was causing leaks as it was passed by reference
$response

View file

@ -425,6 +425,7 @@ App::error()
$log->setType(Log::TYPE_ERROR);
$log->setMessage($error->getMessage());
$log->addTag('database', $project->getAttribute('database', 'console'));
$log->addTag('method', $route->getMethod());
$log->addTag('url', $route->getPath());
$log->addTag('verboseType', get_class($error));
@ -437,7 +438,7 @@ App::error()
$log->addExtra('line', $error->getLine());
$log->addExtra('trace', $error->getTraceAsString());
$log->addExtra('detailedTrace', $error->getTrace());
$log->addExtra('roles', Authorization::$roles);
$log->addExtra('roles', Authorization::getRoles());
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
$log->setAction($action);
@ -580,7 +581,7 @@ App::get('/humans.txt')
$response->text($template->render(false));
});
App::get('/.well-known/acme-challenge')
App::get('/.well-known/acme-challenge/*')
->desc('SSL Verification')
->label('scope', 'public')
->label('docs', false)

View file

@ -549,7 +549,7 @@ App::shutdown()
]) ;
$signature = md5($data);
$cacheLog = $dbForProject->getDocument('cache', $key);
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
$accessedAt = $cacheLog->getAttribute('accessedAt', '');
$now = DateTime::now();
if ($cacheLog->isEmpty()) {

View file

@ -1,19 +1,61 @@
<?php
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\App;
App::get('/console')
App::get('/console/*')
->alias('/')
->alias('auth/*')
->alias('/invite')
->alias('/login')
->alias('/recover')
->alias('/register')
->alias('/register/*')
->groups(['web'])
->label('permission', 'public')
->label('scope', 'home')
->inject('request')
->inject('response')
->action(function (Response $response) {
->action(function (Request $request, Response $response) {
$fallback = file_get_contents(__DIR__ . '/../../../console/index.html');
// Card SSR
if (\str_starts_with($request->getURI(), '/card')) {
$urlCunks = \explode('/', $request->getURI());
$userId = $urlCunks[\count($urlCunks) - 1] ?? '';
$domain = $request->getProtocol() . '://' . $request->getHostname();
if (!empty($userId)) {
$ogImageUrl = $domain . '/v1/cards/cloud-og?userId=' . $userId;
} else {
$ogImageUrl = $domain . '/v1/cards/cloud-og?mock=normal';
}
$ogTags = [
'<title>Appwrite Cloud Card</title>',
'<meta name="description" content="Appwrite Cloud is now LIVE! Share your Cloud card for a chance to win an exclusive Cloud hoodie!">',
'<meta property="og:url" content="' . $domain . $request->getURI() . '">',
'<meta name="og:image:type" content="image/png">',
'<meta name="og:image:width" content="1008">',
'<meta name="og:image:height" content="1008">',
'<meta property="og:type" content="website">',
'<meta property="og:title" content="Appwrite Cloud Card">',
'<meta property="og:description" content="Appwrite Cloud is now LIVE! Share your Cloud card for a chance to win an exclusive Cloud hoodie!">',
'<meta property="og:image" content="' . $ogImageUrl . '">',
'<meta name="twitter:card" content="summary_large_image">',
'<meta property="twitter:domain" content="' . $request->getHostname() . '">',
'<meta property="twitter:url" content="' . $domain . $request->getURI() . '">',
'<meta name="twitter:title" content="Appwrite Cloud Card">',
'<meta name="twitter:image:type" content="image/png">',
'<meta name="twitter:image:width" content="1008">',
'<meta name="twitter:image:height" content="1008">',
'<meta name="twitter:description" content="Appwrite Cloud is now LIVE! Share your Cloud card for a chance to win an exclusive Cloud hoodie!">',
'<meta name="twitter:image" content="' . $ogImageUrl . '">',
];
$fallback = \str_replace('<!-- {{CLOUD_OG}} -->', \implode('', $ogTags), $fallback);
}
$response->html($fallback);
});

View file

@ -85,10 +85,8 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
Console::success('[Setup] - Server database init started...');
/** @var array $collections */
$collections = Config::getParam('collections', []);
try {
$cache = $app->getResource('cache'); /** @var Utopia\Cache\Cache $cache */
Console::success('[Setup] - Creating database: appwrite...');
$dbForConsole->create();
} catch (\Exception $e) {
@ -105,7 +103,10 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
$adapter->setup();
}
foreach ($collections as $key => $collection) {
/** @var array $collections */
$collections = Config::getParam('collections', []);
$consoleCollections = $collections['console'];
foreach ($consoleCollections as $key => $collection) {
if (($collection['$collection'] ?? '') !== Database::METADATA) {
continue;
}
@ -177,7 +178,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
$bucket = $dbForConsole->getDocument('buckets', 'default');
Console::success('[Setup] - Creating files collection for default bucket...');
$files = $collections['files'] ?? [];
$files = $collections['buckets']['files'] ?? [];
if (empty($files)) {
throw new Exception('Files collection is not configured.');
}

View file

@ -1053,6 +1053,11 @@ App::setResource('console', function () {
],
'authWhitelistEmails' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [],
'authWhitelistIPs' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [],
'authProviders' => [
'githubEnabled' => true,
'githubSecret' => App::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''),
'githubAppid' => App::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '')
],
]);
}, []);
@ -1289,3 +1294,21 @@ App::setResource('schema', function ($utopia, $dbForProject) {
$params,
);
}, ['utopia', 'dbForProject']);
App::setResource('contributors', function () {
$path = 'app/config/cloud/contributors.json';
$list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
return $list;
}, []);
App::setResource('employees', function () {
$path = 'app/config/cloud/employees.json';
$list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
return $list;
}, []);
App::setResource('heroes', function () {
$path = 'app/config/cloud/heroes.json';
$list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
return $list;
}, []);

File diff suppressed because one or more lines are too long

View file

@ -126,16 +126,15 @@ $server
->error()
->inject('error')
->inject('logger')
->action(function (Throwable $error, Logger $logger) {
->inject('log')
->action(function (Throwable $error, Logger $logger, Log $log) {
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
if ($error instanceof PDOException) {
throw $error;
}
if ($error->getCode() >= 500 || $error->getCode() === 0) {
$log = new Log();
if ($logger && ($error->getCode() >= 500 || $error->getCode() === 0)) {
$log->setNamespace("appwrite-worker");
$log->setServer(\gethostname());
$log->setVersion($version);
@ -153,7 +152,8 @@ $server
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
$logger->addLog($log);
$responseCode = $logger->addLog($log);
Console::info('Usage stats log pushed with status code: ' . $responseCode);
}
Console::error('[Error] Type: ' . get_class($error));

View file

@ -113,10 +113,10 @@ class DeletesV1 extends Worker
break;
case DELETE_TYPE_CACHE_BY_RESOURCE:
$this->deleteCacheByResource($this->args['resource']);
$this->deleteCacheByResource($project, $this->args['resource']);
break;
case DELETE_TYPE_CACHE_BY_TIMESTAMP:
$this->deleteCacheByDate();
$this->deleteCacheByDate($this->args['datetime']);
break;
case DELETE_TYPE_SCHEDULES:
$this->deleteSchedules($this->args['datetime']);
@ -164,32 +164,54 @@ class DeletesV1 extends Worker
}
/**
* @param Document $project
* @param string $resource
* @throws Exception
*/
protected function deleteCacheByResource(string $resource): void
protected function deleteCacheByResource(Document $project, string $resource): void
{
$this->deleteCacheFiles([
Query::equal('resource', [$resource]),
]);
$projectId = $project->getId();
$dbForProject = $this->getProjectDB($project);
$document = $dbForProject->findOne('cache', [Query::equal('resource', [$resource])]);
if ($document) {
$cache = new Cache(
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId)
);
$this->deleteById(
$document,
$dbForProject,
function ($document) use ($cache, $projectId) {
$path = APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId . DIRECTORY_SEPARATOR . $document->getId();
if ($cache->purge($document->getId())) {
Console::success('Deleting cache file: ' . $path);
} else {
Console::error('Failed to delete cache file: ' . $path);
}
}
);
}
}
protected function deleteCacheByDate(): void
/**
* @param string $datetime
* @throws Exception
*/
protected function deleteCacheByDate(string $datetime): void
{
$this->deleteCacheFiles([
Query::lessThan('accessedAt', $this->args['datetime']),
]);
}
protected function deleteCacheFiles($query): void
{
$this->deleteForProjectIds(function (Document $project) use ($query) {
$this->deleteForProjectIds(function (Document $project) use ($datetime) {
$projectId = $project->getId();
$dbForProject = $this->getProjectDB($project);
$cache = new Cache(
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId)
);
$query = [
Query::lessThan('accessedAt', $datetime),
];
$this->deleteByGroup(
'cache',
$query,
@ -207,9 +229,10 @@ class DeletesV1 extends Worker
});
}
/**
* @param Document $document database document
* @param Document $projectId
* @param Document $project
*/
protected function deleteDatabase(Document $document, Document $project): void
{
@ -662,19 +685,23 @@ class DeletesV1 extends Worker
$executionStart = \microtime(true);
while ($sum === $limit) {
$chunk++;
try {
while ($sum === $limit) {
$chunk++;
$results = $database->find($collection, \array_merge([Query::limit($limit)], $queries));
$results = $database->find($collection, \array_merge([Query::limit($limit)], $queries));
$sum = count($results);
$sum = count($results);
Console::info('Deleting chunk #' . $chunk . '. Found ' . $sum . ' documents');
Console::info('Deleting chunk #' . $chunk . '. Found ' . $sum . ' documents in collection ' . $database->getNamespace() . '_' . $collection);
foreach ($results as $document) {
$this->deleteById($document, $database, $callback);
$count++;
foreach ($results as $document) {
$this->deleteById($document, $database, $callback);
$count++;
}
}
} catch (\Exception $e) {
Console::error($e->getMessage());
}
$executionEnd = \microtime(true);

View file

@ -18,6 +18,7 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Logger\Log;
use Utopia\Queue\Server;
use Utopia\Database\Helpers\Role;
@ -26,6 +27,7 @@ Authorization::setDefaultStatus(false);
Server::setResource('execute', function () {
return function (
Log $log,
Func $queueForFunctions,
Database $dbForProject,
Usage $queueForUsage,
@ -39,12 +41,14 @@ Server::setResource('execute', function () {
string $eventData = null,
string $executionId = null,
) {
$user ??= new Document();
$functionId = $function->getId();
$functionInternalId = $function->getInternalId();
$deploymentId = $function->getAttribute('deployment', '');
$log->addTag('functionId', $functionId);
$log->addTag('projectId', $project->getId());
/** Check if deployment exists */
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
$deploymentInternalId = $deployment->getInternalId();
@ -165,10 +169,8 @@ Server::setResource('execute', function () {
->setAttribute('statusCode', $th->getCode())
->setAttribute('stderr', $th->getMessage());
Console::error($th->getTraceAsString());
Console::error($th->getFile());
Console::error($th->getLine());
Console::error($th->getMessage());
$error = $th->getMessage();
$errorCode = $th->getCode();
}
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
@ -259,8 +261,7 @@ $server->job()
while ($sum >= $limit) {
$functions = $dbForProject->find('functions', [
Query::limit($limit),
Query::offset($offset),
Query::orderAsc('name'),
Query::offset($offset)
]);
$sum = \count($functions);
@ -303,6 +304,7 @@ $server->job()
$execution = new Document($payload['execution'] ?? []);
$user = new Document($payload['user'] ?? []);
$execute(
log: $log,
project: $project,
function: $function,
dbForProject: $dbForProject,
@ -319,6 +321,7 @@ $server->job()
break;
case 'schedule':
$execute(
log: $log,
project: $project,
function: $function,
dbForProject: $dbForProject,

3
bin/calc-tier-stats Normal file
View file

@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php calc-tier-stats $@

3
bin/calc-users-stats Normal file
View file

@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php calc-users-stats $@

3
bin/clear-card-cache Normal file
View file

@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php clear-card-cache $@

View file

@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php patch-delete-project-collections $@

View file

@ -50,18 +50,18 @@
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "0.30.*",
"utopia-php/queue": "0.5.*",
"utopia-php/domains": "1.1.*",
"utopia-php/dsn": "0.1.*",
"utopia-php/framework": "0.26.*",
"utopia-php/image": "0.5.*",
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.3.*",
"utopia-php/messaging": "0.1.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.3.*",
"utopia-php/pools": "0.4.*",
"utopia-php/preloader": "0.2.*",
"utopia-php/domains": "1.1.*",
"utopia-php/framework": "0.26.*",
"utopia-php/image": "0.5.*",
"utopia-php/dsn": "0.1.*",
"utopia-php/locale": "0.4.*",
"utopia-php/logger": "0.3.*",
"utopia-php/messaging": "0.1.*",
"utopia-php/queue": "0.5.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.14.*",
"utopia-php/swoole": "0.5.*",
@ -73,7 +73,8 @@
"chillerlan/php-qrcode": "4.3.3",
"adhocore/jwt": "1.1.2",
"webonyx/graphql-php": "14.11.*",
"slickdeals/statsd": "3.1.0"
"slickdeals/statsd": "3.1.0",
"league/csv": "9.7.1"
},
"repositories": [
{

211
composer.lock generated
View file

@ -300,16 +300,16 @@
},
{
"name": "colinmollenhour/credis",
"version": "v1.14.0",
"version": "v1.15.0",
"source": {
"type": "git",
"url": "https://github.com/colinmollenhour/credis.git",
"reference": "dccc8a46586475075fbb012d8bd523b8a938c2dc"
"reference": "28810439de1d9597b7ba11794ed9479fb6f3de7c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/colinmollenhour/credis/zipball/dccc8a46586475075fbb012d8bd523b8a938c2dc",
"reference": "dccc8a46586475075fbb012d8bd523b8a938c2dc",
"url": "https://api.github.com/repos/colinmollenhour/credis/zipball/28810439de1d9597b7ba11794ed9479fb6f3de7c",
"reference": "28810439de1d9597b7ba11794ed9479fb6f3de7c",
"shasum": ""
},
"require": {
@ -341,9 +341,9 @@
"homepage": "https://github.com/colinmollenhour/credis",
"support": {
"issues": "https://github.com/colinmollenhour/credis/issues",
"source": "https://github.com/colinmollenhour/credis/tree/v1.14.0"
"source": "https://github.com/colinmollenhour/credis/tree/v1.15.0"
},
"time": "2022-11-09T01:18:39+00:00"
"time": "2023-04-18T15:34:23+00:00"
},
{
"name": "composer/package-versions-deprecated",
@ -600,6 +600,90 @@
},
"time": "2022-11-29T16:25:20+00:00"
},
{
"name": "league/csv",
"version": "9.7.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/csv.git",
"reference": "0ec57e8264ec92565974ead0d1724cf1026e10c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/csv/zipball/0ec57e8264ec92565974ead0d1724cf1026e10c1",
"reference": "0ec57e8264ec92565974ead0d1724cf1026e10c1",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-mbstring": "*",
"php": "^7.3 || ^8.0"
},
"require-dev": {
"ext-curl": "*",
"ext-dom": "*",
"friendsofphp/php-cs-fixer": "^2.16",
"phpstan/phpstan": "^0.12.0",
"phpstan/phpstan-phpunit": "^0.12.0",
"phpstan/phpstan-strict-rules": "^0.12.0",
"phpunit/phpunit": "^9.5"
},
"suggest": {
"ext-dom": "Required to use the XMLConverter and or the HTMLConverter classes",
"ext-iconv": "Needed to ease transcoding CSV using iconv stream filters"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "9.x-dev"
}
},
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"League\\Csv\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ignace Nyamagana Butera",
"email": "nyamsprod@gmail.com",
"homepage": "https://github.com/nyamsprod/",
"role": "Developer"
}
],
"description": "CSV data manipulation made easy in PHP",
"homepage": "http://csv.thephpleague.com",
"keywords": [
"convert",
"csv",
"export",
"filter",
"import",
"read",
"transform",
"write"
],
"support": {
"docs": "https://csv.thephpleague.com",
"issues": "https://github.com/thephpleague/csv/issues",
"rss": "https://github.com/thephpleague/csv/releases.atom",
"source": "https://github.com/thephpleague/csv"
},
"funding": [
{
"url": "https://github.com/sponsors/nyamsprod",
"type": "github"
}
],
"time": "2021-04-17T16:32:08+00:00"
},
{
"name": "matomo/device-detector",
"version": "6.0.0",
@ -1588,16 +1672,16 @@
},
{
"name": "utopia-php/framework",
"version": "0.26.0",
"version": "0.26.3",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/framework.git",
"reference": "e8da5576370366d3bf9c574ec855f8c96fe4f34e"
"reference": "7a18a2b0d6c8dba878c926b73650fd2c4eeaa70d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/framework/zipball/e8da5576370366d3bf9c574ec855f8c96fe4f34e",
"reference": "e8da5576370366d3bf9c574ec855f8c96fe4f34e",
"url": "https://api.github.com/repos/utopia-php/framework/zipball/7a18a2b0d6c8dba878c926b73650fd2c4eeaa70d",
"reference": "7a18a2b0d6c8dba878c926b73650fd2c4eeaa70d",
"shasum": ""
},
"require": {
@ -1626,9 +1710,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/framework/issues",
"source": "https://github.com/utopia-php/framework/tree/0.26.0"
"source": "https://github.com/utopia-php/framework/tree/0.26.3"
},
"time": "2023-01-13T08:14:43+00:00"
"time": "2023-06-03T14:01:15+00:00"
},
{
"name": "utopia-php/image",
@ -2099,16 +2183,16 @@
},
{
"name": "utopia-php/queue",
"version": "0.5.2",
"version": "0.5.3",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/queue.git",
"reference": "310271c5cd477541208d7fa74a4dea64df8e04a0"
"reference": "8e8b6cb27172713fe5d8b7b092ce68516caf129a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/queue/zipball/310271c5cd477541208d7fa74a4dea64df8e04a0",
"reference": "310271c5cd477541208d7fa74a4dea64df8e04a0",
"url": "https://api.github.com/repos/utopia-php/queue/zipball/8e8b6cb27172713fe5d8b7b092ce68516caf129a",
"reference": "8e8b6cb27172713fe5d8b7b092ce68516caf129a",
"shasum": ""
},
"require": {
@ -2154,9 +2238,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/queue/issues",
"source": "https://github.com/utopia-php/queue/tree/0.5.2"
"source": "https://github.com/utopia-php/queue/tree/0.5.3"
},
"time": "2023-03-07T08:54:10+00:00"
"time": "2023-05-24T19:06:04+00:00"
},
{
"name": "utopia-php/registry",
@ -2608,25 +2692,29 @@
},
{
"name": "doctrine/deprecations",
"version": "v1.0.0",
"version": "v1.1.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/deprecations.git",
"reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de"
"reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
"reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
"url": "https://api.github.com/repos/doctrine/deprecations/zipball/612a3ee5ab0d5dd97b7cf3874a6efe24325efac3",
"reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3",
"shasum": ""
},
"require": {
"php": "^7.1|^8.0"
"php": "^7.1 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^9",
"phpunit/phpunit": "^7.5|^8.5|^9.5",
"psr/log": "^1|^2|^3"
"phpstan/phpstan": "1.4.10 || 1.10.15",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"psalm/plugin-phpunit": "0.18.4",
"psr/log": "^1 || ^2 || ^3",
"vimeo/psalm": "4.30.0 || 5.12.0"
},
"suggest": {
"psr/log": "Allows logging deprecations via PSR-3 logger implementation"
@ -2645,9 +2733,9 @@
"homepage": "https://www.doctrine-project.org/",
"support": {
"issues": "https://github.com/doctrine/deprecations/issues",
"source": "https://github.com/doctrine/deprecations/tree/v1.0.0"
"source": "https://github.com/doctrine/deprecations/tree/v1.1.1"
},
"time": "2022-05-02T15:47:09+00:00"
"time": "2023-06-03T09:27:29+00:00"
},
{
"name": "doctrine/instantiator",
@ -2721,16 +2809,16 @@
},
{
"name": "matthiasmullie/minify",
"version": "1.3.70",
"version": "1.3.71",
"source": {
"type": "git",
"url": "https://github.com/matthiasmullie/minify.git",
"reference": "2807d9f9bece6877577ad44acb5c801bb3ae536b"
"reference": "ae42a47d7fecc1fbb7277b2f2d84c37a33edc3b1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/matthiasmullie/minify/zipball/2807d9f9bece6877577ad44acb5c801bb3ae536b",
"reference": "2807d9f9bece6877577ad44acb5c801bb3ae536b",
"url": "https://api.github.com/repos/matthiasmullie/minify/zipball/ae42a47d7fecc1fbb7277b2f2d84c37a33edc3b1",
"reference": "ae42a47d7fecc1fbb7277b2f2d84c37a33edc3b1",
"shasum": ""
},
"require": {
@ -2780,7 +2868,7 @@
],
"support": {
"issues": "https://github.com/matthiasmullie/minify/issues",
"source": "https://github.com/matthiasmullie/minify/tree/1.3.70"
"source": "https://github.com/matthiasmullie/minify/tree/1.3.71"
},
"funding": [
{
@ -2788,7 +2876,7 @@
"type": "github"
}
],
"time": "2022-12-09T12:56:44+00:00"
"time": "2023-04-25T20:33:03+00:00"
},
{
"name": "matthiasmullie/path-converter",
@ -2904,16 +2992,16 @@
},
{
"name": "nikic/php-parser",
"version": "v4.15.4",
"version": "v4.15.5",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290"
"reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
"reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/11e2663a5bc9db5d714eedb4277ee300403b4a9e",
"reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e",
"shasum": ""
},
"require": {
@ -2954,9 +3042,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.5"
},
"time": "2023-03-05T19:49:14+00:00"
"time": "2023-05-19T20:20:00+00:00"
},
{
"name": "phar-io/manifest",
@ -3307,22 +3395,24 @@
},
{
"name": "phpstan/phpdoc-parser",
"version": "1.16.1",
"version": "1.22.0",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git",
"reference": "e27e92d939e2e3636f0a1f0afaba59692c0bf571"
"reference": "ec58baf7b3c7f1c81b3b00617c953249fb8cf30c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/e27e92d939e2e3636f0a1f0afaba59692c0bf571",
"reference": "e27e92d939e2e3636f0a1f0afaba59692c0bf571",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/ec58baf7b3c7f1c81b3b00617c953249fb8cf30c",
"reference": "ec58baf7b3c7f1c81b3b00617c953249fb8cf30c",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"doctrine/annotations": "^2.0",
"nikic/php-parser": "^4.15",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^1.5",
@ -3346,9 +3436,9 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.16.1"
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.22.0"
},
"time": "2023-02-07T18:11:17+00:00"
"time": "2023-06-01T12:35:21+00:00"
},
{
"name": "phpunit/php-code-coverage",
@ -4071,16 +4161,16 @@
},
{
"name": "sebastian/diff",
"version": "4.0.4",
"version": "4.0.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d"
"reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d",
"reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131",
"reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131",
"shasum": ""
},
"require": {
@ -4125,7 +4215,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
"source": "https://github.com/sebastianbergmann/diff/tree/4.0.4"
"source": "https://github.com/sebastianbergmann/diff/tree/4.0.5"
},
"funding": [
{
@ -4133,7 +4223,7 @@
"type": "github"
}
],
"time": "2020-10-26T13:10:38+00:00"
"time": "2023-05-07T05:35:17+00:00"
},
{
"name": "sebastian/environment",
@ -5100,16 +5190,16 @@
},
{
"name": "twig/twig",
"version": "v3.5.1",
"version": "v3.6.1",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "a6e0510cc793912b451fd40ab983a1d28f611c15"
"reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a6e0510cc793912b451fd40ab983a1d28f611c15",
"reference": "a6e0510cc793912b451fd40ab983a1d28f611c15",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd",
"reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd",
"shasum": ""
},
"require": {
@ -5118,15 +5208,10 @@
"symfony/polyfill-mbstring": "^1.3"
},
"require-dev": {
"psr/container": "^1.0",
"psr/container": "^1.0|^2.0",
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.5-dev"
}
},
"autoload": {
"psr-4": {
"Twig\\": "src/"
@ -5160,7 +5245,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.5.1"
"source": "https://github.com/twigphp/Twig/tree/v3.6.1"
},
"funding": [
{
@ -5172,7 +5257,7 @@
"type": "tidelift"
}
],
"time": "2023-02-08T07:49:20+00:00"
"time": "2023-06-08T12:52:13+00:00"
}
],
"aliases": [],

View file

@ -134,6 +134,7 @@ services:
- _APP_SMTP_SECURE
- _APP_SMTP_USERNAME
- _APP_SMTP_PASSWORD
- _APP_USERS_STATS_RECIPIENTS
- _APP_USAGE_STATS
- _APP_STORAGE_LIMIT
- _APP_STORAGE_PREVIEW_LIMIT
@ -163,6 +164,8 @@ services:
- _APP_GRAPHQL_MAX_BATCH_SIZE
- _APP_GRAPHQL_MAX_COMPLEXITY
- _APP_GRAPHQL_MAX_DEPTH
- _APP_CONSOLE_GITHUB_APP_ID
- _APP_CONSOLE_GITHUB_SECRET
appwrite-realtime:
entrypoint: realtime
@ -478,6 +481,8 @@ services:
- _APP_USAGE_STATS
- _APP_DOCKER_HUB_USERNAME
- _APP_DOCKER_HUB_PASSWORD
- _APP_LOGGING_CONFIG
- _APP_LOGGING_PROVIDER
appwrite-worker-mails:
entrypoint: worker-mails

View file

@ -7,7 +7,7 @@
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
>
<extensions>
<extension class="Appwrite\Tests\TestHook" />
</extensions>

BIN
public/fonts/Inter-Bold.ttf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

View file

@ -158,6 +158,18 @@ class Github extends OAuth2
return $user['name'] ?? '';
}
/**
* @param string $accessToken
*
* @return string
*/
public function getUserSlug(string $accessToken): string
{
$user = $this->getUser($accessToken);
return $user['login'] ?? '';
}
/**
* @param string $accessToken
*
@ -171,13 +183,27 @@ class Github extends OAuth2
$emails = $this->request('GET', 'https://api.github.com/user/emails', ['Authorization: token ' . \urlencode($accessToken)]);
$emails = \json_decode($emails, true);
$verifiedEmail = null;
$primaryEmail = null;
foreach ($emails as $email) {
if (isset($email['verified']) && $email['verified'] === true) {
$this->user['email'] = $email['email'];
$this->user['verified'] = $email['verified'];
break;
$verifiedEmail = $email;
if (isset($email['primary']) && $email['primary'] === true) {
$primaryEmail = $email;
}
}
}
if (!empty($primaryEmail)) {
$this->user['email'] = $primaryEmail['email'];
$this->user['verified'] = $primaryEmail['verified'];
} elseif (!empty($verifiedEmail)) {
$this->user['email'] = $verifiedEmail['email'];
$this->user['verified'] = $verifiedEmail['verified'];
}
}
return $this->user;

View file

@ -7,6 +7,11 @@ use Utopia\Validator;
class Event extends Validator
{
/**
* @var string
*/
protected string $message = 'Event is not valid.';
/**
* Get Description.
*
@ -16,7 +21,7 @@ class Event extends Validator
*/
public function getDescription(): string
{
return 'Event is not valid.';
return $this->message;
}
/**
@ -40,6 +45,12 @@ class Event extends Validator
* Identify all sections of the pattern.
*/
$type = $parts[0] ?? false;
if ($type == 'functions') {
$this->message = 'Triggering a function on a function event is not allowed.';
return false;
}
$resource = $parts[1] ?? false;
$hasSubResource = $count > 3 && ($events[$type]['$resource'] ?? false) && ($events[$type][$parts[2]]['$resource'] ?? false);
$hasSubSubResource = $count > 5 && $hasSubResource && ($events[$type][$parts[2]][$parts[4]]['$resource'] ?? false);

View file

@ -157,6 +157,7 @@ class Exception extends \Exception
public const PROJECT_UNKNOWN = 'project_unknown';
public const PROJECT_PROVIDER_DISABLED = 'project_provider_disabled';
public const PROJECT_PROVIDER_UNSUPPORTED = 'project_provider_unsupported';
public const PROJECT_ALREADY_EXISTS = 'project_already_exists';
public const PROJECT_INVALID_SUCCESS_URL = 'project_invalid_success_url';
public const PROJECT_INVALID_FAILURE_URL = 'project_invalid_failure_url';
public const PROJECT_RESERVED_PROJECT = 'project_reserved_project';
@ -178,6 +179,7 @@ class Exception extends \Exception
/** Domain */
public const DOMAIN_NOT_FOUND = 'domain_not_found';
public const DOMAIN_ALREADY_EXISTS = 'domain_already_exists';
public const DOMAIN_FORBIDDEN = 'domain_forbidden';
public const DOMAIN_VERIFICATION_FAILED = 'domain_verification_failed';
public const DOMAIN_TARGET_INVALID = 'domain_target_invalid';

View file

@ -14,10 +14,14 @@ use Appwrite\Platform\Tasks\Specs;
use Appwrite\Platform\Tasks\SSL;
use Appwrite\Platform\Tasks\Hamster;
use Appwrite\Platform\Tasks\PatchDeleteScheduleUpdatedAtAttribute;
use Appwrite\Platform\Tasks\ClearCardCache;
use Appwrite\Platform\Tasks\Usage;
use Appwrite\Platform\Tasks\Vars;
use Appwrite\Platform\Tasks\Version;
use Appwrite\Platform\Tasks\VolumeSync;
use Appwrite\Platform\Tasks\CalcUsersStats;
use Appwrite\Platform\Tasks\CalcTierStats;
use Appwrite\Platform\Tasks\PatchDeleteProjectCollections;
class Tasks extends Service
{
@ -33,11 +37,16 @@ class Tasks extends Service
->addAction(Install::getName(), new Install())
->addAction(Maintenance::getName(), new Maintenance())
->addAction(PatchCreateMissingSchedules::getName(), new PatchCreateMissingSchedules())
->addAction(ClearCardCache::getName(), new ClearCardCache())
->addAction(PatchDeleteScheduleUpdatedAtAttribute::getName(), new PatchDeleteScheduleUpdatedAtAttribute())
->addAction(Schedule::getName(), new Schedule())
->addAction(Migrate::getName(), new Migrate())
->addAction(SDKs::getName(), new SDKs())
->addAction(VolumeSync::getName(), new VolumeSync())
->addAction(Specs::getName(), new Specs());
->addAction(Specs::getName(), new Specs())
->addAction(CalcUsersStats::getName(), new CalcUsersStats())
->addAction(CalcTierStats::getName(), new CalcTierStats())
->addAction(PatchDeleteProjectCollections::getName(), new PatchDeleteProjectCollections())
;
}
}

View file

@ -0,0 +1,351 @@
<?php
namespace Appwrite\Platform\Tasks;
use Exception;
use League\Csv\CannotInsertRecord;
use Utopia\App;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Action;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Query;
use League\Csv\Writer;
use PHPMailer\PHPMailer\PHPMailer;
use Utopia\Pools\Group;
use Utopia\Registry\Registry;
class CalcTierStats extends Action
{
/*
* Csv cols headers
*/
private array $columns = [
'Project ID',
'Organization ID',
'Organization Members',
'Teams',
'Users',
'Requests',
'Bandwidth',
'Domains',
'Api keys',
'Webhooks',
'Platforms',
'Buckets',
'Files',
'Storage (bytes)',
'Max File Size (bytes)',
'Databases',
'Functions',
'Deployments',
'Executions',
];
protected string $directory = '/usr/local';
protected string $path;
protected string $date;
private array $usageStats = [
'project.$all.network.requests' => 'Requests',
'project.$all.network.bandwidth' => 'Bandwidth',
];
public static function getName(): string
{
return 'calc-tier-stats';
}
public function __construct()
{
$this
->desc('Get stats for projects')
->inject('pools')
->inject('cache')
->inject('dbForConsole')
->inject('register')
->callback(function (Group $pools, Cache $cache, Database $dbForConsole, Registry $register) {
$this->action($pools, $cache, $dbForConsole, $register);
});
}
/**
* @throws \Utopia\Exception
* @throws CannotInsertRecord
*/
public function action(Group $pools, Cache $cache, Database $dbForConsole, Registry $register): void
{
//docker compose exec -t appwrite calc-tier-stats
Console::title('Cloud free tier stats calculation V1');
Console::success(APP_NAME . ' cloud free tier stats calculation has started');
/* Initialise new Utopia app */
$app = new App('UTC');
$console = $app->getResource('console');
/** CSV stuff */
$this->date = date('Y-m-d');
$this->path = "{$this->directory}/tier_stats_{$this->date}.csv";
$csv = Writer::createFromPath($this->path, 'w');
$csv->insertOne($this->columns);
/** Database connections */
$totalProjects = $dbForConsole->count('projects');
Console::success("Found a total of: {$totalProjects} projects");
$projects = [$console];
$count = 0;
$limit = 30;
$sum = 30;
$offset = 0;
while (!empty($projects)) {
foreach ($projects as $project) {
/**
* Skip user projects with id 'console'
*/
if ($project->getId() === 'console') {
continue;
}
Console::info("Getting stats for {$project->getId()}");
try {
$db = $project->getAttribute('database');
$adapter = $pools
->get($db)
->pop()
->getResource();
$dbForProject = new Database($adapter, $cache);
$dbForProject->setDefaultDatabase('appwrite');
$dbForProject->setNamespace('_' . $project->getInternalId());
/** Get Project ID */
$stats['Project ID'] = $project->getId();
$stats['Organization ID'] = $project->getAttribute('teamId', null);
/** Get Total Members */
$teamInternalId = $project->getAttribute('teamInternalId', null);
if ($teamInternalId) {
$stats['Organization Members'] = $dbForConsole->count('memberships', [
Query::equal('teamInternalId', [$teamInternalId])
]);
} else {
$stats['Organization Members'] = 0;
}
/** Get Total internal Teams */
try {
$stats['Teams'] = $dbForProject->count('teams', []);
} catch (\Throwable) {
$stats['Teams'] = 0;
}
/** Get Total users */
try {
$stats['Users'] = $dbForProject->count('users', []);
} catch (\Throwable) {
$stats['Users'] = 0;
}
/** Get Usage stats */
$range = '90d';
$periods = [
'90d' => [
'period' => '1d',
'limit' => 90,
],
];
$tmp = [];
$metrics = $this->usageStats;
Authorization::skip(function () use ($dbForProject, $periods, $range, $metrics, &$tmp) {
foreach ($metrics as $metric => $name) {
$limit = $periods[$range]['limit'];
$period = $periods[$range]['period'];
$requestDocs = $dbForProject->find('stats', [
Query::equal('period', [$period]),
Query::equal('metric', [$metric]),
Query::limit($limit),
Query::orderDesc('time'),
]);
$tmp[$metric] = [];
foreach ($requestDocs as $requestDoc) {
if (empty($requestDoc)) {
continue;
}
$tmp[$metric][] = [
'value' => $requestDoc->getAttribute('value'),
'date' => $requestDoc->getAttribute('time'),
];
}
$tmp[$metric] = array_reverse($tmp[$metric]);
$tmp[$metric] = array_sum(array_column($tmp[$metric], 'value'));
}
});
foreach ($tmp as $key => $value) {
$stats[$metrics[$key]] = $value;
}
try {
/** Get Domains */
$stats['Domains'] = $dbForConsole->count('domains', [
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
} catch (\Throwable) {
$stats['Domains'] = 0;
}
try {
/** Get Api keys */
$stats['Api keys'] = $dbForConsole->count('keys', [
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
} catch (\Throwable) {
$stats['Api keys'] = 0;
}
try {
/** Get Webhooks */
$stats['Webhooks'] = $dbForConsole->count('webhooks', [
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
} catch (\Throwable) {
$stats['Webhooks'] = 0;
}
try {
/** Get Platforms */
$stats['Platforms'] = $dbForConsole->count('platforms', [
Query::equal('projectInternalId', [$project->getInternalId()]),
]);
} catch (\Throwable) {
$stats['Platforms'] = 0;
}
/** Get Files & Buckets */
$filesCount = 0;
$filesSum = 0;
$maxFileSize = 0;
$counter = 0;
try {
$buckets = $dbForProject->find('buckets', []);
foreach ($buckets as $bucket) {
$file = $dbForProject->findOne('bucket_' . $bucket->getInternalId(), [Query::orderDesc('sizeOriginal'),]);
if (empty($file)) {
continue;
}
$filesSum += $dbForProject->sum('bucket_' . $bucket->getInternalId(), 'sizeOriginal', [], 0);
$filesCount += $dbForProject->count('bucket_' . $bucket->getInternalId(), []);
if ($file->getAttribute('sizeOriginal') > $maxFileSize) {
$maxFileSize = $file->getAttribute('sizeOriginal');
}
$counter++;
}
} catch (\Throwable) {
;
}
$stats['Buckets'] = $counter;
$stats['Files'] = $filesCount;
$stats['Storage (bytes)'] = $filesSum;
$stats['Max File Size (bytes)'] = $maxFileSize;
try {
/** Get Total Functions */
$stats['Databases'] = $dbForProject->count('databases', []);
} catch (\Throwable) {
$stats['Databases'] = 0;
}
/** Get Total Functions */
try {
$stats['Functions'] = $dbForProject->count('functions', []);
} catch (\Throwable) {
$stats['Functions'] = 0;
}
/** Get Total Deployments */
try {
$stats['Deployments'] = $dbForProject->count('deployments', []);
} catch (\Throwable) {
$stats['Deployments'] = 0;
}
/** Get Total Executions */
try {
$stats['Executions'] = $dbForProject->count('executions', []);
} catch (\Throwable) {
$stats['Executions'] = 0;
}
$csv->insertOne(array_values($stats));
} catch (\Throwable $th) {
Console::error('Failed on project ("' . $project->getId() . '") version with error on File: ' . $th->getFile() . ' line no: ' . $th->getline() . ' with message: ' . $th->getMessage());
} finally {
$pools
->get($db)
->reclaim();
}
}
$sum = \count($projects);
$projects = $dbForConsole->find('projects', [
Query::limit($limit),
Query::offset($offset),
]);
$offset = $offset + $limit;
$count = $count + $sum;
}
Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects...');
$pools
->get('console')
->reclaim();
/** @var PHPMailer $mail */
$mail = $register->get('smtp');
$mail->clearAddresses();
$mail->clearAllRecipients();
$mail->clearReplyTos();
$mail->clearAttachments();
$mail->clearBCCs();
$mail->clearCCs();
try {
/** Addresses */
$mail->setFrom(App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), 'Appwrite Cloud Hamster');
$recipients = explode(',', App::getEnv('_APP_USERS_STATS_RECIPIENTS', ''));
foreach ($recipients as $recipient) {
$mail->addAddress($recipient);
}
/** Attachments */
$mail->addAttachment($this->path);
/** Content */
$mail->Subject = "Cloud Report for {$this->date}";
$mail->Body = "Please find the daily cloud report atttached";
$mail->send();
Console::success('Email has been sent!');
} catch (Exception $e) {
Console::error("Message could not be sent. Mailer Error: {$mail->ErrorInfo}");
}
}
}

View file

@ -0,0 +1,176 @@
<?php
namespace Appwrite\Platform\Tasks;
use Exception;
use Utopia\App;
use Utopia\Platform\Action;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Query;
use League\Csv\Writer;
use PHPMailer\PHPMailer\PHPMailer;
use Utopia\Pools\Group;
use Utopia\Registry\Registry;
class CalcUsersStats extends Action
{
private array $columns = [
'Project ID',
'Project Name',
'Team ID',
'Team name',
'Users'
];
protected string $directory = '/usr/local';
protected string $path;
protected string $date;
public static function getName(): string
{
return 'calc-users-stats';
}
public function __construct()
{
$this
->desc('Get stats for projects')
->inject('pools')
->inject('cache')
->inject('dbForConsole')
->inject('register')
->callback(function (Group $pools, Cache $cache, Database $dbForConsole, Registry $register) {
$this->action($pools, $cache, $dbForConsole, $register);
});
}
public function action(Group $pools, Cache $cache, Database $dbForConsole, Registry $register): void
{
//docker compose exec -t appwrite calc-users-stats
Console::title('Cloud Users calculation V1');
Console::success(APP_NAME . ' cloud Users calculation has started');
/* Initialise new Utopia app */
$app = new App('UTC');
$console = $app->getResource('console');
/** CSV stuff */
$this->date = date('Y-m-d');
$this->path = "{$this->directory}/users_stats_{$this->date}.csv";
$csv = Writer::createFromPath($this->path, 'w');
$csv->insertOne($this->columns);
/** Database connections */
$totalProjects = $dbForConsole->count('projects');
Console::success("Found a total of: {$totalProjects} projects");
$projects = [$console];
$count = 0;
$limit = 30;
$sum = 30;
$offset = 0;
while (!empty($projects)) {
foreach ($projects as $project) {
/**
* Skip user projects with id 'console'
*/
if ($project->getId() === 'console') {
continue;
}
Console::info("Getting stats for {$project->getId()}");
try {
$db = $project->getAttribute('database');
$adapter = $pools
->get($db)
->pop()
->getResource();
$dbForProject = new Database($adapter, $cache);
$dbForProject->setDefaultDatabase('appwrite');
$dbForProject->setNamespace('_' . $project->getInternalId());
/** Get Project ID */
$stats['Project ID'] = $project->getId();
/** Get Project Name */
$stats['Project Name'] = $project->getAttribute('name');
/** Get Team Name and Id */
$teamId = $project->getAttribute('teamId', null);
$teamName = null;
if ($teamId) {
$team = $dbForConsole->getDocument('teams', $teamId);
$teamName = $team->getAttribute('name');
}
$stats['Team ID'] = $teamId;
$stats['Team name'] = $teamName;
/** Get Total Users */
$stats['users'] = $dbForProject->count('users', []);
$csv->insertOne(array_values($stats));
} catch (\Throwable $th) {
Console::error('Failed to update project ("' . $project->getId() . '") version with error: ' . $th->getMessage());
} finally {
$pools
->get($db)
->reclaim();
}
}
$sum = \count($projects);
$projects = $dbForConsole->find('projects', [
Query::limit($limit),
Query::offset($offset),
]);
$offset = $offset + $limit;
$count = $count + $sum;
}
Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects...');
$pools
->get('console')
->reclaim();
/** @var PHPMailer $mail */
$mail = $register->get('smtp');
$mail->clearAddresses();
$mail->clearAllRecipients();
$mail->clearReplyTos();
$mail->clearAttachments();
$mail->clearBCCs();
$mail->clearCCs();
try {
/** Addresses */
$mail->setFrom(App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), 'Appwrite Cloud Hamster');
$recipients = explode(',', App::getEnv('_APP_USERS_STATS_RECIPIENTS', ''));
foreach ($recipients as $recipient) {
$mail->addAddress($recipient);
}
/** Attachments */
$mail->addAttachment($this->path);
/** Content */
$mail->Subject = "Cloud Report for {$this->date}";
$mail->Body = "Please find the daily cloud report atttached";
$mail->send();
Console::success('Email has been sent!');
} catch (Exception $e) {
Console::error("Message could not be sent. Mailer Error: {$mail->ErrorInfo}");
}
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace Appwrite\Platform\Tasks;
use Utopia\Cache\Adapter\Filesystem;
use Utopia\Cache\Cache;
use Utopia\Platform\Action;
use Utopia\CLI\Console;
use Utopia\Database\Query;
use Utopia\Database\Database;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
class ClearCardCache extends Action
{
public static function getName(): string
{
return 'clear-card-cache';
}
public function __construct()
{
$this
->desc('Deletes card cache for specific user')
->param('userId', '', new UID(), 'User UID.', false)
->inject('dbForConsole')
->callback(fn (string $userId, Database $dbForConsole) => $this->action($userId, $dbForConsole));
}
public function action(string $userId, Database $dbForConsole): void
{
Authorization::disable();
Authorization::setDefaultStatus(false);
Console::title('ClearCardCache V1');
Console::success(APP_NAME . ' ClearCardCache v1 has started');
$resources = ['card/' . $userId, 'card-back/' . $userId, 'card-og/' . $userId];
$caches = Authorization::skip(fn () => $dbForConsole->find('cache', [
Query::equal('resource', $resources),
Query::limit(100)
]));
$count = \count($caches);
Console::info("Going to delete {$count} cache records in 10 seconds...");
\sleep(10);
foreach ($caches as $cache) {
$key = $cache->getId();
$cacheFolder = new Cache(
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-console')
);
$cacheFolder->purge($key);
Authorization::skip(fn () => $dbForConsole->deleteDocument('cache', $cache->getId()));
}
Console::success(APP_NAME . ' ClearCardCache v1 has finished');
}
}

View file

@ -2,6 +2,7 @@
namespace Appwrite\Platform\Tasks;
use Appwrite\Network\Validator\Origin;
use Exception;
use Utopia\App;
use Utopia\Platform\Action;
@ -12,6 +13,7 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Analytics\Adapter\Mixpanel;
use Utopia\Analytics\Event;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Pools\Group;
@ -98,6 +100,12 @@ class Hamster extends Action
/** Get Total Functions */
$statsPerProject['custom_functions'] = $dbForProject->count('functions', [], APP_LIMIT_COUNT);
foreach (\array_keys(Config::getParam('runtimes')) as $runtime) {
$statsPerProject['custom_functions_' . $runtime] = $dbForProject->count('functions', [
Query::equal('runtime', [$runtime]),
], APP_LIMIT_COUNT);
}
/** Get Total Deployments */
$statsPerProject['custom_deployments'] = $dbForProject->count('deployments', [], APP_LIMIT_COUNT);
@ -136,7 +144,10 @@ class Hamster extends Action
}
/** Get Domains */
$statsPerProject['custom_domains'] = $dbForProject->count('domains', [], APP_LIMIT_COUNT);
$statsPerProject['custom_domains'] = $dbForConsole->count('domains', [
Query::equal('projectInternalId', [$project->getInternalId()]),
Query::limit(APP_LIMIT_COUNT)
]);
/** Get Platforms */
$platforms = $dbForConsole->find('platforms', [
@ -152,7 +163,7 @@ class Hamster extends Action
return $platform['type'] === 'android';
}));
$statsPerProject['custom_platforms_iOS'] = sizeof(array_filter($platforms, function ($platform) {
$statsPerProject['custom_platforms_apple'] = sizeof(array_filter($platforms, function ($platform) {
return str_contains($platform['type'], 'apple');
}));
@ -160,6 +171,19 @@ class Hamster extends Action
return str_contains($platform['type'], 'flutter');
}));
$flutterPlatforms = [Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_FLUTTER_LINUX];
foreach ($flutterPlatforms as $flutterPlatform) {
$statsPerProject['custom_platforms_' . $flutterPlatform] = sizeof(array_filter($platforms, function ($platform) use ($flutterPlatform) {
return $platform['type'] === $flutterPlatform;
}));
}
$statsPerProject['custom_platforms_api_keys'] = $dbForConsole->count('keys', [
Query::equal('projectInternalId', [$project->getInternalId()]),
Query::limit(APP_LIMIT_COUNT)
]);
/** Get Usage $statsPerProject */
$periods = [
'infinity' => [

View file

@ -0,0 +1,129 @@
<?php
namespace Appwrite\Platform\Tasks;
use Utopia\App;
use Utopia\Platform\Action;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Query;
use Utopia\Pools\Group;
use Utopia\Validator\Numeric;
class PatchDeleteProjectCollections extends Action
{
private array $names = [
'webhooks',
'platforms',
'schedules',
'projects',
'domains',
'certificates',
'keys',
'realtime',
];
public static function getName(): string
{
return 'patch-delete-project-collections';
}
public function __construct()
{
$this
->desc('Delete unnecessary project collections')
->param('offset', 0, new Numeric(), 'Resume deletion from param pos', true)
->inject('pools')
->inject('cache')
->inject('dbForConsole')
->callback(function (int $offset, Group $pools, Cache $cache, Database $dbForConsole) {
$this->action($offset, $pools, $cache, $dbForConsole);
});
}
public function action(int $offset, Group $pools, Cache $cache, Database $dbForConsole): void
{
//docker compose exec -t appwrite patch-delete-project-collections
Console::title('Delete project collections V1');
Console::success(APP_NAME . ' delete project collections has started');
/* Initialise new Utopia app */
$app = new App('UTC');
$console = $app->getResource('console');
/** Database connections */
$totalProjects = $dbForConsole->count('projects');
Console::success("Found a total of: {$totalProjects} projects");
$projects = [$console];
$count = 0;
$limit = 50;
$sum = 50;
$offset = $offset;
while (!empty($projects)) {
foreach ($projects as $project) {
/**
* Skip user projects with id 'console'
*/
if ($project->getId() === 'console') {
continue;
}
Console::info("Deleting collections for {$project->getId()}");
try {
$db = $project->getAttribute('database');
$adapter = $pools
->get($db)
->pop()
->getResource();
$dbForProject = new Database($adapter, $cache);
$dbForProject->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$dbForProject->setNamespace('_' . $project->getInternalId());
foreach ($this->names as $name) {
if (empty($name)) {
continue;
}
if ($dbForProject->exists(App::getEnv('_APP_DB_SCHEMA', 'appwrite'), $name)) {
if ($dbForProject->deleteCollection($name)) {
Console::log('Deleted ' . $name);
} else {
Console::error('Failed to delete ' . $name);
}
}
}
} catch (\Throwable $th) {
Console::error('Failed on project ("' . $project->getId() . '") version with error: ' . $th->getMessage());
} finally {
$pools
->get($db)
->reclaim();
}
}
$sum = \count($projects);
$projects = $dbForConsole->find('projects', [
Query::limit($limit),
Query::offset($offset),
]);
if (!empty($projects)) {
Console::log('Querying..... offset=' . $offset . ' , limit=' . $limit . ', count=' . $count);
}
$offset = $offset + $limit;
$count = $count + $sum;
}
Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects...');
$pools
->get('console')
->reclaim();
}
}

View file

@ -174,6 +174,7 @@ abstract class Worker
* @throws Exception
*/
protected static $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
protected function getProjectDB(Document $project): Database
{
global $register;
@ -218,6 +219,14 @@ abstract class Worker
$pools = $register->get('pools'); /** @var Group $pools */
$databaseName = 'console';
if (isset(self::$databases[$databaseName])) {
$database = self::$databases[$databaseName];
$database->setNamespace('console');
return $database;
}
$dbAdapter = $pools
->get('console')
->pop()
@ -226,6 +235,8 @@ abstract class Worker
$database = new Database($dbAdapter, $this->getCache());
self::$databases[$databaseName] = $database;
$database->setNamespace('console');
return $database;

View file

@ -22,7 +22,9 @@ class Base extends Queries
*/
public function __construct(string $collection, array $allowedAttributes)
{
$collection = Config::getParam('collections', [])[$collection];
$config = Config::getParam('collections', []);
$collections = array_merge($config['console'], $config['projects'], $config['buckets'], $config['databases']);
$collection = $collections[$collection];
// array for constant lookup time
$allowedAttributesLookup = [];
foreach ($allowedAttributes as $attribute) {

View file

@ -160,17 +160,14 @@ class Client
* @param array $params
* @param array $headers
* @param bool $decode
* @return array|string
* @return array
* @throws Exception
*/
public function call(string $method, string $path = '', array $headers = [], array $params = [], bool $decode = true)
public function call(string $method, string $path = '', array $headers = [], array $params = [], bool $decode = true): array
{
$headers = array_merge($this->headers, $headers);
$ch = curl_init($this->endpoint . $path . (($method == self::METHOD_GET && !empty($params)) ? '?' . http_build_query($params) : ''));
$responseHeaders = [];
$responseStatus = -1;
$responseType = '';
$responseBody = '';
switch ($headers['content-type']) {
case 'application/json':
@ -220,7 +217,7 @@ class Client
curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
}
// Allow self signed certificates
// Allow self-signed certificates
if ($this->selfSigned) {
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
@ -230,22 +227,18 @@ class Client
$responseType = $responseHeaders['content-type'] ?? '';
$responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($decode) {
switch (substr($responseType, 0, strpos($responseType, ';'))) {
case 'application/json':
$json = json_decode($responseBody, true);
if ($decode && substr($responseType, 0, strpos($responseType, ';')) == 'application/json') {
$json = json_decode($responseBody, true);
if ($json === null) {
throw new Exception('Failed to parse response: ' . $responseBody);
}
$responseBody = $json;
$json = null;
break;
if ($json === null) {
throw new Exception('Failed to parse response: ' . $responseBody);
}
$responseBody = $json;
$json = null;
}
if ((curl_errno($ch)/* || 200 != $responseStatus*/)) {
if ((curl_errno($ch))) {
throw new Exception(curl_error($ch) . ' with status code ' . $responseStatus, $responseStatus);
}
@ -273,7 +266,7 @@ class Client
{
$cookies = [];
parse_str(strtr($cookie, array('&' => '%26', '+' => '%2B', ';' => '&')), $cookies);
parse_str(strtr($cookie, ['&' => '%26', '+' => '%2B', ';' => '&']), $cookies);
return $cookies;
}

View file

@ -12,6 +12,12 @@ class HTTPTest extends Scope
use ProjectNone;
use SideNone;
public function setUp(): void
{
parent::setUp();
$this->client->setEndpoint('http://localhost');
}
public function testOptions()
{
/**
@ -32,24 +38,6 @@ class HTTPTest extends Scope
$this->assertEmpty($response['body']);
}
public function testError()
{
/**
* Test for SUCCESS
*/
$this->markTestIncomplete('This test needs to be updated for the new console.');
// $response = $this->client->call(Client::METHOD_GET, '/error', \array_merge([
// 'origin' => 'http://localhost',
// 'content-type' => 'application/json',
// ]), []);
// $this->assertEquals(404, $response['headers']['status-code']);
// $this->assertEquals('Not Found', $response['body']['message']);
// $this->assertEquals(Exception::GENERAL_ROUTE_NOT_FOUND, $response['body']['type']);
// $this->assertEquals(404, $response['body']['code']);
// $this->assertEquals('dev', $response['body']['version']);
}
public function testHumans()
{
/**
@ -57,7 +45,7 @@ class HTTPTest extends Scope
*/
$response = $this->client->call(Client::METHOD_GET, '/humans.txt', \array_merge([
'origin' => 'http://localhost',
]), []);
]));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertStringContainsString('# humanstxt.org/', $response['body']);
@ -70,7 +58,7 @@ class HTTPTest extends Scope
*/
$response = $this->client->call(Client::METHOD_GET, '/robots.txt', \array_merge([
'origin' => 'http://localhost',
]), []);
]));
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertStringContainsString('# robotstxt.org/', $response['body']);
@ -87,7 +75,7 @@ class HTTPTest extends Scope
*/
$response = $this->client->call(Client::METHOD_GET, '/.well-known/acme-challenge/8DdIKX257k6Dih5s_saeVMpTnjPJdKO5Ase0OCiJrIg', \array_merge([
'origin' => 'http://localhost',
]), []);
]));
$this->assertEquals(404, $response['headers']['status-code']);
// 'Unknown path', but validation passed
@ -97,7 +85,7 @@ class HTTPTest extends Scope
*/
$response = $this->client->call(Client::METHOD_GET, '/.well-known/acme-challenge/../../../../../../../etc/passwd', \array_merge([
'origin' => 'http://localhost',
]), []);
]));
$this->assertEquals(400, $response['headers']['status-code']);
@ -171,4 +159,15 @@ class HTTPTest extends Scope
$this->assertIsString($body['server-ruby']);
$this->assertIsString($body['console-cli']);
}
public function testDefaultOAuth2()
{
$response = $this->client->call(Client::METHOD_GET, '/auth/oauth2/success', $this->getHeaders());
$this->assertEquals(200, $response['headers']['status-code']);
$response = $this->client->call(Client::METHOD_GET, '/auth/oauth2/failure', $this->getHeaders());
$this->assertEquals(200, $response['headers']['status-code']);
}
}

View file

@ -104,7 +104,7 @@ trait ProjectCustom
'name' => 'Webhook Test',
'events' => [
'databases.*',
'functions.*',
// 'functions.*', TODO @christyjacob4 : enable test once we allow functions.* events
'buckets.*',
'teams.*',
'users.*'

View file

@ -17,10 +17,7 @@ abstract class Scope extends TestCase
protected function setUp(): void
{
$this->client = new Client();
$this->client
->setEndpoint($this->endpoint)
;
$this->client->setEndpoint($this->endpoint);
}
protected function tearDown(): void
@ -45,10 +42,10 @@ abstract class Scope extends TestCase
{
sleep(2);
$resquest = json_decode(file_get_contents('http://request-catcher:5000/__last_request__'), true);
$resquest['data'] = json_decode($resquest['data'], true);
$request = json_decode(file_get_contents('http://request-catcher:5000/__last_request__'), true);
$request['data'] = json_decode($request['data'], true);
return $resquest;
return $request;
}
/**

View file

@ -252,6 +252,8 @@ class AccountCustomClientTest extends Scope
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertStringContainsString('a_session_' . $this->getProject()['$id'] . '=deleted', $response['headers']['set-cookie']);
$this->assertEquals('[]', $response['headers']['x-fallback-cookies']);
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
'origin' => 'http://localhost',

View file

@ -981,6 +981,12 @@ trait DatabasesBase
$this->assertEquals(400, $document4['headers']['status-code']);
// Delete document 4 with incomplete path
$this->assertEquals(404, $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()))['headers']['status-code']);
return $data;
}
@ -2869,7 +2875,7 @@ trait DatabasesBase
$databaseId = $database['body']['$id'];
// Create collection
$movies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/', array_merge([
$movies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
'x-appwrite-key' => $this->getProject()['apiKey']

View file

@ -3,6 +3,7 @@
namespace Tests\E2E\Services\Projects;
use Appwrite\Auth\Auth;
use Appwrite\Extend\Exception;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\ProjectConsole;
use Tests\E2E\Scopes\SideClient;
@ -96,7 +97,37 @@ class ProjectsConsoleClientTest extends Scope
$this->assertEquals(400, $response['headers']['status-code']);
return ['projectId' => $projectId];
return [
'projectId' => $projectId,
'teamId' => $team['body']['$id']
];
}
/**
* @depends testCreateProject
*/
public function testCreateDuplicateProject($data)
{
$teamId = $data['teamId'] ?? '';
$projectId = $data['projectId'] ?? '';
/**
* Test for FAILURE
*/
$response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'projectId' => $projectId,
'name' => 'Project Duplicate',
'teamId' => $teamId,
'region' => 'default'
]);
$this->assertEquals(409, $response['headers']['status-code']);
$this->assertEquals(409, $response['body']['code']);
$this->assertEquals(Exception::PROJECT_ALREADY_EXISTS, $response['body']['type']);
$this->assertEquals('Project with the requested ID already exists.', $response['body']['message']);
}
/**
@ -781,11 +812,11 @@ class ProjectsConsoleClientTest extends Scope
$this->assertEquals($response['headers']['status-code'], 501);
$response = $this->client->call(Client::METHOD_POST, '/account/anonymous', array_merge([
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/anonymous', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $id,
]), []);
]));
$this->assertEquals($response['headers']['status-code'], 501);
@ -841,6 +872,19 @@ class ProjectsConsoleClientTest extends Scope
'name' => $name,
]);
$email = uniqid() . 'user@localhost.test';
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $id,
]), [
'userId' => ID::unique(),
'email' => $email,
'password' => $password,
'name' => $name,
]);
$this->assertEquals($response['headers']['status-code'], 501);
/**
@ -856,6 +900,8 @@ class ProjectsConsoleClientTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$email = uniqid() . 'user@localhost.test';
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',

View file

@ -497,7 +497,7 @@ class RealtimeConsoleClientTest extends Scope
$this->assertArrayHasKey('timestamp', $response['data']);
$this->assertCount(1, $response['data']['channels']);
$this->assertContains('console', $response['data']['channels']);
$this->assertContains("functions.{$functionId}.deployments.{$deploymentId}.create", $response['data']['events']);
// $this->assertContains("functions.{$functionId}.deployments.{$deploymentId}.create", $response['data']['events']); TODO @christyjacob4 : enable test once we allow functions.* events
$this->assertNotEmpty($response['data']['payload']);
$client->close();

View file

@ -415,10 +415,10 @@ class WebhooksCustomServerTest extends Scope
$this->assertEquals($webhook['method'], 'POST');
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
$this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString('functions.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.create", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
@ -474,10 +474,10 @@ class WebhooksCustomServerTest extends Scope
$this->assertEquals($webhook['method'], 'POST');
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
$this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString('functions.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
@ -529,12 +529,12 @@ class WebhooksCustomServerTest extends Scope
$this->assertEquals($webhook['method'], 'POST');
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
$this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
@ -572,16 +572,16 @@ class WebhooksCustomServerTest extends Scope
$this->assertEquals($webhook['method'], 'POST');
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
$this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString('functions.*.deployments.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.*.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.deployments.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*.deployments.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.*.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.deployments.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
@ -620,16 +620,16 @@ class WebhooksCustomServerTest extends Scope
$this->assertEquals($webhook['method'], 'POST');
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
$this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString('functions.*.executions.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.*.executions.{$executionId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.executions.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.executions.{$executionId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*.executions.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.*.executions.{$executionId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.executions.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.executions.{$executionId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
@ -643,16 +643,16 @@ class WebhooksCustomServerTest extends Scope
$this->assertEquals($webhook['method'], 'POST');
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
$this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString('functions.*.executions.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.*.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.executions.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*.executions.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.*.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.executions.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
@ -688,16 +688,16 @@ class WebhooksCustomServerTest extends Scope
$this->assertEquals($webhook['method'], 'POST');
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
$this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString('functions.*.deployments.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.*.deployments.{$deploymentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.deployments.*.delete", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*.deployments.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.*.deployments.{$deploymentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.deployments.*.delete", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
@ -733,10 +733,10 @@ class WebhooksCustomServerTest extends Scope
$this->assertEquals($webhook['method'], 'POST');
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
$this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString('functions.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
$this->assertStringContainsString("functions.{$id}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString('functions.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
// $this->assertStringContainsString("functions.{$id}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);

View file

@ -50,7 +50,7 @@ class EventValidatorTest extends TestCase
$this->assertTrue($this->object->isValid('databases.books'));
$this->assertTrue($this->object->isValid('databases.books.collections.chapters'));
$this->assertTrue($this->object->isValid('databases.books.collections.*'));
$this->assertTrue($this->object->isValid('functions.*'));
// $this->assertTrue($this->object->isValid('functions.*')); TODO @christyjacob4 : enable test once we allow functions.* events
$this->assertTrue($this->object->isValid('buckets.*'));
$this->assertTrue($this->object->isValid('teams.*'));
$this->assertTrue($this->object->isValid('users.*'));

View file

@ -15,16 +15,18 @@ class CollectionsTest extends TestCase
public function testDuplicateRules(): void
{
foreach ($this->collections as $key => $collection) {
if (array_key_exists('attributes', $collection)) {
foreach ($collection['attributes'] as $check) {
$occurrences = 0;
foreach ($collection['attributes'] as $attribute) {
if ($attribute['$id'] == $check['$id']) {
$occurrences++;
foreach ($this->collections as $key => $sections) {
foreach ($sections as $key => $collection) {
if (array_key_exists('attributes', $collection)) {
foreach ($collection['attributes'] as $check) {
$occurrences = 0;
foreach ($collection['attributes'] as $attribute) {
if ($attribute['$id'] == $check['$id']) {
$occurrences++;
}
}
$this->assertEquals(1, $occurrences);
}
$this->assertEquals(1, $occurrences);
}
}
}