Merge branch 'feat-db-pools-master' into feat-executor-v3
No files matched your search
@@ -47,8 +47,9 @@ _APP_SMTP_PORT=1025
|
||||
_APP_SMTP_SECURE=
|
||||
_APP_SMTP_USERNAME=
|
||||
_APP_SMTP_PASSWORD=
|
||||
_APP_HAMSTER_RECIPIENTS=
|
||||
_APP_HAMSTER_INTERVAL=86400
|
||||
_APP_HAMSTER_TIME=21:00
|
||||
_APP_MIXPANEL_TOKEN=
|
||||
_APP_SMS_PROVIDER=sms://username:password@mock
|
||||
_APP_SMS_FROM=+123456789
|
||||
_APP_STORAGE_LIMIT=30000000
|
||||
@@ -77,4 +78,6 @@ _APP_LOGGING_PROVIDER=
|
||||
_APP_LOGGING_CONFIG=
|
||||
_APP_REGION=default
|
||||
_APP_DOCKER_HUB_USERNAME=
|
||||
_APP_DOCKER_HUB_PASSWORD=
|
||||
_APP_DOCKER_HUB_PASSWORD=
|
||||
_APP_CONSOLE_GITHUB_SECRET=
|
||||
_APP_CONSOLE_GITHUB_APP_ID=
|
||||
@@ -29,7 +29,7 @@ ENV VITE_APPWRITE_GROWTH_ENDPOINT=$VITE_APPWRITE_GROWTH_ENDPOINT
|
||||
RUN npm ci
|
||||
RUN npm run build
|
||||
|
||||
FROM appwrite/base:0.1.0 as final
|
||||
FROM appwrite/base:0.2.2 as final
|
||||
|
||||
LABEL maintainer="team@appwrite.io"
|
||||
|
||||
@@ -136,6 +136,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
|
||||
@@ -156,6 +157,8 @@ 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/clear-card-cache && \
|
||||
chmod +x /usr/local/bin/maintenance && \
|
||||
chmod +x /usr/local/bin/usage && \
|
||||
chmod +x /usr/local/bin/install && \
|
||||
|
||||
@@ -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]
|
||||
@@ -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" }
|
||||
}
|
||||
@@ -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" }
|
||||
}
|
||||
@@ -2356,17 +2356,6 @@ $collections = [
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('scheduleUpdatedAt'),
|
||||
'type' => Database::VAR_DATETIME,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => false,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('timeout'),
|
||||
'type' => Database::VAR_INTEGER,
|
||||
|
||||
@@ -566,6 +566,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.
|
||||
*/
|
||||
|
||||
@@ -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());
|
||||
});
|
||||
@@ -93,7 +93,6 @@ App::post('/v1/functions')
|
||||
'deployment' => '',
|
||||
'events' => $events,
|
||||
'schedule' => $schedule,
|
||||
'scheduleUpdatedAt' => DateTime::now(),
|
||||
'timeout' => $timeout,
|
||||
'entrypoint' => $entrypoint,
|
||||
'buildCommand' => $buildCommand,
|
||||
@@ -544,7 +543,6 @@ App::put('/v1/functions/:functionId')
|
||||
'name' => $name,
|
||||
'events' => $events,
|
||||
'schedule' => $schedule,
|
||||
'scheduleUpdatedAt' => DateTime::now(),
|
||||
'timeout' => $timeout,
|
||||
'enabled' => $enabled,
|
||||
'logging' => $logging,
|
||||
@@ -556,18 +554,11 @@ App::put('/v1/functions/:functionId')
|
||||
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
|
||||
/**
|
||||
* In case we want to clear the schedule
|
||||
*/
|
||||
if (!empty($function->getAttribute('deployment'))) {
|
||||
$schedule->setAttribute('resourceUpdatedAt', $function->getAttribute('scheduleUpdatedAt'));
|
||||
}
|
||||
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
|
||||
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$eventsInstance->setParam('functionId', $function->getId());
|
||||
@@ -624,15 +615,10 @@ App::patch('/v1/functions/:functionId/deployments/:deploymentId')
|
||||
])));
|
||||
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
|
||||
$active = !empty($function->getAttribute('schedule'));
|
||||
|
||||
if ($active) {
|
||||
$schedule->setAttribute('resourceUpdatedAt', datetime::now());
|
||||
}
|
||||
|
||||
$schedule->setAttribute('active', $active);
|
||||
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$events
|
||||
@@ -881,22 +867,6 @@ App::post('/v1/functions/:functionId/deployments')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO Should we update also the function collection with the scheduleUpdatedAt attr?
|
||||
*/
|
||||
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
|
||||
$active = !empty($function->getAttribute('schedule'));
|
||||
|
||||
if ($active) {
|
||||
$schedule->setAttribute('resourceUpdatedAt', datetime::now());
|
||||
}
|
||||
|
||||
$schedule->setAttribute('active', $active);
|
||||
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$metadata = null;
|
||||
|
||||
$events
|
||||
@@ -1531,7 +1501,8 @@ App::post('/v1/functions/:functionId/variables')
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $functionId, string $key, string $value, Document $project, Response $response, Database $dbForProject) {
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $functionId, string $key, string $value, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) {
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
@@ -1561,6 +1532,13 @@ App::post('/v1/functions/:functionId/variables')
|
||||
throw new Exception(Exception::VARIABLE_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$dbForProject->deleteCachedDocument('functions', $function->getId());
|
||||
|
||||
// Stop all running runtimes with this variable
|
||||
@@ -1657,7 +1635,8 @@ App::put('/v1/functions/:functionId/variables/:variableId')
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $functionId, string $variableId, string $key, ?string $value, Document $project, Response $response, Database $dbForProject) {
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $functionId, string $variableId, string $key, ?string $value, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) {
|
||||
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
@@ -1686,6 +1665,13 @@ App::put('/v1/functions/:functionId/variables/:variableId')
|
||||
throw new Exception(Exception::VARIABLE_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$dbForProject->deleteCachedDocument('functions', $function->getId());
|
||||
|
||||
// Stop all running runtimes with this variable
|
||||
@@ -1715,7 +1701,8 @@ App::delete('/v1/functions/:functionId/variables/:variableId')
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $functionId, string $variableId, Document $project, Response $response, Database $dbForProject) {
|
||||
->inject('dbForConsole')
|
||||
->action(function (string $functionId, string $variableId, Document $project, Response $response, Database $dbForProject, Database $dbForConsole) {
|
||||
$function = $dbForProject->getDocument('functions', $functionId);
|
||||
|
||||
if ($function->isEmpty()) {
|
||||
@@ -1733,6 +1720,14 @@ App::delete('/v1/functions/:functionId/variables/:variableId')
|
||||
}
|
||||
|
||||
$dbForProject->deleteDocument('variables', $variable->getId());
|
||||
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule
|
||||
->setAttribute('resourceUpdatedAt', DateTime::now())
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deployment')));
|
||||
Authorization::skip(fn () => $dbForConsole->updateDocument('schedules', $schedule->getId(), $schedule));
|
||||
|
||||
$dbForProject->deleteCachedDocument('functions', $function->getId());
|
||||
|
||||
// Stop all running runtimes with this variable
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Utopia\App;
|
||||
|
||||
@@ -12,8 +13,48 @@ App::get('/console')
|
||||
->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);
|
||||
});
|
||||
@@ -1028,6 +1028,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', '')
|
||||
],
|
||||
]);
|
||||
}, []);
|
||||
|
||||
@@ -1174,3 +1179,21 @@ App::setResource('servers', function () {
|
||||
|
||||
return $languages;
|
||||
});
|
||||
|
||||
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;
|
||||
}, []);
|
||||
@@ -17,49 +17,96 @@ $title = $this->getParam('title', '')
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?php echo $title; ?></title>
|
||||
|
||||
<link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink" />
|
||||
<link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink-icons" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500;600;700&display=swap" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body class="theme-dark">
|
||||
<section class="container" style="display: grid; place-items: center; min-height: 100vh;">
|
||||
<div class="u-flex u-flex-vertical u-cross-center" style="width: 100%">
|
||||
<div class="card" style="min-width: 600px; max-width: 100%;">
|
||||
<h1 class="heading-level-2">Error <?php echo $code; ?></h1>
|
||||
<p class="u-bold" style="margin-block-start: 0.5rem;"><?php echo $message; ?></p>
|
||||
<small style="margin-block-start: 4px;display: block;">Error ID: <?php echo $errorID; ?></small>
|
||||
<?php if (!empty($projectURL)) : ?>
|
||||
<hr />
|
||||
<p><a href="<?php echo $this->escape($projectURL); ?>" rel="noopener">Back to <?php echo $projectName; ?></a></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<section>
|
||||
<h1>Error <?php echo $code; ?></h1>
|
||||
|
||||
<p><?php echo $message; ?></p>
|
||||
|
||||
<small>Error ID: <?php echo $errorID; ?></small>
|
||||
|
||||
<?php if (!empty($projectURL)) : ?>
|
||||
<hr />
|
||||
|
||||
<p><a href="<?php echo $this->escape($projectURL); ?>" rel="noopener">Back to <?php echo $projectName; ?></a></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($development) : ?>
|
||||
<div>
|
||||
|
||||
<h2>Error Trace</h2>
|
||||
|
||||
<?php foreach ($trace as $log) : ?>
|
||||
<table>
|
||||
<?php foreach ($log as $key => $value) : ?>
|
||||
<tr>
|
||||
<td style="width: 120px"><?php echo $key; ?></td>
|
||||
<td>
|
||||
<?php if (is_array($value)) : ?>
|
||||
<?php var_dump($value); ?>
|
||||
<?php else : ?>
|
||||
<?php echo $value; ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php if ($development) : ?>
|
||||
<div>
|
||||
<h2>Error Trace</h2>
|
||||
<?php foreach ($trace as $log) : ?>
|
||||
<table>
|
||||
<?php foreach ($log as $key => $value) : ?>
|
||||
<tr>
|
||||
<td style="width: 120px"><?php echo $key; ?></td>
|
||||
<td>
|
||||
<?php if (is_array($value)) : ?>
|
||||
<?php var_dump($value); ?>
|
||||
<?php else : ?>
|
||||
<?php echo $value; ?>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
<?php endforeach; ?>
|
||||
</table>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<style>
|
||||
.with-borders {
|
||||
border-top: 1px solid hsl(var(--color-neutral-150));
|
||||
margin-block-start: 3rem;
|
||||
padding-block-start: 2rem;
|
||||
margin-inline: -2rem;
|
||||
padding-inline: 2rem;
|
||||
|
||||
display: none;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.with-borders[data-active] {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
<div class="with-borders">
|
||||
<a class="button">Go to console</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="u-gap-4 u-flex u-main-center u-cross-center" style="margin-block-start: 2rem;">
|
||||
<span class="text">Powered by</span>
|
||||
<a href="https://appwrite.io/" target="_blank" style="display: grid;place-items: center;">
|
||||
<svg width="132" height="34" viewBox="0 0 132 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M30.3916 17.6788C30.3916 22.216 26.7133 25.8941 22.1756 25.8941C20.0679 25.8941 18.1458 25.1004 16.6913 23.796C19.0212 22.7471 20.6433 20.4053 20.6433 17.6847C20.6433 14.9606 19.0172 12.6164 16.6824 11.5695C18.1381 10.2604 20.0636 9.46356 22.1756 9.46356C26.7133 9.46356 30.3916 13.1416 30.3916 17.6788Z" fill="#A3A8BD"></path>
|
||||
<path d="M18.8914 11.1213C13.8456 7.30847 6.5638 10.387 5.78442 16.6629C5.42225 19.5797 6.64589 22.4677 8.99326 24.2365C14.0419 28.0459 21.3216 24.9621 22.0967 18.6856C22.4563 15.7725 21.2344 12.8892 18.8914 11.1213V11.1213ZM17.9776 12.3319C22.0943 15.438 21.3042 21.835 16.5556 23.8467C14.352 24.7803 11.8175 24.4674 9.90703 23.0258C5.79061 19.9196 6.58046 13.5226 11.329 11.511C13.5327 10.5776 16.0672 10.8904 17.9776 12.3319Z" fill="#A3A8BD"></path>
|
||||
<path d="M39.8456 11.6498C40.9732 11.6498 41.9342 11.9416 42.7289 12.5255C43.5236 13.1095 44.07 13.8834 44.3683 14.8473V11.7912H45.7751V22.9159H44.3683V19.8599C44.07 20.8247 43.5236 21.599 42.7289 22.1829C41.9342 22.7668 40.9732 23.0588 39.8456 23.0588C38.8404 23.0588 37.9405 22.8278 37.1457 22.3661C36.3511 21.9045 35.7331 21.2422 35.2915 20.3793C34.8501 19.5166 34.6292 18.5079 34.6292 17.3535C34.6292 16.1858 34.8501 15.1705 35.2915 14.3077C35.7331 13.4457 36.3477 12.7873 37.1357 12.3321C37.9235 11.8771 38.8268 11.6498 39.8456 11.6498ZM40.2123 12.9123C38.9625 12.9123 37.9607 13.3095 37.2064 14.1042C36.4531 14.8989 36.0764 15.982 36.0764 17.3535C36.0764 18.7253 36.4531 19.8085 37.2064 20.6031C37.9607 21.3976 38.9625 21.7948 40.2123 21.7948C41 21.7948 41.7098 21.6117 42.3409 21.245C42.9728 20.8787 43.4687 20.359 43.8286 19.6867C44.1884 19.0143 44.3683 18.2367 44.3683 17.3535C44.3683 16.4705 44.1884 15.6934 43.8286 15.0216C43.4687 14.3495 42.9728 13.8294 42.3409 13.4623C41.7098 13.0956 41 12.9123 40.2123 12.9123ZM54.8213 11.6498C55.8401 11.6498 56.7401 11.8771 57.5211 12.3321C58.3023 12.7873 58.9137 13.4457 59.3552 14.3077C59.7967 15.1705 60.0174 16.1858 60.0174 17.3535C60.0174 18.5079 59.7967 19.5166 59.3552 20.3793C58.9137 21.2422 58.2986 21.9045 57.5111 22.3661C56.7232 22.8278 55.8265 23.0588 54.8213 23.0588C53.6801 23.0588 52.7158 22.7668 51.9279 22.1829C51.14 21.599 50.5969 20.8247 50.2985 19.8599V28.1725H48.8715V11.7912H50.2985V14.8473C50.5969 13.8834 51.14 13.1095 51.9279 12.5255C52.7158 11.9416 53.6801 11.6498 54.8213 11.6498ZM54.4343 12.9123C53.6465 12.9123 52.9366 13.0956 52.3045 13.4623C51.6734 13.8294 51.1812 14.3495 50.8282 15.0216C50.4752 15.6934 50.2985 16.4705 50.2985 17.3535C50.2985 18.2367 50.4752 19.0143 50.8282 19.6867C51.1812 20.359 51.6734 20.8787 52.3045 21.245C52.9366 21.6117 53.6465 21.7948 54.4343 21.7948C55.6975 21.7948 56.7028 21.3976 57.4503 20.6031C58.197 19.8085 58.5702 18.7253 58.5702 17.3535C58.5702 15.982 58.197 14.8989 57.4503 14.1042C56.7028 13.3095 55.6975 12.9123 54.4343 12.9123ZM68.5338 11.6498C69.5527 11.6498 70.4527 11.8771 71.2337 12.3321C72.0149 12.7873 72.6264 13.4457 73.068 14.3077C73.5093 15.1705 73.7302 16.1858 73.7302 17.3535C73.7302 18.5079 73.5093 19.5166 73.068 20.3793C72.6264 21.2422 72.0115 21.9045 71.2237 22.3661C70.4359 22.8278 69.5393 23.0588 68.5338 23.0588C67.3928 23.0588 66.4285 22.7668 65.6407 22.1829C64.8527 21.599 64.3097 20.8247 64.0114 19.8599V28.1725H62.5843V11.7912H64.0114V14.8473C64.3097 13.8834 64.8527 13.1095 65.6407 12.5255C66.4285 11.9416 67.3928 11.6498 68.5338 11.6498ZM68.1472 12.9123C67.3594 12.9123 66.6492 13.0956 66.0173 13.4623C65.3862 13.8294 64.8941 14.3495 64.5409 15.0216C64.1879 15.6934 64.0114 16.4705 64.0114 17.3535C64.0114 18.2367 64.1879 19.0143 64.5409 19.6867C64.8941 20.359 65.3862 20.8787 66.0173 21.245C66.6492 21.6117 67.3594 21.7948 68.1472 21.7948C69.4103 21.7948 70.4156 21.3976 71.1629 20.6031C71.9097 19.8085 72.283 18.7253 72.283 17.3535C72.283 15.982 71.9097 14.8989 71.1629 14.1042C70.4156 13.3095 69.4103 12.9123 68.1472 12.9123ZM89.5209 11.6295H92.2108L89.1746 22.9159H85.9957L83.5714 14.7058L81.0851 22.9159H77.9074L74.8915 11.6295H77.7646L79.5987 20.3491L82.1038 11.6295H85.1399L87.6667 20.3491L89.5209 11.6295ZM96.5102 13.7882C96.9307 13.0687 97.4874 12.5017 98.18 12.0871C98.8735 11.6734 99.6478 11.4666 100.503 11.4666V14.5427H99.6678C97.5627 14.5427 96.5102 15.5205 96.5102 17.476V22.9159H93.6373V11.6295H96.5102V13.7882ZM101.767 8.38884C101.767 7.92721 101.93 7.55055 102.256 7.25894C102.582 6.96675 103.01 6.8205 103.539 6.8205C104.069 6.8205 104.497 6.96675 104.823 7.25894C105.149 7.55055 105.312 7.92721 105.312 8.38884C105.312 8.83718 105.149 9.20749 104.823 9.49993C104.497 9.7922 104.069 9.93846 103.539 9.93846C103.01 9.93846 102.582 9.7922 102.256 9.49993C101.93 9.20749 101.767 8.83718 101.767 8.38884ZM102.093 11.6295H104.966V22.9159H102.093V11.6295ZM112.485 20.4502H113.605V22.9159H112.097C110.833 22.9159 109.866 22.6106 109.193 21.9997C108.521 21.3879 108.185 20.3694 108.185 18.9436V14.0536H106.616V11.6295H108.185V8.8376H111.058V11.6295H113.584V14.0536H111.058V18.984C111.058 19.5274 111.166 19.908 111.384 20.1253C111.601 20.3419 111.968 20.4502 112.485 20.4502ZM126.278 16.8443C126.278 17.1298 126.245 17.456 126.177 17.8224H117.904C117.945 18.8276 118.216 19.5679 118.72 20.0432C119.222 20.5184 119.847 20.7561 120.594 20.7561C121.26 20.7561 121.813 20.5931 122.254 20.2669C122.695 19.9416 122.977 19.5006 123.099 18.9436H126.135C125.986 19.7314 125.667 20.4374 125.179 21.062C124.689 21.6872 124.061 22.1763 123.294 22.5293C122.526 22.8822 121.674 23.0588 120.737 23.0588C119.636 23.0588 118.658 22.8246 117.802 22.3561C116.947 21.8875 116.281 21.2185 115.806 20.3491C115.33 19.4796 115.092 18.454 115.092 17.2727C115.092 16.0906 115.33 15.0618 115.806 14.1864C116.281 13.31 116.947 12.6408 117.802 12.1794C118.658 11.7176 119.636 11.4868 120.737 11.4868C121.85 11.4868 122.825 11.7176 123.66 12.1794C1Line truncated
|
||||
<path d="M14.1624 14.8366C14.1527 14.8608 14.0268 15.3542 13.8918 15.9395C13.7513 16.5247 13.5288 17.4485 13.4032 17.9949C13.1613 18.9961 13.0161 19.6539 13.0161 19.7409C13.0161 19.7649 13.1662 19.7845 13.3498 19.7845H13.6837L13.8335 19.1169C13.9207 18.7543 14.1143 17.9223 14.2689 17.2694C14.4237 16.6165 14.6122 15.8233 14.6849 15.5039C14.7574 15.1847 14.83 14.8948 14.8445 14.8608C14.859 14.8173 14.7767 14.803 14.5251 14.803C14.3365 14.803 14.172 14.8173 14.1624 14.8366ZM11.565 16.9307L11.1201 17.4145L11.2509 17.5693C11.3232 17.6562 11.5218 17.874 11.6909 18.0531L12.0006 18.3817H12.8809L12.4648 17.932C12.2373 17.6904 12.0487 17.458 12.0487 17.4291C12.0487 17.3952 12.2229 17.1777 12.4359 16.9454C12.6486 16.7086 12.8228 16.5052 12.8228 16.4811C12.8228 16.4616 12.639 16.4472 12.4166 16.4472H12.0151L11.565 16.9307ZM14.9507 16.4762C14.9507 16.4908 15.033 16.5825 15.1345 16.6841C15.512 17.0614 15.7779 17.3808 15.7633 17.4434C15.7538 17.4775 15.5698 17.7047 15.3474 17.9416L14.9461 18.3817H15.3958L15.8456 18.377L16.2565 17.9271C16.4841 17.6758 16.6678 17.453 16.6678 17.4242C16.6678 17.4001 16.4744 17.1727 16.2324 16.9165L15.7975 16.4472H15.3764C15.1394 16.4472 14.9507 16.4616 14.9507 16.4762Z" fill="#A3A8BD"></path>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const queryString = window.location.search;
|
||||
const urlParams = new URLSearchParams(queryString);
|
||||
|
||||
const state = JSON.parse(urlParams.get('state') ?? '{}');
|
||||
const failure = state?.failure ?? '';
|
||||
|
||||
if (failure) {
|
||||
const el = document.querySelector('.with-borders');
|
||||
el.setAttribute('data-active', 'true');
|
||||
const anchorEl = el.querySelector('a');
|
||||
anchorEl.setAttribute('href', failure);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -219,8 +219,6 @@ class BuildsV1 extends Worker
|
||||
|
||||
Console::success("Build id: $buildId created");
|
||||
|
||||
$function->setAttribute('scheduleUpdatedAt', DateTime::now());
|
||||
|
||||
/** Set auto deploy */
|
||||
if ($deployment->getAttribute('activate') === true) {
|
||||
$function->setAttribute('deploymentInternalId', $deployment->getInternalId());
|
||||
@@ -231,7 +229,7 @@ class BuildsV1 extends Worker
|
||||
/** Update function schedule */
|
||||
$dbForConsole = $this->getConsoleDB();
|
||||
$schedule = $dbForConsole->getDocument('schedules', $function->getAttribute('scheduleId'));
|
||||
$schedule->setAttribute('resourceUpdatedAt', $function->getAttribute('scheduleUpdatedAt'));
|
||||
$schedule->setAttribute('resourceUpdatedAt', DateTime::now());
|
||||
|
||||
$schedule
|
||||
->setAttribute('schedule', $function->getAttribute('schedule'))
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php clear-card-cache $@
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php patch-create-missing-schedules $@
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
php /usr/src/code/app/cli.php patch-delete-schedule-updated-at-attribute $@
|
||||
@@ -44,10 +44,10 @@
|
||||
"appwrite/php-clamav": "1.1.*",
|
||||
"appwrite/php-runtimes": "0.11.*",
|
||||
"utopia-php/abuse": "0.16.*",
|
||||
"utopia-php/analytics": "0.2.*",
|
||||
"utopia-php/analytics": "0.10.2",
|
||||
"utopia-php/audit": "0.17.*",
|
||||
"utopia-php/cache": "0.8.*",
|
||||
"utopia-php/cli": "0.14.*",
|
||||
"utopia-php/cli": "0.15.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "0.28.*",
|
||||
"utopia-php/queue": "0.5.*",
|
||||
@@ -73,8 +73,7 @@
|
||||
"phpmailer/phpmailer": "6.6.0",
|
||||
"chillerlan/php-qrcode": "4.3.3",
|
||||
"adhocore/jwt": "1.1.2",
|
||||
"slickdeals/statsd": "3.1.0",
|
||||
"league/csv": "^9.0.0"
|
||||
"slickdeals/statsd": "3.1.0"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
|
||||
@@ -145,7 +145,6 @@ services:
|
||||
- _APP_SMTP_SECURE
|
||||
- _APP_SMTP_USERNAME
|
||||
- _APP_SMTP_PASSWORD
|
||||
- _APP_HAMSTER_RECIPIENTS
|
||||
- _APP_USAGE_STATS
|
||||
- _APP_INFLUXDB_HOST
|
||||
- _APP_INFLUXDB_PORT
|
||||
@@ -176,6 +175,8 @@ services:
|
||||
- _APP_SMS_PROVIDER
|
||||
- _APP_SMS_FROM
|
||||
- _APP_REGION
|
||||
- _APP_CONSOLE_GITHUB_APP_ID
|
||||
- _APP_CONSOLE_GITHUB_SECRET
|
||||
|
||||
appwrite-realtime:
|
||||
entrypoint: realtime
|
||||
@@ -585,8 +586,9 @@ services:
|
||||
- _APP_CONNECTIONS_DB_CONSOLE
|
||||
- _APP_CONNECTIONS_DB_PROJECT
|
||||
- _APP_CONNECTIONS_CACHE
|
||||
- _APP_HAMSTER_RECIPIENTS
|
||||
- _APP_HAMSTER_INTERVAL
|
||||
- _APP_HAMSTER_TIME
|
||||
- _APP_MIXPANEL_TOKEN
|
||||
|
||||
appwrite-maintenance:
|
||||
entrypoint: maintenance
|
||||
|
||||
|
After Width: | Height: | Size: 539 KiB |
|
After Width: | Height: | Size: 358 KiB |
|
After Width: | Height: | Size: 359 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 500 KiB |
|
After Width: | Height: | Size: 353 KiB |
|
After Width: | Height: | Size: 193 KiB |
|
After Width: | Height: | Size: 513 B |
|
After Width: | Height: | Size: 599 B |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 237 KiB |
|
After Width: | Height: | Size: 237 KiB |
|
After Width: | Height: | Size: 257 KiB |
|
After Width: | Height: | Size: 329 KiB |
|
After Width: | Height: | Size: 306 KiB |
|
After Width: | Height: | Size: 215 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 187 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 344 KiB |
|
After Width: | Height: | Size: 311 KiB |
|
After Width: | Height: | Size: 230 KiB |
|
After Width: | Height: | Size: 270 KiB |
|
After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 263 KiB |
@@ -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;
|
||||
|
||||
@@ -13,6 +13,8 @@ use Appwrite\Platform\Tasks\SDKs;
|
||||
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;
|
||||
@@ -33,6 +35,8 @@ 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())
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Appwrite\Network\Validator\Origin;
|
||||
use Exception;
|
||||
use Utopia\App;
|
||||
use Utopia\Platform\Action;
|
||||
@@ -10,53 +11,36 @@ use Utopia\CLI\Console;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use League\Csv\Writer;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use Utopia\Analytics\Adapter\Mixpanel;
|
||||
use Utopia\Analytics\Event;
|
||||
use Utopia\Config\Config;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Pools\Group;
|
||||
use Utopia\Registry\Registry;
|
||||
|
||||
class Hamster extends Action
|
||||
{
|
||||
private array $columns = [
|
||||
'Project ID',
|
||||
'Project Name',
|
||||
'Functions',
|
||||
'Deployments',
|
||||
'Members',
|
||||
'Domains',
|
||||
'Files',
|
||||
'Buckets',
|
||||
'Databases',
|
||||
'Documents',
|
||||
'Collections',
|
||||
'Storage',
|
||||
'Requests',
|
||||
'Bandwidth',
|
||||
'Users',
|
||||
'Sessions',
|
||||
'Executions'
|
||||
];
|
||||
|
||||
private array $usageStats = [
|
||||
'Files' => 'files.$all.count.total',
|
||||
'Buckets' => 'buckets.$all.count.total',
|
||||
'Databases' => 'databases.$all.count.total',
|
||||
'Documents' => 'documents.$all.count.total',
|
||||
'Collections' => 'collections.$all.count.total',
|
||||
'Storage' => 'project.$all.storage.size',
|
||||
'Requests' => 'project.$all.network.requests',
|
||||
'Bandwidth' => 'project.$all.network.bandwidth',
|
||||
'Users' => 'users.$all.count.total',
|
||||
'Sessions' => 'sessions.$all.requests.create',
|
||||
'Executions' => 'executions.$all.compute.total',
|
||||
private array $metrics = [
|
||||
'usage_files' => 'files.$all.count.total',
|
||||
'usage_buckets' => 'buckets.$all.count.total',
|
||||
'usage_databases' => 'databases.$all.count.total',
|
||||
'usage_documents' => 'documents.$all.count.total',
|
||||
'usage_collections' => 'collections.$all.count.total',
|
||||
'usage_storage' => 'project.$all.storage.size',
|
||||
'usage_requests' => 'project.$all.network.requests',
|
||||
'usage_bandwidth' => 'project.$all.network.bandwidth',
|
||||
'usage_users' => 'users.$all.count.total',
|
||||
'usage_sessions' => 'sessions.email.requests.create',
|
||||
'usage_executions' => 'executions.$all.compute.total',
|
||||
];
|
||||
|
||||
protected string $directory = '/usr/local';
|
||||
|
||||
protected string $path;
|
||||
|
||||
protected string $date;
|
||||
|
||||
protected Mixpanel $mixpanel;
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'hamster';
|
||||
@@ -64,166 +48,254 @@ class Hamster extends Action
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->mixpanel = new Mixpanel(App::getEnv('_APP_MIXPANEL_TOKEN', ''));
|
||||
|
||||
$this
|
||||
->desc('Get stats for projects')
|
||||
->inject('register')
|
||||
->inject('pools')
|
||||
->inject('cache')
|
||||
->inject('dbForConsole')
|
||||
->callback(function (Registry $register, Group $pools, Cache $cache, Database $dbForConsole) {
|
||||
$this->action($register, $pools, $cache, $dbForConsole);
|
||||
->callback(function (Group $pools, Cache $cache, Database $dbForConsole) {
|
||||
$this->action($pools, $cache, $dbForConsole);
|
||||
});
|
||||
}
|
||||
|
||||
private function getStats(Database $dbForConsole, Database $dbForProject, Document $project): array
|
||||
private function getStatsPerProject(Group $pools, Cache $cache, Database $dbForConsole)
|
||||
{
|
||||
$stats = [];
|
||||
$this->calculateByGroup('projects', $dbForConsole, function (Database $dbForConsole, Document $project) use ($pools, $cache) {
|
||||
/**
|
||||
* Skip user projects with id 'console'
|
||||
*/
|
||||
if ($project->getId() === 'console') {
|
||||
Console::info("Skipping project console");
|
||||
return;
|
||||
}
|
||||
|
||||
/** Get Project ID */
|
||||
$stats['Project ID'] = $project->getId();
|
||||
Console::log("Getting stats for {$project->getId()}");
|
||||
|
||||
/** Get Project Name */
|
||||
$stats['Project Name'] = $project->getAttribute('name');
|
||||
try {
|
||||
$db = $project->getAttribute('database');
|
||||
$adapter = $pools
|
||||
->get($db)
|
||||
->pop()
|
||||
->getResource();
|
||||
|
||||
/** Get Total Functions */
|
||||
$stats['Functions'] = $dbForProject->count('functions', [], APP_LIMIT_COUNT);
|
||||
$dbForProject = new Database($adapter, $cache);
|
||||
$dbForProject->setDefaultDatabase('appwrite');
|
||||
$dbForProject->setNamespace('_' . $project->getInternalId());
|
||||
|
||||
/** Get Total Deployments */
|
||||
$stats['Deployments'] = $dbForProject->count('deployments', [], APP_LIMIT_COUNT);
|
||||
$statsPerProject = [];
|
||||
|
||||
/** Get Total Members */
|
||||
$teamInternalId = $project->getAttribute('teamInternalId', null);
|
||||
if ($teamInternalId) {
|
||||
$stats['Members'] = $dbForConsole->count('memberships', [
|
||||
Query::equal('teamInternalId', [$teamInternalId])
|
||||
], APP_LIMIT_COUNT);
|
||||
} else {
|
||||
$stats['Members'] = 0;
|
||||
}
|
||||
$statsPerProject['time'] = microtime(true);
|
||||
|
||||
/** Get Domains */
|
||||
$stats['Domains'] = $dbForProject->count('domains', [], APP_LIMIT_COUNT);
|
||||
/** Get Project ID */
|
||||
$statsPerProject['project_id'] = $project->getId();
|
||||
|
||||
/** Get Usage stats */
|
||||
$range = '90d';
|
||||
$periods = [
|
||||
'90d' => [
|
||||
'period' => '1d',
|
||||
'limit' => 90,
|
||||
],
|
||||
];
|
||||
/** Get project created time */
|
||||
$statsPerProject['project_created'] = $project->getAttribute('$createdAt');
|
||||
|
||||
$metrics = array_values($this->usageStats);
|
||||
Authorization::skip(function () use ($dbForProject, $periods, $range, $metrics, &$stats) {
|
||||
foreach ($metrics as $metric) {
|
||||
$limit = $periods[$range]['limit'];
|
||||
$period = $periods[$range]['period'];
|
||||
/** Get Project Name */
|
||||
$statsPerProject['project_name'] = $project->getAttribute('name');
|
||||
|
||||
$requestDocs = $dbForProject->find('stats', [
|
||||
Query::equal('period', [$period]),
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::limit($limit),
|
||||
Query::orderDesc('time'),
|
||||
]);
|
||||
/** Get Total Functions */
|
||||
$statsPerProject['custom_functions'] = $dbForProject->count('functions', [], APP_LIMIT_COUNT);
|
||||
|
||||
$stats[$metric] = [];
|
||||
foreach ($requestDocs as $requestDoc) {
|
||||
$stats[$metric][] = [
|
||||
'value' => $requestDoc->getAttribute('value'),
|
||||
'date' => $requestDoc->getAttribute('time'),
|
||||
];
|
||||
foreach (\array_keys(Config::getParam('runtimes')) as $runtime) {
|
||||
$statsPerProject['custom_functions_' . $runtime] = $dbForProject->count('functions', [
|
||||
Query::equal('runtime', [$runtime]),
|
||||
], APP_LIMIT_COUNT);
|
||||
}
|
||||
|
||||
$stats[$metric] = array_reverse($stats[$metric]);
|
||||
// Calculate aggregate of each metric
|
||||
$stats[$metric] = array_sum(array_column($stats[$metric], 'value'));
|
||||
/** Get Total Deployments */
|
||||
$statsPerProject['custom_deployments'] = $dbForProject->count('deployments', [], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get Total Teams */
|
||||
$statsPerProject['custom_teams'] = $dbForProject->count('teams', [], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get Total Members */
|
||||
$teamInternalId = $project->getAttribute('teamInternalId', null);
|
||||
if ($teamInternalId) {
|
||||
$statsPerProject['custom_organization_members'] = $dbForConsole->count('memberships', [
|
||||
Query::equal('teamInternalId', [$teamInternalId])
|
||||
], APP_LIMIT_COUNT);
|
||||
} else {
|
||||
$statsPerProject['custom_organization_members'] = 0;
|
||||
}
|
||||
|
||||
/** Get Email and Name of the project owner */
|
||||
if ($teamInternalId) {
|
||||
$membership = $dbForConsole->findOne('memberships', [
|
||||
Query::equal('teamInternalId', [$teamInternalId]),
|
||||
]);
|
||||
|
||||
if (!$membership || $membership->isEmpty()) {
|
||||
throw new Exception('Membership not found. Skipping project : ' . $project->getId());
|
||||
}
|
||||
|
||||
$userInternalId = $membership->getAttribute('userInternalId', null);
|
||||
if ($userInternalId) {
|
||||
$user = $dbForConsole->findOne('users', [
|
||||
Query::equal('_id', [$userInternalId]),
|
||||
]);
|
||||
|
||||
$statsPerProject['email'] = $user->getAttribute('email', null);
|
||||
$statsPerProject['name'] = $user->getAttribute('name', null);
|
||||
}
|
||||
}
|
||||
|
||||
/** Get Domains */
|
||||
$statsPerProject['custom_domains'] = $dbForProject->count('domains', [], APP_LIMIT_COUNT);
|
||||
|
||||
/** Get Platforms */
|
||||
$platforms = $dbForConsole->find('platforms', [
|
||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_COUNT)
|
||||
]);
|
||||
|
||||
$statsPerProject['custom_platforms_web'] = sizeof(array_filter($platforms, function ($platform) {
|
||||
return $platform['type'] === 'web';
|
||||
}));
|
||||
|
||||
$statsPerProject['custom_platforms_android'] = sizeof(array_filter($platforms, function ($platform) {
|
||||
return $platform['type'] === 'android';
|
||||
}));
|
||||
|
||||
$statsPerProject['custom_platforms_apple'] = sizeof(array_filter($platforms, function ($platform) {
|
||||
return str_contains($platform['type'], 'apple');
|
||||
}));
|
||||
|
||||
$statsPerProject['custom_platforms_flutter'] = sizeof(array_filter($platforms, function ($platform) {
|
||||
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' => [
|
||||
'period' => '1d',
|
||||
'limit' => 90,
|
||||
],
|
||||
'24h' => [
|
||||
'period' => '1h',
|
||||
'limit' => 24,
|
||||
],
|
||||
];
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $periods, &$statsPerProject) {
|
||||
foreach ($this->metrics as $key => $metric) {
|
||||
foreach ($periods as $periodKey => $periodValue) {
|
||||
$limit = $periodValue['limit'];
|
||||
$period = $periodValue['period'];
|
||||
|
||||
$requestDocs = $dbForProject->find('stats', [
|
||||
Query::equal('period', [$period]),
|
||||
Query::equal('metric', [$metric]),
|
||||
Query::limit($limit),
|
||||
Query::orderDesc('time'),
|
||||
]);
|
||||
|
||||
$statsPerProject[$key . '_' . $periodKey] = [];
|
||||
foreach ($requestDocs as $requestDoc) {
|
||||
$statsPerProject[$key . '_' . $periodKey][] = [
|
||||
'value' => $requestDoc->getAttribute('value'),
|
||||
'date' => $requestDoc->getAttribute('time'),
|
||||
];
|
||||
}
|
||||
|
||||
$statsPerProject[$key . '_' . $periodKey] = array_reverse($statsPerProject[$key . '_' . $periodKey]);
|
||||
// Calculate aggregate of each metric
|
||||
$statsPerProject[$key . '_' . $periodKey] = array_sum(array_column($statsPerProject[$key . '_' . $periodKey], 'value'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (isset($statsPerProject['email'])) {
|
||||
/** Send data to mixpanel */
|
||||
$res = $this->mixpanel->createProfile($statsPerProject['email'], '', [
|
||||
'name' => $statsPerProject['name'],
|
||||
'email' => $statsPerProject['email']
|
||||
]);
|
||||
|
||||
if (!$res) {
|
||||
Console::error('Failed to create user profile for project: ' . $project->getId());
|
||||
}
|
||||
|
||||
$event = new Event();
|
||||
$event
|
||||
->setName('Project Daily Usage')
|
||||
->setProps($statsPerProject);
|
||||
$res = $this->mixpanel->createEvent($event);
|
||||
if (!$res) {
|
||||
Console::error('Failed to create event for project: ' . $project->getId());
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Console::error('Failed to send stats for project: ' . $project->getId());
|
||||
Console::error($e->getMessage());
|
||||
} finally {
|
||||
$pools
|
||||
->get($db)
|
||||
->reclaim();
|
||||
}
|
||||
});
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
public function action(Registry $register, Group $pools, Cache $cache, Database $dbForConsole): void
|
||||
public function action(Group $pools, Cache $cache, Database $dbForConsole): void
|
||||
{
|
||||
|
||||
Console::title('Cloud Hamster V1');
|
||||
Console::success(APP_NAME . ' cloud hamster process v1 has started');
|
||||
Console::success(APP_NAME . ' cloud hamster process has started');
|
||||
|
||||
$interval = (int) App::getEnv('_APP_HAMSTER_INTERVAL', '30'); // 30 seconds (by default)
|
||||
$sleep = (int) App::getEnv('_APP_HAMSTER_INTERVAL', '30'); // 30 seconds (by default)
|
||||
|
||||
Console::loop(function () use ($register, $pools, $cache, $dbForConsole, $interval) {
|
||||
$jobInitTime = App::getEnv('_APP_HAMSTER_TIME', '22:00'); // (hour:minutes)
|
||||
$now = new \DateTime();
|
||||
$now->setTimezone(new \DateTimeZone(date_default_timezone_get()));
|
||||
$next = new \DateTime($now->format("Y-m-d $jobInitTime"));
|
||||
$next->setTimezone(new \DateTimeZone(date_default_timezone_get()));
|
||||
$delay = $next->getTimestamp() - $now->getTimestamp();
|
||||
|
||||
/**
|
||||
* If time passed for the target day.
|
||||
*/
|
||||
if ($delay <= 0) {
|
||||
$next->add(\DateInterval::createFromDateString('1 days'));
|
||||
$delay = $next->getTimestamp() - $now->getTimestamp();
|
||||
}
|
||||
|
||||
Console::log('[' . $now->format("Y-m-d H:i:s.v") . '] Delaying for ' . $delay . ' setting loop to [' . $next->format("Y-m-d H:i:s.v") . ']');
|
||||
|
||||
Console::loop(function () use ($pools, $cache, $dbForConsole, $sleep) {
|
||||
$now = date('d-m-Y H:i:s', time());
|
||||
Console::info("[{$now}] Getting Cloud Usage Stats every {$interval} seconds");
|
||||
Console::info("[{$now}] Getting Cloud Usage Stats every {$sleep} seconds");
|
||||
$loopStart = microtime(true);
|
||||
|
||||
/* Initialise new Utopia app */
|
||||
$app = new App('UTC');
|
||||
$console = $app->getResource('console');
|
||||
|
||||
/** CSV stuff */
|
||||
$this->date = date('Y-m-d');
|
||||
$this->path = "{$this->directory}/stats_{$this->date}.csv";
|
||||
$csv = Writer::createFromPath($this->path, 'w');
|
||||
$csv->insertOne($this->columns);
|
||||
Console::info('Getting stats for all projects');
|
||||
$this->getStatsPerProject($pools, $cache, $dbForConsole);
|
||||
Console::success('Completed getting stats for all projects');
|
||||
|
||||
/** Database connections */
|
||||
$totalProjects = $dbForConsole->count('projects') + 1;
|
||||
Console::success("Found a total of: {$totalProjects} projects");
|
||||
Console::info('Getting stats for all organizations');
|
||||
$this->getStatsPerOrganization($dbForConsole);
|
||||
Console::success('Completed getting stats for all organizations');
|
||||
|
||||
$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());
|
||||
|
||||
$statsPerProject = $this->getStats($dbForConsole, $dbForProject, $project);
|
||||
$csv->insertOne(array_values($statsPerProject));
|
||||
} catch (\Throwable $th) {
|
||||
throw $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 . '/' . $totalProjects . ' projects...');
|
||||
}
|
||||
|
||||
$this->sendEmail($register);
|
||||
Console::info('Getting stats for all users');
|
||||
$this->getStatsPerUser($dbForConsole);
|
||||
Console::success('Completed getting stats for all users');
|
||||
|
||||
$pools
|
||||
->get('console')
|
||||
@@ -232,40 +304,138 @@ class Hamster extends Action
|
||||
$loopTook = microtime(true) - $loopStart;
|
||||
$now = date('d-m-Y H:i:s', time());
|
||||
Console::info("[{$now}] Cloud Stats took {$loopTook} seconds");
|
||||
}, $interval);
|
||||
}, $sleep, $delay);
|
||||
}
|
||||
|
||||
private function sendEmail(Registry $register)
|
||||
protected function calculateByGroup(string $collection, Database $dbForConsole, callable $callback)
|
||||
{
|
||||
/** @var \PHPMailer\PHPMailer\PHPMailer $mail */
|
||||
$mail = $register->get('smtp');
|
||||
$count = 0;
|
||||
$chunk = 0;
|
||||
$limit = 50;
|
||||
$results = [];
|
||||
$sum = $limit;
|
||||
|
||||
$mail->clearAddresses();
|
||||
$mail->clearAllRecipients();
|
||||
$mail->clearReplyTos();
|
||||
$mail->clearAttachments();
|
||||
$mail->clearBCCs();
|
||||
$mail->clearCCs();
|
||||
$executionStart = \microtime(true);
|
||||
|
||||
try {
|
||||
/** Addresses */
|
||||
$mail->setFrom(App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), 'Appwrite Cloud Hamster');
|
||||
$recipients = explode(',', App::getEnv('_APP_HAMSTER_RECIPIENTS', ''));
|
||||
foreach ($recipients as $recipient) {
|
||||
$mail->addAddress($recipient);
|
||||
while ($sum === $limit) {
|
||||
$chunk++;
|
||||
|
||||
$results = $dbForConsole->find($collection, \array_merge([
|
||||
Query::limit($limit),
|
||||
Query::offset($count)
|
||||
]));
|
||||
|
||||
$sum = count($results);
|
||||
|
||||
Console::log('Processing chunk #' . $chunk . '. Found ' . $sum . ' documents');
|
||||
|
||||
foreach ($results as $document) {
|
||||
call_user_func($callback, $dbForConsole, $document);
|
||||
$count++;
|
||||
}
|
||||
|
||||
/** 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}");
|
||||
}
|
||||
|
||||
$executionEnd = \microtime(true);
|
||||
|
||||
Console::log("Processed {$count} document by group in " . ($executionEnd - $executionStart) . " seconds");
|
||||
}
|
||||
|
||||
protected function getStatsPerOrganization(Database $dbForConsole)
|
||||
{
|
||||
|
||||
$this->calculateByGroup('teams', $dbForConsole, function (Database $dbForConsole, Document $document) {
|
||||
try {
|
||||
$statsPerOrganization = [];
|
||||
|
||||
/** Organization name */
|
||||
$statsPerOrganization['name'] = $document->getAttribute('name');
|
||||
|
||||
/** Get Email and of the organization owner */
|
||||
$membership = $dbForConsole->findOne('memberships', [
|
||||
Query::equal('teamInternalId', [$document->getInternalId()]),
|
||||
]);
|
||||
|
||||
if (!$membership || $membership->isEmpty()) {
|
||||
throw new Exception('Membership not found. Skipping organization : ' . $document->getId());
|
||||
}
|
||||
|
||||
$userInternalId = $membership->getAttribute('userInternalId', null);
|
||||
if ($userInternalId) {
|
||||
$user = $dbForConsole->findOne('users', [
|
||||
Query::equal('_id', [$userInternalId]),
|
||||
]);
|
||||
|
||||
$statsPerOrganization['email'] = $user->getAttribute('email', null);
|
||||
}
|
||||
|
||||
/** Organization Creation Date */
|
||||
$statsPerOrganization['created'] = $document->getAttribute('$createdAt');
|
||||
|
||||
/** Number of team members */
|
||||
$statsPerOrganization['members'] = $document->getAttribute('total');
|
||||
|
||||
/** Number of projects in this organization */
|
||||
$statsPerOrganization['projects'] = $dbForConsole->count('projects', [
|
||||
Query::equal('teamId', [$document->getId()]),
|
||||
Query::limit(APP_LIMIT_COUNT)
|
||||
]);
|
||||
|
||||
if (!isset($statsPerOrganization['email'])) {
|
||||
throw new Exception('Email not found. Skipping organization : ' . $document->getId());
|
||||
}
|
||||
|
||||
$event = new Event();
|
||||
$event
|
||||
->setName('Organization Daily Usage')
|
||||
->setProps($statsPerOrganization);
|
||||
$res = $this->mixpanel->createEvent($event);
|
||||
if (!$res) {
|
||||
throw new Exception('Failed to create event for organization : ' . $document->getId());
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Console::error($e->getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected function getStatsPerUser(Database $dbForConsole)
|
||||
{
|
||||
$this->calculateByGroup('users', $dbForConsole, function (Database $dbForConsole, Document $document) {
|
||||
try {
|
||||
$statsPerUser = [];
|
||||
|
||||
/** Organization name */
|
||||
$statsPerUser['name'] = $document->getAttribute('name');
|
||||
|
||||
/** Organization ID (needs to be stored as an email since mixpanel uses the email attribute as a distinctID) */
|
||||
$statsPerUser['email'] = $document->getAttribute('email');
|
||||
|
||||
/** Organization Creation Date */
|
||||
$statsPerUser['created'] = $document->getAttribute('$createdAt');
|
||||
|
||||
/** Number of teams this user is a part of */
|
||||
$statsPerUser['memberships'] = $dbForConsole->count('memberships', [
|
||||
Query::equal('userInternalId', [$document->getInternalId()]),
|
||||
Query::limit(APP_LIMIT_COUNT)
|
||||
]);
|
||||
|
||||
if (!isset($statsPerUser['email'])) {
|
||||
throw new Exception('User has no email: ' . $document->getId());
|
||||
}
|
||||
|
||||
/** Send data to mixpanel */
|
||||
$event = new Event();
|
||||
$event
|
||||
->setName('User Daily Usage')
|
||||
->setProps($statsPerUser);
|
||||
$res = $this->mixpanel->createEvent($event);
|
||||
|
||||
if (!$res) {
|
||||
throw new Exception('Failed to create user profile for user: ' . $document->getId());
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Console::error($e->getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\Platform\Tasks;
|
||||
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Pools\Group;
|
||||
|
||||
class PatchDeleteScheduleUpdatedAtAttribute extends Action
|
||||
{
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'patch-delete-schedule-updated-at-attribute';
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this
|
||||
->desc('Ensure function collections do not have scheduleUpdatedAt attribute')
|
||||
->inject('pools')
|
||||
->inject('dbForConsole')
|
||||
->inject('getProjectDB')
|
||||
->callback(fn (Group $pools, Database $dbForConsole, callable $getProjectDB) => $this->action($pools, $dbForConsole, $getProjectDB));
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over every function on every project to make sure there is a schedule. If not, recreate the schedule.
|
||||
*/
|
||||
public function action(Group $pools, Database $dbForConsole, callable $getProjectDB): void
|
||||
{
|
||||
Authorization::disable();
|
||||
Authorization::setDefaultStatus(false);
|
||||
|
||||
Console::title('PatchDeleteScheduleUpdatedAtAttribute V1');
|
||||
Console::success(APP_NAME . ' PatchDeleteScheduleUpdatedAtAttribute v1 has started');
|
||||
|
||||
$limit = 100;
|
||||
$projectCursor = null;
|
||||
while (true) {
|
||||
$projectsQueries = [Query::limit($limit)];
|
||||
if ($projectCursor !== null) {
|
||||
$projectsQueries[] = Query::cursorAfter($projectCursor);
|
||||
}
|
||||
$projects = $dbForConsole->find('projects', $projectsQueries);
|
||||
|
||||
if (count($projects) === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($projects as $project) {
|
||||
Console::log("Checking Project " . $project->getAttribute('name') . " (" . $project->getId() . ")");
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
try {
|
||||
/**
|
||||
* Delete 'scheduleUpdatedAt' attribute
|
||||
*/
|
||||
$dbForProject->deleteAttribute('functions', 'scheduleUpdatedAt');
|
||||
$dbForProject->deleteCachedCollection('functions');
|
||||
Console::success("'scheduleUpdatedAt' deleted.");
|
||||
} catch (\Throwable $th) {
|
||||
Console::warning("'scheduleUpdatedAt' errored: {$th->getMessage()}");
|
||||
}
|
||||
|
||||
$pools->reclaim();
|
||||
}
|
||||
|
||||
$projectCursor = $projects[array_key_last($projects)];
|
||||
}
|
||||
}
|
||||
}
|
||||