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
90 changed files with 4065 additions and 2030 deletions
+3
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
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 }}
+1 -1
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
+7 -2
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 && \
+1 -1
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 */
+1
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]
+33
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" }
}
+9
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" }
}
+1660 -1679
View File
File diff suppressed because it is too large Load Diff
+12 -2
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.',
+10
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);
});
+817 -12
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());
});
+1 -1
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.');
}
+81 -38
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());
+2 -8
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
+3 -2
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)
+1 -1
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()) {
+45 -3
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);
});
+6 -5
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.');
}
+23
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
+5 -5
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));
+52 -25
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);
+10 -7
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
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php calc-tier-stats $@
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php calc-users-stats $@
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php clear-card-cache $@
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
php /usr/src/code/app/cli.php patch-delete-project-collections $@
+10 -9
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": [
{
Generated
+148 -63
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": [],
+5
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
+1 -1
View File
@@ -7,7 +7,7 @@
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
>
>
<extensions>
<extension class="Appwrite\Tests\TestHook" />
</extensions>
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.0 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

+29 -3
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;
+12 -1
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);
+2
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';
+10 -1
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())
;
}
}
@@ -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}");
}
}
}
@@ -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}");
}
}
}
@@ -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');
}
}
+26 -2
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' => [
@@ -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();
}
}
+11
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;
@@ -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) {
+12 -19
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;
}
+21 -22
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']);
}
}
+1 -1
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.*'
+4 -7
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;
}
/**
@@ -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',
@@ -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']
@@ -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',
@@ -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();
@@ -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']);
@@ -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.*'));
+10 -8
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);
}
}
}