Merge remote-tracking branch 'origin/feat-db-pools-master' into feat-db-pools-db-pools-master-sync
3
.env
|
@ -41,6 +41,7 @@ _APP_SMTP_PORT=1025
|
||||||
_APP_SMTP_SECURE=
|
_APP_SMTP_SECURE=
|
||||||
_APP_SMTP_USERNAME=
|
_APP_SMTP_USERNAME=
|
||||||
_APP_SMTP_PASSWORD=
|
_APP_SMTP_PASSWORD=
|
||||||
|
_APP_USERS_STATS_RECIPIENTS=
|
||||||
_APP_HAMSTER_INTERVAL=86400
|
_APP_HAMSTER_INTERVAL=86400
|
||||||
_APP_HAMSTER_TIME=21:00
|
_APP_HAMSTER_TIME=21:00
|
||||||
_APP_MIXPANEL_TOKEN=
|
_APP_MIXPANEL_TOKEN=
|
||||||
|
@ -76,3 +77,5 @@ _APP_GRAPHQL_MAX_DEPTH=3
|
||||||
_APP_REGION=default
|
_APP_REGION=default
|
||||||
_APP_DOCKER_HUB_USERNAME=
|
_APP_DOCKER_HUB_USERNAME=
|
||||||
_APP_DOCKER_HUB_PASSWORD=
|
_APP_DOCKER_HUB_PASSWORD=
|
||||||
|
_APP_CONSOLE_GITHUB_SECRET=
|
||||||
|
_APP_CONSOLE_GITHUB_APP_ID=
|
||||||
|
|
46
.github/workflows/publish.yml
vendored
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
name: "Build and Publish"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- appwrite-*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-publish:
|
||||||
|
name: Build and Publish
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
submodules: recursive
|
||||||
|
ref: feat-db-pools-master
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v4
|
||||||
|
with:
|
||||||
|
images: appwrite/cloud
|
||||||
|
tags: |
|
||||||
|
type=ref,event=tag
|
||||||
|
|
||||||
|
- name: Build & Publish to DockerHub
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64
|
||||||
|
build-args: |
|
||||||
|
VERSION=${{ steps.meta.outputs.version }}
|
||||||
|
VITE_APPWRITE_GROWTH_ENDPOINT=https://growth.appwrite.io/v1
|
||||||
|
VITE_GA_PROJECT=G-L7G2B6PLDS
|
||||||
|
VITE_CONSOLE_MODE=cloud
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
2
.gitmodules
vendored
|
@ -1,4 +1,4 @@
|
||||||
[submodule "app/console"]
|
[submodule "app/console"]
|
||||||
path = app/console
|
path = app/console
|
||||||
url = https://github.com/appwrite/console
|
url = https://github.com/appwrite/console
|
||||||
branch = 2.2.2
|
branch = cloud-cards
|
||||||
|
|
|
@ -29,7 +29,7 @@ ENV VITE_APPWRITE_GROWTH_ENDPOINT=$VITE_APPWRITE_GROWTH_ENDPOINT
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM appwrite/base:0.2.0 as final
|
FROM appwrite/base:0.2.2 as final
|
||||||
|
|
||||||
LABEL maintainer="team@appwrite.io"
|
LABEL maintainer="team@appwrite.io"
|
||||||
|
|
||||||
|
@ -91,6 +91,7 @@ COPY --from=node /usr/local/src/console/build /usr/src/code/console
|
||||||
|
|
||||||
# Add Source Code
|
# Add Source Code
|
||||||
COPY ./app /usr/src/code/app
|
COPY ./app /usr/src/code/app
|
||||||
|
COPY ./public /usr/src/code/public
|
||||||
COPY ./bin /usr/local/bin
|
COPY ./bin /usr/local/bin
|
||||||
COPY ./docs /usr/src/code/docs
|
COPY ./docs /usr/src/code/docs
|
||||||
COPY ./src /usr/src/code/src
|
COPY ./src /usr/src/code/src
|
||||||
|
@ -112,7 +113,11 @@ RUN mkdir -p /storage/uploads && \
|
||||||
# Executables
|
# Executables
|
||||||
RUN chmod +x /usr/local/bin/doctor && \
|
RUN chmod +x /usr/local/bin/doctor && \
|
||||||
chmod +x /usr/local/bin/patch-delete-schedule-updated-at-attribute && \
|
chmod +x /usr/local/bin/patch-delete-schedule-updated-at-attribute && \
|
||||||
chmod +x /usr/local/bin/maintenance && \
|
chmod +x /usr/local/bin/clear-card-cache && \
|
||||||
|
chmod +x /usr/local/bin/calc-users-stats && \
|
||||||
|
chmod +x /usr/local/bin/calc-tier-stats && \
|
||||||
|
chmod +x /usr/local/bin/patch-delete-project-collections && \
|
||||||
|
chmod +x /usr/local/bin/maintenance && \
|
||||||
chmod +x /usr/local/bin/volume-sync && \
|
chmod +x /usr/local/bin/volume-sync && \
|
||||||
chmod +x /usr/local/bin/install && \
|
chmod +x /usr/local/bin/install && \
|
||||||
chmod +x /usr/local/bin/migrate && \
|
chmod +x /usr/local/bin/migrate && \
|
||||||
|
|
|
@ -61,7 +61,7 @@ CLI::setResource('dbForConsole', function ($pools, $cache) {
|
||||||
$dbForConsole->setNamespace('console');
|
$dbForConsole->setNamespace('console');
|
||||||
|
|
||||||
// Ensure tables exist
|
// Ensure tables exist
|
||||||
$collections = Config::getParam('collections', []);
|
$collections = Config::getParam('collections', [])['console'];
|
||||||
$last = \array_key_last($collections);
|
$last = \array_key_last($collections);
|
||||||
|
|
||||||
if (!($dbForConsole->exists($dbForConsole->getDefaultDatabase(), $last))) { /** TODO cache ready variable using registry */
|
if (!($dbForConsole->exists($dbForConsole->getDefaultDatabase(), $last))) { /** TODO cache ready variable using registry */
|
||||||
|
|
1
app/config/cloud/contributors.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[1297371,1759475,6360216,20852629,5857008,19310830,9708641,26739219,23742426,22174310,77877486,1477010,29069505,62933155,45863583,42211,176163,58045728,7091609,31401437,1911066,66096031,22432834,43054051,79051850,22895284,100597998,91385411,49699333,7818620,11004008,54898623,27856297,33250853,49375670,30630364,50206,47822499,7481165,52557347,13681567,20492520,51369094,51821861,50256986,28431370,13692220,24373771,15938422,27148250,14805534,36137226,36071208,40193621,56179878,53281158,48085134,19358691,32528768,19733683,37348419,13719696,18309412,42106787,53618500,5306011,4408379,35486736,18586611,15062564,27011453,56090587,44623032,25107942,81188,33922418,16717633,32809211,69401139,25815659,4104127,20889958,3102249,12476526,2635185,28495651,50957556,2791280,31023616,20955511,835733,471907,69008866,44906587,45271396,76054330,45748739,41908747,23402178,47356149,8921,36632821,3668741,71702982,29725587,26303198,785830,51410502,60089135,2847349,43172716,48546075,8216525,41161981,51828039,4334997,80918302,38534289,47860497,80036766,41341387,49818988,58487637,29237374,46913894,5148229,4377199,29686102,26272249,75117692,4090256,27357868,33062368,38664231,46695441,743291,22633385,6368283,11593067,45097959,43381712,3284228,1972717,33012425,61755381,25405707,3144291,44156359,5497267,7423905,20716175,28586681,5975506,23518097,22187384,24191952,7768078,971530,51240166,55633427,34207400,77061285,11719476,35950229,66742927,34406802,802933,50047839,39148877,26602940,9693472,44273767,19362725,31209978,30521594,686298,6237394,35039730,42580581,36671793,8502129,8466918,81866614,54903252,28373606,13381361,72331432,30694270,5355510,8209163,86675510,9453522,42496309,56145786,2149381,393945,22084723,52621436,8872447,5575392,29619660,5547479,8852116,11151445,4717349,17725274,65615065,18537755,29292618,53044263,26597930,10313411,55998629,77529288,17404636,33729848,19422168,17916404,66111735,10329006,33502846,398230,81643826,105039167,47522632,91655303,9774614,10603631,284924,60857954,22885912,116552306,36103454,794606,27729549,1754457,36594527,13899668,78664749,47406531,27698189,5305654,53345517,6756412,29176704,77790497,47504894,37251540,52361778,52200375,1351177,66022861,73975409,25745396,31433638,37118134,43210805,20317665,11923975,47187468,16362381,36751163,14959876,32362757,65529384,52352285,74085816,3628535,43902034,75667593,26132902,466713,617558,96806061,33605526,11290524,43621940,12446314,17146935,55018955,56096559,79797000,40014186,34449936,58387964,23368207,42414965,44056349,33743031,12294525,58251592,33755729,9021747,932084,11428067,97121933,80122730,60894542,58583793,56051809,32243289,9934371,90936802,74638775,65399526,77604,64524822,47782249,43633955,42793632,55969597,72334601,82395440,92818577,60866204,65016769,23725091,45892107,55308895,86314140,82756460,47685349,63562160,73419211,1613216,50882624,91469717,46166258,60927324,41763158,83607556,2171717,50497814,39427312,61322830,40076195,39419448,29397545,55090719,53259730,20885012,64558515,69677883,55741087,72426535,46033036,68477507,30376878,73700530,25518600,29922887,36229969,47573417,40424087,49054503,16880385,22801227,72848513,64347914,814402,49149679,55017867,49481876,67067955,31439735,63878173,80322286,43746210,17332970,22702905,62476876,89888292,75736952,54059881,90782137,63588969,57111920,63330165,70258211,46371923,17837758,59364507,52203828,60147326,18481195,74822422,9803078,67309607,60410049,47360939,19922556,90848252,24698014,58886915,63579762,96648934,68523530,60518745,37345795,3929651,54993657,52061363,43019989,5787917,94674993,71593494,17143469,10288548,1830380,71510505,59124772,2335145,70798495,46474346,49263351,52062536,63151043,65248303,26071571,53626355,43992469,60785452,63467479,71837281,19490891,58628586,38250310,7271718,1110414,57227290,11625672,85063520,88965873,70096901,42029519,85363195,64471630,69353350,66922161,2221746,100430077,12299813,62690310,68282006,99184676,2450,22989561,22212661,59973863,11232940,76688923,22321353,77732479,84286404,32268377,34828782,23068019,57074509,24620969,20735983,26173690,75809937,49760818,86646105,52617262]
|
33
app/config/cloud/employees.json
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"eldad@appwrite.io": { "memberSince": "2020-10-15", "spot": "0", "gitHub": "eldadfux" },
|
||||||
|
"christy@appwrite.io": { "memberSince": "2020-12-01", "spot": "1", "gitHub": "christyjacob4" },
|
||||||
|
"torsten@appwrite.io": { "memberSince": "2020-12-28", "spot": "2", "gitHub": "torstendittmann" },
|
||||||
|
"damodar@appwrite.io": { "memberSince": "2021-01-02", "spot": "3", "gitHub": "lohanidamodar" },
|
||||||
|
"bradley@appwrite.io": { "memberSince": "2021-05-21", "spot": "5", "gitHub": "PineappleIOnic" },
|
||||||
|
"jake@appwrite.io": { "memberSince": "2021-06-28", "spot": "6", "gitHub": "abnegate" },
|
||||||
|
"sara@appwrite.io": { "memberSince": "2021-08-16", "spot": "7", "gitHub": "sarakaandorp" },
|
||||||
|
"matej@appwrite.io": { "memberSince": "2021-08-23", "spot": "8", "gitHub": "meldiron" },
|
||||||
|
"aditya@appwrite.io": { "memberSince": "2021-09-01", "spot": "9", "gitHub": "adityaoberai" },
|
||||||
|
"wess@appwrite.io": { "memberSince": "2021-11-08", "spot": "12", "gitHub": "wess" },
|
||||||
|
"may@appwrite.io": { "memberSince": "2021-11-28", "spot": "14", "gitHub": "MayEnder" },
|
||||||
|
"elad@appwrite.io": { "memberSince": "2021-12-19", "spot": "15", "gitHub": "elad2412" },
|
||||||
|
"vincent@appwrite.io": { "memberSince": "2022-01-01", "spot": "16", "gitHub": "gewenyu99" },
|
||||||
|
"haimantika@appwrite.io": { "memberSince": "2022-04-01", "spot": "18", "gitHub": "Haimantika" },
|
||||||
|
"chen@appwrite.io": { "memberSince": "2022-01-24", "spot": "19", "gitHub": "chenparnasa" },
|
||||||
|
"tessa@appwrite.io": { "memberSince": "2022-04-21", "spot": "20", "gitHub": "tessamero" },
|
||||||
|
"shimon@appwrite.io": { "memberSince": "2022-05-01", "spot": "23", "gitHub": "shimonewman" },
|
||||||
|
"shmuel@appwrite.io": { "memberSince": "2022-03-20", "spot": "24", "gitHub": "fogelito" },
|
||||||
|
"arman@appwrite.io": { "memberSince": "2022-04-04", "spot": "25", "gitHub": "ArmanNik" },
|
||||||
|
"carla@appwrite.io": { "memberSince": "2022-04-04", "spot": "26", "gitHub": "heyCarla" },
|
||||||
|
"emma@appwrite.io": { "memberSince": "2022-05-08", "spot": "27", "gitHub": "emmacarpagnano1" },
|
||||||
|
"dylan@appwrite.io": { "memberSince": "2022-05-09", "spot": "28", "gitHub": "DylanG-64" },
|
||||||
|
"steven@appwrite.io": { "memberSince": "2022-07-01", "spot": "30", "gitHub": "stnguyen90" },
|
||||||
|
"jyoti@appwrite.io": { "memberSince": "2022-10-24", "spot": "31", "gitHub": "joeyouss" },
|
||||||
|
"jade@appwrite.io": { "memberSince": "2022-10-31", "spot": "32", "gitHub": "dajebp" },
|
||||||
|
"khushboo@appwrite.io": { "memberSince": "2021-11-08", "spot": "13", "gitHub": "vermakhushboo" },
|
||||||
|
"thomas@appwrite.io": { "memberSince": "2022-11-03", "spot": "34", "gitHub": "TGlide" },
|
||||||
|
"holly@appwrite.io": { "memberSince": "2022-12-05", "spot": "35", "gitHub": "HollyBarclay" },
|
||||||
|
"laura@appwrite.io": { "memberSince": "2023-01-25", "spot": "36", "gitHub": "LauraDuRy" },
|
||||||
|
"caio@appwrite.io": { "memberSince": "2023-03-27", "spot": "37", "gitHub": "ariascaio" },
|
||||||
|
"luke@appwrite.io": { "memberSince": "2023-05-04", "spot": "38", "gitHub": "loks0n" }
|
||||||
|
}
|
9
app/config/cloud/heroes.json
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"bishwajeet.techmaster@gmail.com": { "memberSince": "2023-02-07" },
|
||||||
|
"lucasaudart@gmail.com": { "memberSince": "2023-02-07" },
|
||||||
|
"tkarmakar27112000@gmail.com": { "memberSince": "2023-02-07" },
|
||||||
|
"alves.mckl@gmail.com": { "memberSince": "2023-02-07" },
|
||||||
|
"dpns_nampula@rnlay.com": { "memberSince": "2023-02-07" },
|
||||||
|
"a.stephensimon@outlook.com": { "memberSince": "2023-02-07" },
|
||||||
|
"hidianapham@gmail.com": { "memberSince": "2023-02-07" }
|
||||||
|
}
|
|
@ -112,7 +112,7 @@ return [
|
||||||
],
|
],
|
||||||
Exception::USER_BLOCKED => [
|
Exception::USER_BLOCKED => [
|
||||||
'name' => Exception::USER_BLOCKED,
|
'name' => Exception::USER_BLOCKED,
|
||||||
'description' => 'The current user has been blocked. You can unblock the user from the Appwrite console.',
|
'description' => 'The current user has been blocked.',
|
||||||
'code' => 401,
|
'code' => 401,
|
||||||
],
|
],
|
||||||
Exception::USER_INVALID_TOKEN => [
|
Exception::USER_INVALID_TOKEN => [
|
||||||
|
@ -484,6 +484,11 @@ return [
|
||||||
'description' => 'Project with the requested ID could not be found. Please check the value of the X-Appwrite-Project header to ensure the correct project ID is being used.',
|
'description' => 'Project with the requested ID could not be found. Please check the value of the X-Appwrite-Project header to ensure the correct project ID is being used.',
|
||||||
'code' => 404,
|
'code' => 404,
|
||||||
],
|
],
|
||||||
|
Exception::PROJECT_ALREADY_EXISTS => [
|
||||||
|
'name' => Exception::PROJECT_ALREADY_EXISTS,
|
||||||
|
'description' => 'Project with the requested ID already exists.',
|
||||||
|
'code' => 409,
|
||||||
|
],
|
||||||
Exception::PROJECT_UNKNOWN => [
|
Exception::PROJECT_UNKNOWN => [
|
||||||
'name' => Exception::PROJECT_UNKNOWN,
|
'name' => Exception::PROJECT_UNKNOWN,
|
||||||
'description' => 'The project ID is either missing or not valid. Please check the value of the X-Appwrite-Project header to ensure the correct project ID is being used.',
|
'description' => 'The project ID is either missing or not valid. Please check the value of the X-Appwrite-Project header to ensure the correct project ID is being used.',
|
||||||
|
@ -541,9 +546,14 @@ return [
|
||||||
],
|
],
|
||||||
Exception::DOMAIN_ALREADY_EXISTS => [
|
Exception::DOMAIN_ALREADY_EXISTS => [
|
||||||
'name' => Exception::DOMAIN_ALREADY_EXISTS,
|
'name' => Exception::DOMAIN_ALREADY_EXISTS,
|
||||||
'description' => 'A Domain with the requested ID already exists.',
|
'description' => 'The requested domain is currently in use by a project.',
|
||||||
'code' => 409,
|
'code' => 409,
|
||||||
],
|
],
|
||||||
|
Exception::DOMAIN_FORBIDDEN => [
|
||||||
|
'name' => Exception::DOMAIN_FORBIDDEN,
|
||||||
|
'description' => 'The requested domain cannot be used as a custom domain.',
|
||||||
|
'code' => 403,
|
||||||
|
],
|
||||||
Exception::VARIABLE_NOT_FOUND => [
|
Exception::VARIABLE_NOT_FOUND => [
|
||||||
'name' => Exception::VARIABLE_NOT_FOUND,
|
'name' => Exception::VARIABLE_NOT_FOUND,
|
||||||
'description' => 'Variable with the requested ID could not be found.',
|
'description' => 'Variable with the requested ID could not be found.',
|
||||||
|
|
|
@ -572,6 +572,10 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||||
$name = $oauth2->getUserName($accessToken);
|
$name = $oauth2->getUserName($accessToken);
|
||||||
$email = $oauth2->getUserEmail($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.
|
* Is verified is not used yet, since we don't know after an accout is created anymore if it was verified or not.
|
||||||
*/
|
*/
|
||||||
|
@ -1813,6 +1817,12 @@ App::patch('/v1/account/status')
|
||||||
$response->addHeader('X-Fallback-Cookies', \json_encode([]));
|
$response->addHeader('X-Fallback-Cookies', \json_encode([]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$protocol = $request->getProtocol();
|
||||||
|
$response
|
||||||
|
->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null)
|
||||||
|
->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
|
||||||
|
;
|
||||||
|
|
||||||
$response->dynamic($user, Response::MODEL_ACCOUNT);
|
$response->dynamic($user, Response::MODEL_ACCOUNT);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,16 @@ use Appwrite\Utopia\Response;
|
||||||
use chillerlan\QRCode\QRCode;
|
use chillerlan\QRCode\QRCode;
|
||||||
use chillerlan\QRCode\QROptions;
|
use chillerlan\QRCode\QROptions;
|
||||||
use Utopia\App;
|
use Utopia\App;
|
||||||
|
use Utopia\CLI\Console;
|
||||||
use Utopia\Config\Config;
|
use Utopia\Config\Config;
|
||||||
|
use Utopia\Database\Database;
|
||||||
|
use Utopia\Database\DateTime;
|
||||||
use Utopia\Database\Document;
|
use Utopia\Database\Document;
|
||||||
|
use Utopia\Database\Validator\Authorization;
|
||||||
|
use Utopia\Database\Validator\UID;
|
||||||
use Utopia\Image\Image;
|
use Utopia\Image\Image;
|
||||||
|
use Utopia\Logger\Log;
|
||||||
|
use Utopia\Logger\Logger;
|
||||||
use Utopia\Validator\Boolean;
|
use Utopia\Validator\Boolean;
|
||||||
use Utopia\Validator\HexColor;
|
use Utopia\Validator\HexColor;
|
||||||
use Utopia\Validator\Range;
|
use Utopia\Validator\Range;
|
||||||
|
@ -49,11 +56,139 @@ $avatarCallback = function (string $type, string $code, int $width, int $height,
|
||||||
$response
|
$response
|
||||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
||||||
->setContentType('image/png')
|
->setContentType('image/png')
|
||||||
->file($data)
|
->file($data);
|
||||||
;
|
|
||||||
unset($image);
|
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')
|
App::get('/v1/avatars/credit-cards/:code')
|
||||||
->desc('Get Credit Card Icon')
|
->desc('Get Credit Card Icon')
|
||||||
->groups(['api', 'avatars'])
|
->groups(['api', 'avatars'])
|
||||||
|
@ -160,8 +295,7 @@ App::get('/v1/avatars/image')
|
||||||
$response
|
$response
|
||||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
||||||
->setContentType('image/png')
|
->setContentType('image/png')
|
||||||
->file($data)
|
->file($data);
|
||||||
;
|
|
||||||
unset($image);
|
unset($image);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -274,8 +408,7 @@ App::get('/v1/avatars/favicon')
|
||||||
$response
|
$response
|
||||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
||||||
->setContentType('image/x-icon')
|
->setContentType('image/x-icon')
|
||||||
->file($data)
|
->file($data);
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$fetch = @\file_get_contents($outputHref, false);
|
$fetch = @\file_get_contents($outputHref, false);
|
||||||
|
@ -292,8 +425,7 @@ App::get('/v1/avatars/favicon')
|
||||||
$response
|
$response
|
||||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT')
|
||||||
->setContentType('image/png')
|
->setContentType('image/png')
|
||||||
->file($data)
|
->file($data);
|
||||||
;
|
|
||||||
unset($image);
|
unset($image);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -334,8 +466,7 @@ App::get('/v1/avatars/qr')
|
||||||
$response
|
$response
|
||||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||||
->setContentType('image/png')
|
->setContentType('image/png')
|
||||||
->send($image->output('png', 9))
|
->send($image->output('png', 9));
|
||||||
;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
App::get('/v1/avatars/initials')
|
App::get('/v1/avatars/initials')
|
||||||
|
@ -419,6 +550,680 @@ App::get('/v1/avatars/initials')
|
||||||
$response
|
$response
|
||||||
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache
|
||||||
->setContentType('image/png')
|
->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());
|
||||||
});
|
});
|
||||||
|
|
|
@ -176,7 +176,7 @@ App::post('/v1/databases')
|
||||||
]));
|
]));
|
||||||
$database = $dbForProject->getDocument('databases', $databaseId);
|
$database = $dbForProject->getDocument('databases', $databaseId);
|
||||||
|
|
||||||
$collections = Config::getParam('collections', [])['collections'] ?? [];
|
$collections = (Config::getParam('collections', [])['databases'] ?? [])['collections'] ?? [];
|
||||||
if (empty($collections)) {
|
if (empty($collections)) {
|
||||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'The "collections" collection is not configured.');
|
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'The "collections" collection is not configured.');
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,17 +18,18 @@ use Utopia\Config\Config;
|
||||||
use Utopia\Database\Database;
|
use Utopia\Database\Database;
|
||||||
use Utopia\Database\Document;
|
use Utopia\Database\Document;
|
||||||
use Utopia\Database\Helpers\ID;
|
use Utopia\Database\Helpers\ID;
|
||||||
use Utopia\Database\DateTime;
|
|
||||||
use Utopia\Database\Helpers\Permission;
|
use Utopia\Database\Helpers\Permission;
|
||||||
use Utopia\Database\Query;
|
use Utopia\Database\Query;
|
||||||
use Utopia\Database\Helpers\Role;
|
use Utopia\Database\Helpers\Role;
|
||||||
use Utopia\Database\Validator\DatetimeValidator;
|
use Utopia\Database\Validator\DatetimeValidator;
|
||||||
|
use Utopia\Database\DateTime;
|
||||||
use Utopia\Database\Validator\UID;
|
use Utopia\Database\Validator\UID;
|
||||||
use Utopia\Domains\Domain;
|
use Utopia\Domains\Domain;
|
||||||
use Appwrite\Extend\Exception;
|
use Appwrite\Extend\Exception;
|
||||||
use Appwrite\Utopia\Database\Validator\Queries\Projects;
|
use Appwrite\Utopia\Database\Validator\Queries\Projects;
|
||||||
use Utopia\Cache\Cache;
|
use Utopia\Cache\Cache;
|
||||||
use Utopia\Pools\Group;
|
use Utopia\Pools\Group;
|
||||||
|
use Utopia\Database\Exception\Duplicate;
|
||||||
use Utopia\Validator\ArrayList;
|
use Utopia\Validator\ArrayList;
|
||||||
use Utopia\Validator\Boolean;
|
use Utopia\Validator\Boolean;
|
||||||
use Utopia\Validator\Hostname;
|
use Utopia\Validator\Hostname;
|
||||||
|
@ -88,46 +89,81 @@ App::post('/v1/projects')
|
||||||
}
|
}
|
||||||
|
|
||||||
$projectId = ($projectId == 'unique()') ? ID::unique() : $projectId;
|
$projectId = ($projectId == 'unique()') ? ID::unique() : $projectId;
|
||||||
|
|
||||||
|
$backups['database_db_fra1_02'] = ['from' => '7:30', 'to' => '8:15'];
|
||||||
|
$backups['database_db_fra1_03'] = ['from' => '10:30', 'to' => '11:15'];
|
||||||
|
$backups['database_db_fra1_04'] = ['from' => '13:30', 'to' => '14:15'];
|
||||||
|
$backups['database_db_fra1_05'] = ['from' => '4:30', 'to' => '5:15'];
|
||||||
|
|
||||||
$databases = Config::getParam('pools-database', []);
|
$databases = Config::getParam('pools-database', []);
|
||||||
$database = $databases[array_rand($databases)];
|
|
||||||
|
/**
|
||||||
|
* Extract db from list while backing
|
||||||
|
*/
|
||||||
|
if (count($databases) > 1) {
|
||||||
|
$now = new \DateTime();
|
||||||
|
|
||||||
|
foreach ($databases as $index => $database) {
|
||||||
|
if (empty($backups[$database])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$backup = $backups[$database];
|
||||||
|
$from = \DateTime::createFromFormat('H:i', $backup['from']);
|
||||||
|
$to = \DateTime::createFromFormat('H:i', $backup['to']);
|
||||||
|
if ($now >= $from && $now <= $to) {
|
||||||
|
unset($databases[$index]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($index = array_search('database_db_fra1_05', $databases)) {
|
||||||
|
$database = $databases[$index];
|
||||||
|
} else {
|
||||||
|
$database = $databases[array_rand($databases)];
|
||||||
|
}
|
||||||
|
|
||||||
if ($projectId === 'console') {
|
if ($projectId === 'console') {
|
||||||
throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project.");
|
throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$project = $dbForConsole->createDocument('projects', new Document([
|
try {
|
||||||
'$id' => $projectId,
|
$project = $dbForConsole->createDocument('projects', new Document([
|
||||||
'$permissions' => [
|
'$id' => $projectId,
|
||||||
Permission::read(Role::team(ID::custom($teamId))),
|
'$permissions' => [
|
||||||
Permission::update(Role::team(ID::custom($teamId), 'owner')),
|
Permission::read(Role::team(ID::custom($teamId))),
|
||||||
Permission::update(Role::team(ID::custom($teamId), 'developer')),
|
Permission::update(Role::team(ID::custom($teamId), 'owner')),
|
||||||
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
|
Permission::update(Role::team(ID::custom($teamId), 'developer')),
|
||||||
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
|
Permission::delete(Role::team(ID::custom($teamId), 'owner')),
|
||||||
],
|
Permission::delete(Role::team(ID::custom($teamId), 'developer')),
|
||||||
'name' => $name,
|
],
|
||||||
'teamInternalId' => $team->getInternalId(),
|
'name' => $name,
|
||||||
'teamId' => $team->getId(),
|
'teamInternalId' => $team->getInternalId(),
|
||||||
'region' => $region,
|
'teamId' => $team->getId(),
|
||||||
'description' => $description,
|
'region' => $region,
|
||||||
'logo' => $logo,
|
'description' => $description,
|
||||||
'url' => $url,
|
'logo' => $logo,
|
||||||
'version' => APP_VERSION_STABLE,
|
'url' => $url,
|
||||||
'legalName' => $legalName,
|
'version' => APP_VERSION_STABLE,
|
||||||
'legalCountry' => $legalCountry,
|
'legalName' => $legalName,
|
||||||
'legalState' => $legalState,
|
'legalCountry' => $legalCountry,
|
||||||
'legalCity' => $legalCity,
|
'legalState' => $legalState,
|
||||||
'legalAddress' => $legalAddress,
|
'legalCity' => $legalCity,
|
||||||
'legalTaxId' => ID::custom($legalTaxId),
|
'legalAddress' => $legalAddress,
|
||||||
'services' => new stdClass(),
|
'legalTaxId' => ID::custom($legalTaxId),
|
||||||
'platforms' => null,
|
'services' => new stdClass(),
|
||||||
'authProviders' => [],
|
'platforms' => null,
|
||||||
'webhooks' => null,
|
'authProviders' => [],
|
||||||
'keys' => null,
|
'webhooks' => null,
|
||||||
'domains' => null,
|
'keys' => null,
|
||||||
'auths' => $auths,
|
'domains' => null,
|
||||||
'search' => implode(' ', [$projectId, $name]),
|
'auths' => $auths,
|
||||||
'database' => $database,
|
'search' => implode(' ', [$projectId, $name]),
|
||||||
]));
|
'database' => $database
|
||||||
|
]));
|
||||||
|
} catch (Duplicate $th) {
|
||||||
|
throw new Exception(Exception::PROJECT_ALREADY_EXISTS);
|
||||||
|
}
|
||||||
|
|
||||||
$dbForProject = new Database($pools->get($database)->pop()->getResource(), $cache);
|
$dbForProject = new Database($pools->get($database)->pop()->getResource(), $cache);
|
||||||
$dbForProject->setNamespace("_{$project->getInternalId()}");
|
$dbForProject->setNamespace("_{$project->getInternalId()}");
|
||||||
|
@ -140,7 +176,7 @@ App::post('/v1/projects')
|
||||||
$adapter->setup();
|
$adapter->setup();
|
||||||
|
|
||||||
/** @var array $collections */
|
/** @var array $collections */
|
||||||
$collections = Config::getParam('collections', []);
|
$collections = Config::getParam('collections', [])['projects'] ?? [];
|
||||||
|
|
||||||
foreach ($collections as $key => $collection) {
|
foreach ($collections as $key => $collection) {
|
||||||
if (($collection['$collection'] ?? '') !== Database::METADATA) {
|
if (($collection['$collection'] ?? '') !== Database::METADATA) {
|
||||||
|
@ -1221,9 +1257,12 @@ App::post('/v1/projects/:projectId/domains')
|
||||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($domain === App::getEnv('_APP_DOMAIN', '') || $domain === App::getEnv('_APP_DOMAIN_TARGET', '')) {
|
||||||
|
throw new Exception(Exception::DOMAIN_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
$document = $dbForConsole->findOne('domains', [
|
$document = $dbForConsole->findOne('domains', [
|
||||||
Query::equal('domain', [$domain]),
|
Query::equal('domain', [$domain])
|
||||||
Query::equal('projectInternalId', [$project->getInternalId()]),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($document && !$document->isEmpty()) {
|
if ($document && !$document->isEmpty()) {
|
||||||
|
@ -1421,6 +1460,10 @@ App::delete('/v1/projects/:projectId/domains/:domainId')
|
||||||
throw new Exception(Exception::DOMAIN_NOT_FOUND);
|
throw new Exception(Exception::DOMAIN_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($domain->getAttribute('domain') === App::getEnv('_APP_DOMAIN', '') || $domain->getAttribute('domain') === App::getEnv('_APP_DOMAIN_TARGET', '')) {
|
||||||
|
throw new Exception(Exception::DOMAIN_FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
$dbForConsole->deleteDocument('domains', $domain->getId());
|
$dbForConsole->deleteDocument('domains', $domain->getId());
|
||||||
|
|
||||||
$dbForConsole->deleteCachedDocument('projects', $project->getId());
|
$dbForConsole->deleteCachedDocument('projects', $project->getId());
|
||||||
|
|
|
@ -79,7 +79,7 @@ App::post('/v1/storage/buckets')
|
||||||
$permissions = Permission::aggregate($permissions);
|
$permissions = Permission::aggregate($permissions);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$files = Config::getParam('collections', [])['files'] ?? [];
|
$files = (Config::getParam('collections', [])['buckets'] ?? [])['files'] ?? [];
|
||||||
if (empty($files)) {
|
if (empty($files)) {
|
||||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Files collection is not configured.');
|
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Files collection is not configured.');
|
||||||
}
|
}
|
||||||
|
@ -352,8 +352,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||||
->inject('mode')
|
->inject('mode')
|
||||||
->inject('deviceFiles')
|
->inject('deviceFiles')
|
||||||
->inject('deviceLocal')
|
->inject('deviceLocal')
|
||||||
->inject('deletes')
|
->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) {
|
||||||
->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal, Delete $deletes) {
|
|
||||||
|
|
||||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||||
|
|
||||||
|
@ -649,11 +648,6 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||||
->setContext('bucket', $bucket)
|
->setContext('bucket', $bucket)
|
||||||
;
|
;
|
||||||
|
|
||||||
$deletes
|
|
||||||
->setType(DELETE_TYPE_CACHE_BY_RESOURCE)
|
|
||||||
->setResource('file/' . $file->getId())
|
|
||||||
;
|
|
||||||
|
|
||||||
$metadata = null; // was causing leaks as it was passed by reference
|
$metadata = null; // was causing leaks as it was passed by reference
|
||||||
|
|
||||||
$response
|
$response
|
||||||
|
|
|
@ -425,6 +425,7 @@ App::error()
|
||||||
$log->setType(Log::TYPE_ERROR);
|
$log->setType(Log::TYPE_ERROR);
|
||||||
$log->setMessage($error->getMessage());
|
$log->setMessage($error->getMessage());
|
||||||
|
|
||||||
|
$log->addTag('database', $project->getAttribute('database', 'console'));
|
||||||
$log->addTag('method', $route->getMethod());
|
$log->addTag('method', $route->getMethod());
|
||||||
$log->addTag('url', $route->getPath());
|
$log->addTag('url', $route->getPath());
|
||||||
$log->addTag('verboseType', get_class($error));
|
$log->addTag('verboseType', get_class($error));
|
||||||
|
@ -437,7 +438,7 @@ App::error()
|
||||||
$log->addExtra('line', $error->getLine());
|
$log->addExtra('line', $error->getLine());
|
||||||
$log->addExtra('trace', $error->getTraceAsString());
|
$log->addExtra('trace', $error->getTraceAsString());
|
||||||
$log->addExtra('detailedTrace', $error->getTrace());
|
$log->addExtra('detailedTrace', $error->getTrace());
|
||||||
$log->addExtra('roles', Authorization::$roles);
|
$log->addExtra('roles', Authorization::getRoles());
|
||||||
|
|
||||||
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
|
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
|
||||||
$log->setAction($action);
|
$log->setAction($action);
|
||||||
|
@ -580,7 +581,7 @@ App::get('/humans.txt')
|
||||||
$response->text($template->render(false));
|
$response->text($template->render(false));
|
||||||
});
|
});
|
||||||
|
|
||||||
App::get('/.well-known/acme-challenge')
|
App::get('/.well-known/acme-challenge/*')
|
||||||
->desc('SSL Verification')
|
->desc('SSL Verification')
|
||||||
->label('scope', 'public')
|
->label('scope', 'public')
|
||||||
->label('docs', false)
|
->label('docs', false)
|
||||||
|
|
|
@ -549,7 +549,7 @@ App::shutdown()
|
||||||
]) ;
|
]) ;
|
||||||
|
|
||||||
$signature = md5($data);
|
$signature = md5($data);
|
||||||
$cacheLog = $dbForProject->getDocument('cache', $key);
|
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||||
$accessedAt = $cacheLog->getAttribute('accessedAt', '');
|
$accessedAt = $cacheLog->getAttribute('accessedAt', '');
|
||||||
$now = DateTime::now();
|
$now = DateTime::now();
|
||||||
if ($cacheLog->isEmpty()) {
|
if ($cacheLog->isEmpty()) {
|
||||||
|
|
|
@ -1,19 +1,61 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Appwrite\Utopia\Request;
|
||||||
use Appwrite\Utopia\Response;
|
use Appwrite\Utopia\Response;
|
||||||
use Utopia\App;
|
use Utopia\App;
|
||||||
|
|
||||||
App::get('/console')
|
App::get('/console/*')
|
||||||
->alias('/')
|
->alias('/')
|
||||||
|
->alias('auth/*')
|
||||||
->alias('/invite')
|
->alias('/invite')
|
||||||
->alias('/login')
|
->alias('/login')
|
||||||
->alias('/recover')
|
->alias('/recover')
|
||||||
->alias('/register')
|
->alias('/register/*')
|
||||||
->groups(['web'])
|
->groups(['web'])
|
||||||
->label('permission', 'public')
|
->label('permission', 'public')
|
||||||
->label('scope', 'home')
|
->label('scope', 'home')
|
||||||
|
->inject('request')
|
||||||
->inject('response')
|
->inject('response')
|
||||||
->action(function (Response $response) {
|
->action(function (Request $request, Response $response) {
|
||||||
$fallback = file_get_contents(__DIR__ . '/../../../console/index.html');
|
$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);
|
$response->html($fallback);
|
||||||
});
|
});
|
||||||
|
|
11
app/http.php
|
@ -85,10 +85,8 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
||||||
|
|
||||||
Console::success('[Setup] - Server database init started...');
|
Console::success('[Setup] - Server database init started...');
|
||||||
|
|
||||||
/** @var array $collections */
|
|
||||||
$collections = Config::getParam('collections', []);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
$cache = $app->getResource('cache'); /** @var Utopia\Cache\Cache $cache */
|
||||||
Console::success('[Setup] - Creating database: appwrite...');
|
Console::success('[Setup] - Creating database: appwrite...');
|
||||||
$dbForConsole->create();
|
$dbForConsole->create();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
@ -105,7 +103,10 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
||||||
$adapter->setup();
|
$adapter->setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($collections as $key => $collection) {
|
/** @var array $collections */
|
||||||
|
$collections = Config::getParam('collections', []);
|
||||||
|
$consoleCollections = $collections['console'];
|
||||||
|
foreach ($consoleCollections as $key => $collection) {
|
||||||
if (($collection['$collection'] ?? '') !== Database::METADATA) {
|
if (($collection['$collection'] ?? '') !== Database::METADATA) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -177,7 +178,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
|
||||||
$bucket = $dbForConsole->getDocument('buckets', 'default');
|
$bucket = $dbForConsole->getDocument('buckets', 'default');
|
||||||
|
|
||||||
Console::success('[Setup] - Creating files collection for default bucket...');
|
Console::success('[Setup] - Creating files collection for default bucket...');
|
||||||
$files = $collections['files'] ?? [];
|
$files = $collections['buckets']['files'] ?? [];
|
||||||
if (empty($files)) {
|
if (empty($files)) {
|
||||||
throw new Exception('Files collection is not configured.');
|
throw new Exception('Files collection is not configured.');
|
||||||
}
|
}
|
||||||
|
|
23
app/init.php
|
@ -1053,6 +1053,11 @@ App::setResource('console', function () {
|
||||||
],
|
],
|
||||||
'authWhitelistEmails' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [],
|
'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)) : [],
|
'authWhitelistIPs' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [],
|
||||||
|
'authProviders' => [
|
||||||
|
'githubEnabled' => true,
|
||||||
|
'githubSecret' => App::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''),
|
||||||
|
'githubAppid' => App::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '')
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
@ -1289,3 +1294,21 @@ App::setResource('schema', function ($utopia, $dbForProject) {
|
||||||
$params,
|
$params,
|
||||||
);
|
);
|
||||||
}, ['utopia', 'dbForProject']);
|
}, ['utopia', 'dbForProject']);
|
||||||
|
|
||||||
|
App::setResource('contributors', function () {
|
||||||
|
$path = 'app/config/cloud/contributors.json';
|
||||||
|
$list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
|
||||||
|
return $list;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
App::setResource('employees', function () {
|
||||||
|
$path = 'app/config/cloud/employees.json';
|
||||||
|
$list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
|
||||||
|
return $list;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
App::setResource('heroes', function () {
|
||||||
|
$path = 'app/config/cloud/heroes.json';
|
||||||
|
$list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : [];
|
||||||
|
return $list;
|
||||||
|
}, []);
|
||||||
|
|
|
@ -126,16 +126,15 @@ $server
|
||||||
->error()
|
->error()
|
||||||
->inject('error')
|
->inject('error')
|
||||||
->inject('logger')
|
->inject('logger')
|
||||||
->action(function (Throwable $error, Logger $logger) {
|
->inject('log')
|
||||||
|
->action(function (Throwable $error, Logger $logger, Log $log) {
|
||||||
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||||
|
|
||||||
if ($error instanceof PDOException) {
|
if ($error instanceof PDOException) {
|
||||||
throw $error;
|
throw $error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($error->getCode() >= 500 || $error->getCode() === 0) {
|
if ($logger && ($error->getCode() >= 500 || $error->getCode() === 0)) {
|
||||||
$log = new Log();
|
|
||||||
|
|
||||||
$log->setNamespace("appwrite-worker");
|
$log->setNamespace("appwrite-worker");
|
||||||
$log->setServer(\gethostname());
|
$log->setServer(\gethostname());
|
||||||
$log->setVersion($version);
|
$log->setVersion($version);
|
||||||
|
@ -153,7 +152,8 @@ $server
|
||||||
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
|
$isProduction = App::getEnv('_APP_ENV', 'development') === 'production';
|
||||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||||
|
|
||||||
$logger->addLog($log);
|
$responseCode = $logger->addLog($log);
|
||||||
|
Console::info('Usage stats log pushed with status code: ' . $responseCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
Console::error('[Error] Type: ' . get_class($error));
|
Console::error('[Error] Type: ' . get_class($error));
|
||||||
|
|
|
@ -113,10 +113,10 @@ class DeletesV1 extends Worker
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DELETE_TYPE_CACHE_BY_RESOURCE:
|
case DELETE_TYPE_CACHE_BY_RESOURCE:
|
||||||
$this->deleteCacheByResource($this->args['resource']);
|
$this->deleteCacheByResource($project, $this->args['resource']);
|
||||||
break;
|
break;
|
||||||
case DELETE_TYPE_CACHE_BY_TIMESTAMP:
|
case DELETE_TYPE_CACHE_BY_TIMESTAMP:
|
||||||
$this->deleteCacheByDate();
|
$this->deleteCacheByDate($this->args['datetime']);
|
||||||
break;
|
break;
|
||||||
case DELETE_TYPE_SCHEDULES:
|
case DELETE_TYPE_SCHEDULES:
|
||||||
$this->deleteSchedules($this->args['datetime']);
|
$this->deleteSchedules($this->args['datetime']);
|
||||||
|
@ -164,32 +164,54 @@ class DeletesV1 extends Worker
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param Document $project
|
||||||
* @param string $resource
|
* @param string $resource
|
||||||
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
protected function deleteCacheByResource(string $resource): void
|
protected function deleteCacheByResource(Document $project, string $resource): void
|
||||||
{
|
{
|
||||||
$this->deleteCacheFiles([
|
$projectId = $project->getId();
|
||||||
Query::equal('resource', [$resource]),
|
$dbForProject = $this->getProjectDB($project);
|
||||||
]);
|
$document = $dbForProject->findOne('cache', [Query::equal('resource', [$resource])]);
|
||||||
|
|
||||||
|
if ($document) {
|
||||||
|
$cache = new Cache(
|
||||||
|
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->deleteById(
|
||||||
|
$document,
|
||||||
|
$dbForProject,
|
||||||
|
function ($document) use ($cache, $projectId) {
|
||||||
|
$path = APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId . DIRECTORY_SEPARATOR . $document->getId();
|
||||||
|
|
||||||
|
if ($cache->purge($document->getId())) {
|
||||||
|
Console::success('Deleting cache file: ' . $path);
|
||||||
|
} else {
|
||||||
|
Console::error('Failed to delete cache file: ' . $path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function deleteCacheByDate(): void
|
/**
|
||||||
|
* @param string $datetime
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
protected function deleteCacheByDate(string $datetime): void
|
||||||
{
|
{
|
||||||
$this->deleteCacheFiles([
|
$this->deleteForProjectIds(function (Document $project) use ($datetime) {
|
||||||
Query::lessThan('accessedAt', $this->args['datetime']),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function deleteCacheFiles($query): void
|
|
||||||
{
|
|
||||||
$this->deleteForProjectIds(function (Document $project) use ($query) {
|
|
||||||
|
|
||||||
$projectId = $project->getId();
|
$projectId = $project->getId();
|
||||||
$dbForProject = $this->getProjectDB($project);
|
$dbForProject = $this->getProjectDB($project);
|
||||||
$cache = new Cache(
|
$cache = new Cache(
|
||||||
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId)
|
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$query = [
|
||||||
|
Query::lessThan('accessedAt', $datetime),
|
||||||
|
];
|
||||||
|
|
||||||
$this->deleteByGroup(
|
$this->deleteByGroup(
|
||||||
'cache',
|
'cache',
|
||||||
$query,
|
$query,
|
||||||
|
@ -207,9 +229,10 @@ class DeletesV1 extends Worker
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Document $document database document
|
* @param Document $document database document
|
||||||
* @param Document $projectId
|
* @param Document $project
|
||||||
*/
|
*/
|
||||||
protected function deleteDatabase(Document $document, Document $project): void
|
protected function deleteDatabase(Document $document, Document $project): void
|
||||||
{
|
{
|
||||||
|
@ -662,19 +685,23 @@ class DeletesV1 extends Worker
|
||||||
|
|
||||||
$executionStart = \microtime(true);
|
$executionStart = \microtime(true);
|
||||||
|
|
||||||
while ($sum === $limit) {
|
try {
|
||||||
$chunk++;
|
while ($sum === $limit) {
|
||||||
|
$chunk++;
|
||||||
|
|
||||||
$results = $database->find($collection, \array_merge([Query::limit($limit)], $queries));
|
$results = $database->find($collection, \array_merge([Query::limit($limit)], $queries));
|
||||||
|
|
||||||
$sum = count($results);
|
$sum = count($results);
|
||||||
|
|
||||||
Console::info('Deleting chunk #' . $chunk . '. Found ' . $sum . ' documents');
|
Console::info('Deleting chunk #' . $chunk . '. Found ' . $sum . ' documents in collection ' . $database->getNamespace() . '_' . $collection);
|
||||||
|
|
||||||
foreach ($results as $document) {
|
foreach ($results as $document) {
|
||||||
$this->deleteById($document, $database, $callback);
|
$this->deleteById($document, $database, $callback);
|
||||||
$count++;
|
$count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Console::error($e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
$executionEnd = \microtime(true);
|
$executionEnd = \microtime(true);
|
||||||
|
|
|
@ -18,6 +18,7 @@ use Utopia\Database\Helpers\ID;
|
||||||
use Utopia\Database\Helpers\Permission;
|
use Utopia\Database\Helpers\Permission;
|
||||||
use Utopia\Database\Query;
|
use Utopia\Database\Query;
|
||||||
use Utopia\Database\Validator\Authorization;
|
use Utopia\Database\Validator\Authorization;
|
||||||
|
use Utopia\Logger\Log;
|
||||||
use Utopia\Queue\Server;
|
use Utopia\Queue\Server;
|
||||||
use Utopia\Database\Helpers\Role;
|
use Utopia\Database\Helpers\Role;
|
||||||
|
|
||||||
|
@ -26,6 +27,7 @@ Authorization::setDefaultStatus(false);
|
||||||
|
|
||||||
Server::setResource('execute', function () {
|
Server::setResource('execute', function () {
|
||||||
return function (
|
return function (
|
||||||
|
Log $log,
|
||||||
Func $queueForFunctions,
|
Func $queueForFunctions,
|
||||||
Database $dbForProject,
|
Database $dbForProject,
|
||||||
Usage $queueForUsage,
|
Usage $queueForUsage,
|
||||||
|
@ -39,12 +41,14 @@ Server::setResource('execute', function () {
|
||||||
string $eventData = null,
|
string $eventData = null,
|
||||||
string $executionId = null,
|
string $executionId = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
$user ??= new Document();
|
$user ??= new Document();
|
||||||
$functionId = $function->getId();
|
$functionId = $function->getId();
|
||||||
$functionInternalId = $function->getInternalId();
|
$functionInternalId = $function->getInternalId();
|
||||||
$deploymentId = $function->getAttribute('deployment', '');
|
$deploymentId = $function->getAttribute('deployment', '');
|
||||||
|
|
||||||
|
$log->addTag('functionId', $functionId);
|
||||||
|
$log->addTag('projectId', $project->getId());
|
||||||
|
|
||||||
/** Check if deployment exists */
|
/** Check if deployment exists */
|
||||||
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
$deployment = $dbForProject->getDocument('deployments', $deploymentId);
|
||||||
$deploymentInternalId = $deployment->getInternalId();
|
$deploymentInternalId = $deployment->getInternalId();
|
||||||
|
@ -165,10 +169,8 @@ Server::setResource('execute', function () {
|
||||||
->setAttribute('statusCode', $th->getCode())
|
->setAttribute('statusCode', $th->getCode())
|
||||||
->setAttribute('stderr', $th->getMessage());
|
->setAttribute('stderr', $th->getMessage());
|
||||||
|
|
||||||
Console::error($th->getTraceAsString());
|
$error = $th->getMessage();
|
||||||
Console::error($th->getFile());
|
$errorCode = $th->getCode();
|
||||||
Console::error($th->getLine());
|
|
||||||
Console::error($th->getMessage());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
|
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
|
||||||
|
@ -259,8 +261,7 @@ $server->job()
|
||||||
while ($sum >= $limit) {
|
while ($sum >= $limit) {
|
||||||
$functions = $dbForProject->find('functions', [
|
$functions = $dbForProject->find('functions', [
|
||||||
Query::limit($limit),
|
Query::limit($limit),
|
||||||
Query::offset($offset),
|
Query::offset($offset)
|
||||||
Query::orderAsc('name'),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$sum = \count($functions);
|
$sum = \count($functions);
|
||||||
|
@ -303,6 +304,7 @@ $server->job()
|
||||||
$execution = new Document($payload['execution'] ?? []);
|
$execution = new Document($payload['execution'] ?? []);
|
||||||
$user = new Document($payload['user'] ?? []);
|
$user = new Document($payload['user'] ?? []);
|
||||||
$execute(
|
$execute(
|
||||||
|
log: $log,
|
||||||
project: $project,
|
project: $project,
|
||||||
function: $function,
|
function: $function,
|
||||||
dbForProject: $dbForProject,
|
dbForProject: $dbForProject,
|
||||||
|
@ -319,6 +321,7 @@ $server->job()
|
||||||
break;
|
break;
|
||||||
case 'schedule':
|
case 'schedule':
|
||||||
$execute(
|
$execute(
|
||||||
|
log: $log,
|
||||||
project: $project,
|
project: $project,
|
||||||
function: $function,
|
function: $function,
|
||||||
dbForProject: $dbForProject,
|
dbForProject: $dbForProject,
|
||||||
|
|
3
bin/calc-tier-stats
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
php /usr/src/code/app/cli.php calc-tier-stats $@
|
3
bin/calc-users-stats
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
php /usr/src/code/app/cli.php calc-users-stats $@
|
3
bin/clear-card-cache
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
php /usr/src/code/app/cli.php clear-card-cache $@
|
3
bin/patch-delete-project-collections
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
php /usr/src/code/app/cli.php patch-delete-project-collections $@
|
|
@ -50,18 +50,18 @@
|
||||||
"utopia-php/cli": "0.15.*",
|
"utopia-php/cli": "0.15.*",
|
||||||
"utopia-php/config": "0.2.*",
|
"utopia-php/config": "0.2.*",
|
||||||
"utopia-php/database": "0.30.*",
|
"utopia-php/database": "0.30.*",
|
||||||
"utopia-php/queue": "0.5.*",
|
"utopia-php/domains": "1.1.*",
|
||||||
|
"utopia-php/dsn": "0.1.*",
|
||||||
|
"utopia-php/framework": "0.26.*",
|
||||||
|
"utopia-php/image": "0.5.*",
|
||||||
|
"utopia-php/locale": "0.4.*",
|
||||||
|
"utopia-php/logger": "0.3.*",
|
||||||
|
"utopia-php/messaging": "0.1.*",
|
||||||
"utopia-php/orchestration": "0.9.*",
|
"utopia-php/orchestration": "0.9.*",
|
||||||
"utopia-php/platform": "0.3.*",
|
"utopia-php/platform": "0.3.*",
|
||||||
"utopia-php/pools": "0.4.*",
|
"utopia-php/pools": "0.4.*",
|
||||||
"utopia-php/preloader": "0.2.*",
|
"utopia-php/preloader": "0.2.*",
|
||||||
"utopia-php/domains": "1.1.*",
|
"utopia-php/queue": "0.5.*",
|
||||||
"utopia-php/framework": "0.26.*",
|
|
||||||
"utopia-php/image": "0.5.*",
|
|
||||||
"utopia-php/dsn": "0.1.*",
|
|
||||||
"utopia-php/locale": "0.4.*",
|
|
||||||
"utopia-php/logger": "0.3.*",
|
|
||||||
"utopia-php/messaging": "0.1.*",
|
|
||||||
"utopia-php/registry": "0.5.*",
|
"utopia-php/registry": "0.5.*",
|
||||||
"utopia-php/storage": "0.14.*",
|
"utopia-php/storage": "0.14.*",
|
||||||
"utopia-php/swoole": "0.5.*",
|
"utopia-php/swoole": "0.5.*",
|
||||||
|
@ -73,7 +73,8 @@
|
||||||
"chillerlan/php-qrcode": "4.3.3",
|
"chillerlan/php-qrcode": "4.3.3",
|
||||||
"adhocore/jwt": "1.1.2",
|
"adhocore/jwt": "1.1.2",
|
||||||
"webonyx/graphql-php": "14.11.*",
|
"webonyx/graphql-php": "14.11.*",
|
||||||
"slickdeals/statsd": "3.1.0"
|
"slickdeals/statsd": "3.1.0",
|
||||||
|
"league/csv": "9.7.1"
|
||||||
},
|
},
|
||||||
"repositories": [
|
"repositories": [
|
||||||
{
|
{
|
||||||
|
|
211
composer.lock
generated
|
@ -300,16 +300,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "colinmollenhour/credis",
|
"name": "colinmollenhour/credis",
|
||||||
"version": "v1.14.0",
|
"version": "v1.15.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/colinmollenhour/credis.git",
|
"url": "https://github.com/colinmollenhour/credis.git",
|
||||||
"reference": "dccc8a46586475075fbb012d8bd523b8a938c2dc"
|
"reference": "28810439de1d9597b7ba11794ed9479fb6f3de7c"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/colinmollenhour/credis/zipball/dccc8a46586475075fbb012d8bd523b8a938c2dc",
|
"url": "https://api.github.com/repos/colinmollenhour/credis/zipball/28810439de1d9597b7ba11794ed9479fb6f3de7c",
|
||||||
"reference": "dccc8a46586475075fbb012d8bd523b8a938c2dc",
|
"reference": "28810439de1d9597b7ba11794ed9479fb6f3de7c",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -341,9 +341,9 @@
|
||||||
"homepage": "https://github.com/colinmollenhour/credis",
|
"homepage": "https://github.com/colinmollenhour/credis",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/colinmollenhour/credis/issues",
|
"issues": "https://github.com/colinmollenhour/credis/issues",
|
||||||
"source": "https://github.com/colinmollenhour/credis/tree/v1.14.0"
|
"source": "https://github.com/colinmollenhour/credis/tree/v1.15.0"
|
||||||
},
|
},
|
||||||
"time": "2022-11-09T01:18:39+00:00"
|
"time": "2023-04-18T15:34:23+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "composer/package-versions-deprecated",
|
"name": "composer/package-versions-deprecated",
|
||||||
|
@ -600,6 +600,90 @@
|
||||||
},
|
},
|
||||||
"time": "2022-11-29T16:25:20+00:00"
|
"time": "2022-11-29T16:25:20+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "league/csv",
|
||||||
|
"version": "9.7.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/thephpleague/csv.git",
|
||||||
|
"reference": "0ec57e8264ec92565974ead0d1724cf1026e10c1"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/thephpleague/csv/zipball/0ec57e8264ec92565974ead0d1724cf1026e10c1",
|
||||||
|
"reference": "0ec57e8264ec92565974ead0d1724cf1026e10c1",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"php": "^7.3 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"ext-curl": "*",
|
||||||
|
"ext-dom": "*",
|
||||||
|
"friendsofphp/php-cs-fixer": "^2.16",
|
||||||
|
"phpstan/phpstan": "^0.12.0",
|
||||||
|
"phpstan/phpstan-phpunit": "^0.12.0",
|
||||||
|
"phpstan/phpstan-strict-rules": "^0.12.0",
|
||||||
|
"phpunit/phpunit": "^9.5"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-dom": "Required to use the XMLConverter and or the HTMLConverter classes",
|
||||||
|
"ext-iconv": "Needed to ease transcoding CSV using iconv stream filters"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "9.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/functions_include.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"League\\Csv\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Ignace Nyamagana Butera",
|
||||||
|
"email": "nyamsprod@gmail.com",
|
||||||
|
"homepage": "https://github.com/nyamsprod/",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "CSV data manipulation made easy in PHP",
|
||||||
|
"homepage": "http://csv.thephpleague.com",
|
||||||
|
"keywords": [
|
||||||
|
"convert",
|
||||||
|
"csv",
|
||||||
|
"export",
|
||||||
|
"filter",
|
||||||
|
"import",
|
||||||
|
"read",
|
||||||
|
"transform",
|
||||||
|
"write"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"docs": "https://csv.thephpleague.com",
|
||||||
|
"issues": "https://github.com/thephpleague/csv/issues",
|
||||||
|
"rss": "https://github.com/thephpleague/csv/releases.atom",
|
||||||
|
"source": "https://github.com/thephpleague/csv"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/sponsors/nyamsprod",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2021-04-17T16:32:08+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "matomo/device-detector",
|
"name": "matomo/device-detector",
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
|
@ -1588,16 +1672,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "utopia-php/framework",
|
"name": "utopia-php/framework",
|
||||||
"version": "0.26.0",
|
"version": "0.26.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/utopia-php/framework.git",
|
"url": "https://github.com/utopia-php/framework.git",
|
||||||
"reference": "e8da5576370366d3bf9c574ec855f8c96fe4f34e"
|
"reference": "7a18a2b0d6c8dba878c926b73650fd2c4eeaa70d"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/utopia-php/framework/zipball/e8da5576370366d3bf9c574ec855f8c96fe4f34e",
|
"url": "https://api.github.com/repos/utopia-php/framework/zipball/7a18a2b0d6c8dba878c926b73650fd2c4eeaa70d",
|
||||||
"reference": "e8da5576370366d3bf9c574ec855f8c96fe4f34e",
|
"reference": "7a18a2b0d6c8dba878c926b73650fd2c4eeaa70d",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -1626,9 +1710,9 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/utopia-php/framework/issues",
|
"issues": "https://github.com/utopia-php/framework/issues",
|
||||||
"source": "https://github.com/utopia-php/framework/tree/0.26.0"
|
"source": "https://github.com/utopia-php/framework/tree/0.26.3"
|
||||||
},
|
},
|
||||||
"time": "2023-01-13T08:14:43+00:00"
|
"time": "2023-06-03T14:01:15+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "utopia-php/image",
|
"name": "utopia-php/image",
|
||||||
|
@ -2099,16 +2183,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "utopia-php/queue",
|
"name": "utopia-php/queue",
|
||||||
"version": "0.5.2",
|
"version": "0.5.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/utopia-php/queue.git",
|
"url": "https://github.com/utopia-php/queue.git",
|
||||||
"reference": "310271c5cd477541208d7fa74a4dea64df8e04a0"
|
"reference": "8e8b6cb27172713fe5d8b7b092ce68516caf129a"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/utopia-php/queue/zipball/310271c5cd477541208d7fa74a4dea64df8e04a0",
|
"url": "https://api.github.com/repos/utopia-php/queue/zipball/8e8b6cb27172713fe5d8b7b092ce68516caf129a",
|
||||||
"reference": "310271c5cd477541208d7fa74a4dea64df8e04a0",
|
"reference": "8e8b6cb27172713fe5d8b7b092ce68516caf129a",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -2154,9 +2238,9 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/utopia-php/queue/issues",
|
"issues": "https://github.com/utopia-php/queue/issues",
|
||||||
"source": "https://github.com/utopia-php/queue/tree/0.5.2"
|
"source": "https://github.com/utopia-php/queue/tree/0.5.3"
|
||||||
},
|
},
|
||||||
"time": "2023-03-07T08:54:10+00:00"
|
"time": "2023-05-24T19:06:04+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "utopia-php/registry",
|
"name": "utopia-php/registry",
|
||||||
|
@ -2608,25 +2692,29 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/deprecations",
|
"name": "doctrine/deprecations",
|
||||||
"version": "v1.0.0",
|
"version": "v1.1.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/doctrine/deprecations.git",
|
"url": "https://github.com/doctrine/deprecations.git",
|
||||||
"reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de"
|
"reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
|
"url": "https://api.github.com/repos/doctrine/deprecations/zipball/612a3ee5ab0d5dd97b7cf3874a6efe24325efac3",
|
||||||
"reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de",
|
"reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.1|^8.0"
|
"php": "^7.1 || ^8.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"doctrine/coding-standard": "^9",
|
"doctrine/coding-standard": "^9",
|
||||||
"phpunit/phpunit": "^7.5|^8.5|^9.5",
|
"phpstan/phpstan": "1.4.10 || 1.10.15",
|
||||||
"psr/log": "^1|^2|^3"
|
"phpstan/phpstan-phpunit": "^1.0",
|
||||||
|
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
|
||||||
|
"psalm/plugin-phpunit": "0.18.4",
|
||||||
|
"psr/log": "^1 || ^2 || ^3",
|
||||||
|
"vimeo/psalm": "4.30.0 || 5.12.0"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"psr/log": "Allows logging deprecations via PSR-3 logger implementation"
|
"psr/log": "Allows logging deprecations via PSR-3 logger implementation"
|
||||||
|
@ -2645,9 +2733,9 @@
|
||||||
"homepage": "https://www.doctrine-project.org/",
|
"homepage": "https://www.doctrine-project.org/",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/doctrine/deprecations/issues",
|
"issues": "https://github.com/doctrine/deprecations/issues",
|
||||||
"source": "https://github.com/doctrine/deprecations/tree/v1.0.0"
|
"source": "https://github.com/doctrine/deprecations/tree/v1.1.1"
|
||||||
},
|
},
|
||||||
"time": "2022-05-02T15:47:09+00:00"
|
"time": "2023-06-03T09:27:29+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/instantiator",
|
"name": "doctrine/instantiator",
|
||||||
|
@ -2721,16 +2809,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "matthiasmullie/minify",
|
"name": "matthiasmullie/minify",
|
||||||
"version": "1.3.70",
|
"version": "1.3.71",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/matthiasmullie/minify.git",
|
"url": "https://github.com/matthiasmullie/minify.git",
|
||||||
"reference": "2807d9f9bece6877577ad44acb5c801bb3ae536b"
|
"reference": "ae42a47d7fecc1fbb7277b2f2d84c37a33edc3b1"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/matthiasmullie/minify/zipball/2807d9f9bece6877577ad44acb5c801bb3ae536b",
|
"url": "https://api.github.com/repos/matthiasmullie/minify/zipball/ae42a47d7fecc1fbb7277b2f2d84c37a33edc3b1",
|
||||||
"reference": "2807d9f9bece6877577ad44acb5c801bb3ae536b",
|
"reference": "ae42a47d7fecc1fbb7277b2f2d84c37a33edc3b1",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -2780,7 +2868,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/matthiasmullie/minify/issues",
|
"issues": "https://github.com/matthiasmullie/minify/issues",
|
||||||
"source": "https://github.com/matthiasmullie/minify/tree/1.3.70"
|
"source": "https://github.com/matthiasmullie/minify/tree/1.3.71"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -2788,7 +2876,7 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-12-09T12:56:44+00:00"
|
"time": "2023-04-25T20:33:03+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "matthiasmullie/path-converter",
|
"name": "matthiasmullie/path-converter",
|
||||||
|
@ -2904,16 +2992,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nikic/php-parser",
|
"name": "nikic/php-parser",
|
||||||
"version": "v4.15.4",
|
"version": "v4.15.5",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||||
"reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290"
|
"reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
|
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/11e2663a5bc9db5d714eedb4277ee300403b4a9e",
|
||||||
"reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290",
|
"reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -2954,9 +3042,9 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||||
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4"
|
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.5"
|
||||||
},
|
},
|
||||||
"time": "2023-03-05T19:49:14+00:00"
|
"time": "2023-05-19T20:20:00+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phar-io/manifest",
|
"name": "phar-io/manifest",
|
||||||
|
@ -3307,22 +3395,24 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpdoc-parser",
|
"name": "phpstan/phpdoc-parser",
|
||||||
"version": "1.16.1",
|
"version": "1.22.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpdoc-parser.git",
|
"url": "https://github.com/phpstan/phpdoc-parser.git",
|
||||||
"reference": "e27e92d939e2e3636f0a1f0afaba59692c0bf571"
|
"reference": "ec58baf7b3c7f1c81b3b00617c953249fb8cf30c"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/e27e92d939e2e3636f0a1f0afaba59692c0bf571",
|
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/ec58baf7b3c7f1c81b3b00617c953249fb8cf30c",
|
||||||
"reference": "e27e92d939e2e3636f0a1f0afaba59692c0bf571",
|
"reference": "ec58baf7b3c7f1c81b3b00617c953249fb8cf30c",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.2 || ^8.0"
|
"php": "^7.2 || ^8.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
"doctrine/annotations": "^2.0",
|
||||||
|
"nikic/php-parser": "^4.15",
|
||||||
"php-parallel-lint/php-parallel-lint": "^1.2",
|
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||||
"phpstan/extension-installer": "^1.0",
|
"phpstan/extension-installer": "^1.0",
|
||||||
"phpstan/phpstan": "^1.5",
|
"phpstan/phpstan": "^1.5",
|
||||||
|
@ -3346,9 +3436,9 @@
|
||||||
"description": "PHPDoc parser with support for nullable, intersection and generic types",
|
"description": "PHPDoc parser with support for nullable, intersection and generic types",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
|
"issues": "https://github.com/phpstan/phpdoc-parser/issues",
|
||||||
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.16.1"
|
"source": "https://github.com/phpstan/phpdoc-parser/tree/1.22.0"
|
||||||
},
|
},
|
||||||
"time": "2023-02-07T18:11:17+00:00"
|
"time": "2023-06-01T12:35:21+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-code-coverage",
|
"name": "phpunit/php-code-coverage",
|
||||||
|
@ -4071,16 +4161,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sebastian/diff",
|
"name": "sebastian/diff",
|
||||||
"version": "4.0.4",
|
"version": "4.0.5",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sebastianbergmann/diff.git",
|
"url": "https://github.com/sebastianbergmann/diff.git",
|
||||||
"reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d"
|
"reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d",
|
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131",
|
||||||
"reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d",
|
"reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -4125,7 +4215,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/sebastianbergmann/diff/issues",
|
"issues": "https://github.com/sebastianbergmann/diff/issues",
|
||||||
"source": "https://github.com/sebastianbergmann/diff/tree/4.0.4"
|
"source": "https://github.com/sebastianbergmann/diff/tree/4.0.5"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -4133,7 +4223,7 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2020-10-26T13:10:38+00:00"
|
"time": "2023-05-07T05:35:17+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sebastian/environment",
|
"name": "sebastian/environment",
|
||||||
|
@ -5100,16 +5190,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "twig/twig",
|
"name": "twig/twig",
|
||||||
"version": "v3.5.1",
|
"version": "v3.6.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/twigphp/Twig.git",
|
"url": "https://github.com/twigphp/Twig.git",
|
||||||
"reference": "a6e0510cc793912b451fd40ab983a1d28f611c15"
|
"reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a6e0510cc793912b451fd40ab983a1d28f611c15",
|
"url": "https://api.github.com/repos/twigphp/Twig/zipball/7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd",
|
||||||
"reference": "a6e0510cc793912b451fd40ab983a1d28f611c15",
|
"reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -5118,15 +5208,10 @@
|
||||||
"symfony/polyfill-mbstring": "^1.3"
|
"symfony/polyfill-mbstring": "^1.3"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"psr/container": "^1.0",
|
"psr/container": "^1.0|^2.0",
|
||||||
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0"
|
"symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-master": "3.5-dev"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Twig\\": "src/"
|
"Twig\\": "src/"
|
||||||
|
@ -5160,7 +5245,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/twigphp/Twig/issues",
|
"issues": "https://github.com/twigphp/Twig/issues",
|
||||||
"source": "https://github.com/twigphp/Twig/tree/v3.5.1"
|
"source": "https://github.com/twigphp/Twig/tree/v3.6.1"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -5172,7 +5257,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2023-02-08T07:49:20+00:00"
|
"time": "2023-06-08T12:52:13+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
|
|
|
@ -134,6 +134,7 @@ services:
|
||||||
- _APP_SMTP_SECURE
|
- _APP_SMTP_SECURE
|
||||||
- _APP_SMTP_USERNAME
|
- _APP_SMTP_USERNAME
|
||||||
- _APP_SMTP_PASSWORD
|
- _APP_SMTP_PASSWORD
|
||||||
|
- _APP_USERS_STATS_RECIPIENTS
|
||||||
- _APP_USAGE_STATS
|
- _APP_USAGE_STATS
|
||||||
- _APP_STORAGE_LIMIT
|
- _APP_STORAGE_LIMIT
|
||||||
- _APP_STORAGE_PREVIEW_LIMIT
|
- _APP_STORAGE_PREVIEW_LIMIT
|
||||||
|
@ -163,6 +164,8 @@ services:
|
||||||
- _APP_GRAPHQL_MAX_BATCH_SIZE
|
- _APP_GRAPHQL_MAX_BATCH_SIZE
|
||||||
- _APP_GRAPHQL_MAX_COMPLEXITY
|
- _APP_GRAPHQL_MAX_COMPLEXITY
|
||||||
- _APP_GRAPHQL_MAX_DEPTH
|
- _APP_GRAPHQL_MAX_DEPTH
|
||||||
|
- _APP_CONSOLE_GITHUB_APP_ID
|
||||||
|
- _APP_CONSOLE_GITHUB_SECRET
|
||||||
|
|
||||||
appwrite-realtime:
|
appwrite-realtime:
|
||||||
entrypoint: realtime
|
entrypoint: realtime
|
||||||
|
@ -478,6 +481,8 @@ services:
|
||||||
- _APP_USAGE_STATS
|
- _APP_USAGE_STATS
|
||||||
- _APP_DOCKER_HUB_USERNAME
|
- _APP_DOCKER_HUB_USERNAME
|
||||||
- _APP_DOCKER_HUB_PASSWORD
|
- _APP_DOCKER_HUB_PASSWORD
|
||||||
|
- _APP_LOGGING_CONFIG
|
||||||
|
- _APP_LOGGING_PROVIDER
|
||||||
|
|
||||||
appwrite-worker-mails:
|
appwrite-worker-mails:
|
||||||
entrypoint: worker-mails
|
entrypoint: worker-mails
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
convertWarningsToExceptions="true"
|
convertWarningsToExceptions="true"
|
||||||
processIsolation="false"
|
processIsolation="false"
|
||||||
stopOnFailure="false"
|
stopOnFailure="false"
|
||||||
>
|
>
|
||||||
<extensions>
|
<extensions>
|
||||||
<extension class="Appwrite\Tests\TestHook" />
|
<extension class="Appwrite\Tests\TestHook" />
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
BIN
public/fonts/Inter-Bold.ttf
Normal file
BIN
public/fonts/Inter-Medium.ttf
Normal file
BIN
public/fonts/Inter-Regular.ttf
Normal file
BIN
public/fonts/Inter-SemiBold.ttf
Normal file
BIN
public/fonts/Poppins-Bold.ttf
Normal file
BIN
public/fonts/SourceCodePro-Regular.ttf
Normal file
BIN
public/images/cards/cloud/back-golden.png
Normal file
After Width: | Height: | Size: 539 KiB |
BIN
public/images/cards/cloud/back-platinum.png
Normal file
After Width: | Height: | Size: 358 KiB |
BIN
public/images/cards/cloud/back.png
Normal file
After Width: | Height: | Size: 359 KiB |
BIN
public/images/cards/cloud/contributor-skew.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
public/images/cards/cloud/contributor.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
public/images/cards/cloud/employee-skew.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
public/images/cards/cloud/employee.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
public/images/cards/cloud/front-golden.png
Normal file
After Width: | Height: | Size: 500 KiB |
BIN
public/images/cards/cloud/front-platinum.png
Normal file
After Width: | Height: | Size: 353 KiB |
BIN
public/images/cards/cloud/front.png
Normal file
After Width: | Height: | Size: 193 KiB |
BIN
public/images/cards/cloud/github-skew.png
Normal file
After Width: | Height: | Size: 513 B |
BIN
public/images/cards/cloud/github.png
Normal file
After Width: | Height: | Size: 599 B |
BIN
public/images/cards/cloud/hero-skew.png
Normal file
After Width: | Height: | Size: 8 KiB |
BIN
public/images/cards/cloud/hero.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
public/images/cards/cloud/og-background-logo.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
public/images/cards/cloud/og-background1.png
Normal file
After Width: | Height: | Size: 237 KiB |
BIN
public/images/cards/cloud/og-background2.png
Normal file
After Width: | Height: | Size: 237 KiB |
BIN
public/images/cards/cloud/og-background3.png
Normal file
After Width: | Height: | Size: 256 KiB |
BIN
public/images/cards/cloud/og-card-golden1.png
Normal file
After Width: | Height: | Size: 329 KiB |
BIN
public/images/cards/cloud/og-card-golden2.png
Normal file
After Width: | Height: | Size: 306 KiB |
BIN
public/images/cards/cloud/og-card-golden3.png
Normal file
After Width: | Height: | Size: 215 KiB |
BIN
public/images/cards/cloud/og-card-platinum1.png
Normal file
After Width: | Height: | Size: 205 KiB |
BIN
public/images/cards/cloud/og-card-platinum2.png
Normal file
After Width: | Height: | Size: 186 KiB |
BIN
public/images/cards/cloud/og-card-platinum3.png
Normal file
After Width: | Height: | Size: 106 KiB |
BIN
public/images/cards/cloud/og-card1.png
Normal file
After Width: | Height: | Size: 344 KiB |
BIN
public/images/cards/cloud/og-card2.png
Normal file
After Width: | Height: | Size: 311 KiB |
BIN
public/images/cards/cloud/og-card3.png
Normal file
After Width: | Height: | Size: 230 KiB |
BIN
public/images/cards/cloud/og-shadow-golden.png
Normal file
After Width: | Height: | Size: 270 KiB |
BIN
public/images/cards/cloud/og-shadow-platinum.png
Normal file
After Width: | Height: | Size: 173 KiB |
BIN
public/images/cards/cloud/og-shadow.png
Normal file
After Width: | Height: | Size: 263 KiB |
|
@ -158,6 +158,18 @@ class Github extends OAuth2
|
||||||
return $user['name'] ?? '';
|
return $user['name'] ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $accessToken
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getUserSlug(string $accessToken): string
|
||||||
|
{
|
||||||
|
$user = $this->getUser($accessToken);
|
||||||
|
|
||||||
|
return $user['login'] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $accessToken
|
* @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 = $this->request('GET', 'https://api.github.com/user/emails', ['Authorization: token ' . \urlencode($accessToken)]);
|
||||||
|
|
||||||
$emails = \json_decode($emails, true);
|
$emails = \json_decode($emails, true);
|
||||||
|
|
||||||
|
$verifiedEmail = null;
|
||||||
|
$primaryEmail = null;
|
||||||
|
|
||||||
foreach ($emails as $email) {
|
foreach ($emails as $email) {
|
||||||
if (isset($email['verified']) && $email['verified'] === true) {
|
if (isset($email['verified']) && $email['verified'] === true) {
|
||||||
$this->user['email'] = $email['email'];
|
$verifiedEmail = $email;
|
||||||
$this->user['verified'] = $email['verified'];
|
|
||||||
break;
|
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;
|
return $this->user;
|
||||||
|
|
|
@ -7,6 +7,11 @@ use Utopia\Validator;
|
||||||
|
|
||||||
class Event extends Validator
|
class Event extends Validator
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected string $message = 'Event is not valid.';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Description.
|
* Get Description.
|
||||||
*
|
*
|
||||||
|
@ -16,7 +21,7 @@ class Event extends Validator
|
||||||
*/
|
*/
|
||||||
public function getDescription(): string
|
public function getDescription(): string
|
||||||
{
|
{
|
||||||
return 'Event is not valid.';
|
return $this->message;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,6 +45,12 @@ class Event extends Validator
|
||||||
* Identify all sections of the pattern.
|
* Identify all sections of the pattern.
|
||||||
*/
|
*/
|
||||||
$type = $parts[0] ?? false;
|
$type = $parts[0] ?? false;
|
||||||
|
|
||||||
|
if ($type == 'functions') {
|
||||||
|
$this->message = 'Triggering a function on a function event is not allowed.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$resource = $parts[1] ?? false;
|
$resource = $parts[1] ?? false;
|
||||||
$hasSubResource = $count > 3 && ($events[$type]['$resource'] ?? false) && ($events[$type][$parts[2]]['$resource'] ?? false);
|
$hasSubResource = $count > 3 && ($events[$type]['$resource'] ?? false) && ($events[$type][$parts[2]]['$resource'] ?? false);
|
||||||
$hasSubSubResource = $count > 5 && $hasSubResource && ($events[$type][$parts[2]][$parts[4]]['$resource'] ?? false);
|
$hasSubSubResource = $count > 5 && $hasSubResource && ($events[$type][$parts[2]][$parts[4]]['$resource'] ?? false);
|
||||||
|
|
|
@ -157,6 +157,7 @@ class Exception extends \Exception
|
||||||
public const PROJECT_UNKNOWN = 'project_unknown';
|
public const PROJECT_UNKNOWN = 'project_unknown';
|
||||||
public const PROJECT_PROVIDER_DISABLED = 'project_provider_disabled';
|
public const PROJECT_PROVIDER_DISABLED = 'project_provider_disabled';
|
||||||
public const PROJECT_PROVIDER_UNSUPPORTED = 'project_provider_unsupported';
|
public const PROJECT_PROVIDER_UNSUPPORTED = 'project_provider_unsupported';
|
||||||
|
public const PROJECT_ALREADY_EXISTS = 'project_already_exists';
|
||||||
public const PROJECT_INVALID_SUCCESS_URL = 'project_invalid_success_url';
|
public const PROJECT_INVALID_SUCCESS_URL = 'project_invalid_success_url';
|
||||||
public const PROJECT_INVALID_FAILURE_URL = 'project_invalid_failure_url';
|
public const PROJECT_INVALID_FAILURE_URL = 'project_invalid_failure_url';
|
||||||
public const PROJECT_RESERVED_PROJECT = 'project_reserved_project';
|
public const PROJECT_RESERVED_PROJECT = 'project_reserved_project';
|
||||||
|
@ -178,6 +179,7 @@ class Exception extends \Exception
|
||||||
/** Domain */
|
/** Domain */
|
||||||
public const DOMAIN_NOT_FOUND = 'domain_not_found';
|
public const DOMAIN_NOT_FOUND = 'domain_not_found';
|
||||||
public const DOMAIN_ALREADY_EXISTS = 'domain_already_exists';
|
public const DOMAIN_ALREADY_EXISTS = 'domain_already_exists';
|
||||||
|
public const DOMAIN_FORBIDDEN = 'domain_forbidden';
|
||||||
public const DOMAIN_VERIFICATION_FAILED = 'domain_verification_failed';
|
public const DOMAIN_VERIFICATION_FAILED = 'domain_verification_failed';
|
||||||
public const DOMAIN_TARGET_INVALID = 'domain_target_invalid';
|
public const DOMAIN_TARGET_INVALID = 'domain_target_invalid';
|
||||||
|
|
||||||
|
|
|
@ -14,10 +14,14 @@ use Appwrite\Platform\Tasks\Specs;
|
||||||
use Appwrite\Platform\Tasks\SSL;
|
use Appwrite\Platform\Tasks\SSL;
|
||||||
use Appwrite\Platform\Tasks\Hamster;
|
use Appwrite\Platform\Tasks\Hamster;
|
||||||
use Appwrite\Platform\Tasks\PatchDeleteScheduleUpdatedAtAttribute;
|
use Appwrite\Platform\Tasks\PatchDeleteScheduleUpdatedAtAttribute;
|
||||||
|
use Appwrite\Platform\Tasks\ClearCardCache;
|
||||||
use Appwrite\Platform\Tasks\Usage;
|
use Appwrite\Platform\Tasks\Usage;
|
||||||
use Appwrite\Platform\Tasks\Vars;
|
use Appwrite\Platform\Tasks\Vars;
|
||||||
use Appwrite\Platform\Tasks\Version;
|
use Appwrite\Platform\Tasks\Version;
|
||||||
use Appwrite\Platform\Tasks\VolumeSync;
|
use Appwrite\Platform\Tasks\VolumeSync;
|
||||||
|
use Appwrite\Platform\Tasks\CalcUsersStats;
|
||||||
|
use Appwrite\Platform\Tasks\CalcTierStats;
|
||||||
|
use Appwrite\Platform\Tasks\PatchDeleteProjectCollections;
|
||||||
|
|
||||||
class Tasks extends Service
|
class Tasks extends Service
|
||||||
{
|
{
|
||||||
|
@ -33,11 +37,16 @@ class Tasks extends Service
|
||||||
->addAction(Install::getName(), new Install())
|
->addAction(Install::getName(), new Install())
|
||||||
->addAction(Maintenance::getName(), new Maintenance())
|
->addAction(Maintenance::getName(), new Maintenance())
|
||||||
->addAction(PatchCreateMissingSchedules::getName(), new PatchCreateMissingSchedules())
|
->addAction(PatchCreateMissingSchedules::getName(), new PatchCreateMissingSchedules())
|
||||||
|
->addAction(ClearCardCache::getName(), new ClearCardCache())
|
||||||
->addAction(PatchDeleteScheduleUpdatedAtAttribute::getName(), new PatchDeleteScheduleUpdatedAtAttribute())
|
->addAction(PatchDeleteScheduleUpdatedAtAttribute::getName(), new PatchDeleteScheduleUpdatedAtAttribute())
|
||||||
->addAction(Schedule::getName(), new Schedule())
|
->addAction(Schedule::getName(), new Schedule())
|
||||||
->addAction(Migrate::getName(), new Migrate())
|
->addAction(Migrate::getName(), new Migrate())
|
||||||
->addAction(SDKs::getName(), new SDKs())
|
->addAction(SDKs::getName(), new SDKs())
|
||||||
->addAction(VolumeSync::getName(), new VolumeSync())
|
->addAction(VolumeSync::getName(), new VolumeSync())
|
||||||
->addAction(Specs::getName(), new Specs());
|
->addAction(Specs::getName(), new Specs())
|
||||||
|
->addAction(CalcUsersStats::getName(), new CalcUsersStats())
|
||||||
|
->addAction(CalcTierStats::getName(), new CalcTierStats())
|
||||||
|
->addAction(PatchDeleteProjectCollections::getName(), new PatchDeleteProjectCollections())
|
||||||
|
;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
351
src/Appwrite/Platform/Tasks/CalcTierStats.php
Normal file
|
@ -0,0 +1,351 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Appwrite\Platform\Tasks;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use League\Csv\CannotInsertRecord;
|
||||||
|
use Utopia\App;
|
||||||
|
use Utopia\Database\Validator\Authorization;
|
||||||
|
use Utopia\Platform\Action;
|
||||||
|
use Utopia\Cache\Cache;
|
||||||
|
use Utopia\CLI\Console;
|
||||||
|
use Utopia\Database\Database;
|
||||||
|
use Utopia\Database\Query;
|
||||||
|
use League\Csv\Writer;
|
||||||
|
use PHPMailer\PHPMailer\PHPMailer;
|
||||||
|
use Utopia\Pools\Group;
|
||||||
|
use Utopia\Registry\Registry;
|
||||||
|
|
||||||
|
class CalcTierStats extends Action
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Csv cols headers
|
||||||
|
*/
|
||||||
|
private array $columns = [
|
||||||
|
'Project ID',
|
||||||
|
'Organization ID',
|
||||||
|
'Organization Members',
|
||||||
|
'Teams',
|
||||||
|
'Users',
|
||||||
|
'Requests',
|
||||||
|
'Bandwidth',
|
||||||
|
'Domains',
|
||||||
|
'Api keys',
|
||||||
|
'Webhooks',
|
||||||
|
'Platforms',
|
||||||
|
'Buckets',
|
||||||
|
'Files',
|
||||||
|
'Storage (bytes)',
|
||||||
|
'Max File Size (bytes)',
|
||||||
|
'Databases',
|
||||||
|
'Functions',
|
||||||
|
'Deployments',
|
||||||
|
'Executions',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected string $directory = '/usr/local';
|
||||||
|
protected string $path;
|
||||||
|
protected string $date;
|
||||||
|
|
||||||
|
private array $usageStats = [
|
||||||
|
'project.$all.network.requests' => 'Requests',
|
||||||
|
'project.$all.network.bandwidth' => 'Bandwidth',
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
public static function getName(): string
|
||||||
|
{
|
||||||
|
return 'calc-tier-stats';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
|
||||||
|
$this
|
||||||
|
->desc('Get stats for projects')
|
||||||
|
->inject('pools')
|
||||||
|
->inject('cache')
|
||||||
|
->inject('dbForConsole')
|
||||||
|
->inject('register')
|
||||||
|
->callback(function (Group $pools, Cache $cache, Database $dbForConsole, Registry $register) {
|
||||||
|
$this->action($pools, $cache, $dbForConsole, $register);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Utopia\Exception
|
||||||
|
* @throws CannotInsertRecord
|
||||||
|
*/
|
||||||
|
public function action(Group $pools, Cache $cache, Database $dbForConsole, Registry $register): void
|
||||||
|
{
|
||||||
|
//docker compose exec -t appwrite calc-tier-stats
|
||||||
|
|
||||||
|
Console::title('Cloud free tier stats calculation V1');
|
||||||
|
Console::success(APP_NAME . ' cloud free tier stats calculation has started');
|
||||||
|
|
||||||
|
/* Initialise new Utopia app */
|
||||||
|
$app = new App('UTC');
|
||||||
|
$console = $app->getResource('console');
|
||||||
|
|
||||||
|
/** CSV stuff */
|
||||||
|
$this->date = date('Y-m-d');
|
||||||
|
$this->path = "{$this->directory}/tier_stats_{$this->date}.csv";
|
||||||
|
$csv = Writer::createFromPath($this->path, 'w');
|
||||||
|
$csv->insertOne($this->columns);
|
||||||
|
|
||||||
|
/** Database connections */
|
||||||
|
$totalProjects = $dbForConsole->count('projects');
|
||||||
|
Console::success("Found a total of: {$totalProjects} projects");
|
||||||
|
|
||||||
|
$projects = [$console];
|
||||||
|
$count = 0;
|
||||||
|
$limit = 30;
|
||||||
|
$sum = 30;
|
||||||
|
$offset = 0;
|
||||||
|
while (!empty($projects)) {
|
||||||
|
foreach ($projects as $project) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skip user projects with id 'console'
|
||||||
|
*/
|
||||||
|
if ($project->getId() === 'console') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console::info("Getting stats for {$project->getId()}");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = $project->getAttribute('database');
|
||||||
|
$adapter = $pools
|
||||||
|
->get($db)
|
||||||
|
->pop()
|
||||||
|
->getResource();
|
||||||
|
|
||||||
|
$dbForProject = new Database($adapter, $cache);
|
||||||
|
$dbForProject->setDefaultDatabase('appwrite');
|
||||||
|
$dbForProject->setNamespace('_' . $project->getInternalId());
|
||||||
|
|
||||||
|
/** Get Project ID */
|
||||||
|
$stats['Project ID'] = $project->getId();
|
||||||
|
|
||||||
|
$stats['Organization ID'] = $project->getAttribute('teamId', null);
|
||||||
|
|
||||||
|
/** Get Total Members */
|
||||||
|
$teamInternalId = $project->getAttribute('teamInternalId', null);
|
||||||
|
if ($teamInternalId) {
|
||||||
|
$stats['Organization Members'] = $dbForConsole->count('memberships', [
|
||||||
|
Query::equal('teamInternalId', [$teamInternalId])
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$stats['Organization Members'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get Total internal Teams */
|
||||||
|
try {
|
||||||
|
$stats['Teams'] = $dbForProject->count('teams', []);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
$stats['Teams'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get Total users */
|
||||||
|
try {
|
||||||
|
$stats['Users'] = $dbForProject->count('users', []);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
$stats['Users'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get Usage stats */
|
||||||
|
$range = '90d';
|
||||||
|
$periods = [
|
||||||
|
'90d' => [
|
||||||
|
'period' => '1d',
|
||||||
|
'limit' => 90,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$tmp = [];
|
||||||
|
$metrics = $this->usageStats;
|
||||||
|
Authorization::skip(function () use ($dbForProject, $periods, $range, $metrics, &$tmp) {
|
||||||
|
foreach ($metrics as $metric => $name) {
|
||||||
|
$limit = $periods[$range]['limit'];
|
||||||
|
$period = $periods[$range]['period'];
|
||||||
|
|
||||||
|
$requestDocs = $dbForProject->find('stats', [
|
||||||
|
Query::equal('period', [$period]),
|
||||||
|
Query::equal('metric', [$metric]),
|
||||||
|
Query::limit($limit),
|
||||||
|
Query::orderDesc('time'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$tmp[$metric] = [];
|
||||||
|
foreach ($requestDocs as $requestDoc) {
|
||||||
|
if (empty($requestDoc)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$tmp[$metric][] = [
|
||||||
|
'value' => $requestDoc->getAttribute('value'),
|
||||||
|
'date' => $requestDoc->getAttribute('time'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$tmp[$metric] = array_reverse($tmp[$metric]);
|
||||||
|
$tmp[$metric] = array_sum(array_column($tmp[$metric], 'value'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach ($tmp as $key => $value) {
|
||||||
|
$stats[$metrics[$key]] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** Get Domains */
|
||||||
|
$stats['Domains'] = $dbForConsole->count('domains', [
|
||||||
|
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||||
|
]);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
$stats['Domains'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** Get Api keys */
|
||||||
|
$stats['Api keys'] = $dbForConsole->count('keys', [
|
||||||
|
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||||
|
]);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
$stats['Api keys'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** Get Webhooks */
|
||||||
|
$stats['Webhooks'] = $dbForConsole->count('webhooks', [
|
||||||
|
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||||
|
]);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
$stats['Webhooks'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** Get Platforms */
|
||||||
|
$stats['Platforms'] = $dbForConsole->count('platforms', [
|
||||||
|
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||||
|
]);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
$stats['Platforms'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get Files & Buckets */
|
||||||
|
$filesCount = 0;
|
||||||
|
$filesSum = 0;
|
||||||
|
$maxFileSize = 0;
|
||||||
|
$counter = 0;
|
||||||
|
try {
|
||||||
|
$buckets = $dbForProject->find('buckets', []);
|
||||||
|
foreach ($buckets as $bucket) {
|
||||||
|
$file = $dbForProject->findOne('bucket_' . $bucket->getInternalId(), [Query::orderDesc('sizeOriginal'),]);
|
||||||
|
if (empty($file)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$filesSum += $dbForProject->sum('bucket_' . $bucket->getInternalId(), 'sizeOriginal', [], 0);
|
||||||
|
$filesCount += $dbForProject->count('bucket_' . $bucket->getInternalId(), []);
|
||||||
|
if ($file->getAttribute('sizeOriginal') > $maxFileSize) {
|
||||||
|
$maxFileSize = $file->getAttribute('sizeOriginal');
|
||||||
|
}
|
||||||
|
$counter++;
|
||||||
|
}
|
||||||
|
} catch (\Throwable) {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
$stats['Buckets'] = $counter;
|
||||||
|
$stats['Files'] = $filesCount;
|
||||||
|
$stats['Storage (bytes)'] = $filesSum;
|
||||||
|
$stats['Max File Size (bytes)'] = $maxFileSize;
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** Get Total Functions */
|
||||||
|
$stats['Databases'] = $dbForProject->count('databases', []);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
$stats['Databases'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get Total Functions */
|
||||||
|
try {
|
||||||
|
$stats['Functions'] = $dbForProject->count('functions', []);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
$stats['Functions'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get Total Deployments */
|
||||||
|
try {
|
||||||
|
$stats['Deployments'] = $dbForProject->count('deployments', []);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
$stats['Deployments'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get Total Executions */
|
||||||
|
try {
|
||||||
|
$stats['Executions'] = $dbForProject->count('executions', []);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
$stats['Executions'] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$csv->insertOne(array_values($stats));
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
Console::error('Failed on project ("' . $project->getId() . '") version with error on File: ' . $th->getFile() . ' line no: ' . $th->getline() . ' with message: ' . $th->getMessage());
|
||||||
|
} finally {
|
||||||
|
$pools
|
||||||
|
->get($db)
|
||||||
|
->reclaim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sum = \count($projects);
|
||||||
|
|
||||||
|
$projects = $dbForConsole->find('projects', [
|
||||||
|
Query::limit($limit),
|
||||||
|
Query::offset($offset),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$offset = $offset + $limit;
|
||||||
|
$count = $count + $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects...');
|
||||||
|
|
||||||
|
$pools
|
||||||
|
->get('console')
|
||||||
|
->reclaim();
|
||||||
|
|
||||||
|
/** @var PHPMailer $mail */
|
||||||
|
$mail = $register->get('smtp');
|
||||||
|
|
||||||
|
$mail->clearAddresses();
|
||||||
|
$mail->clearAllRecipients();
|
||||||
|
$mail->clearReplyTos();
|
||||||
|
$mail->clearAttachments();
|
||||||
|
$mail->clearBCCs();
|
||||||
|
$mail->clearCCs();
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** Addresses */
|
||||||
|
$mail->setFrom(App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), 'Appwrite Cloud Hamster');
|
||||||
|
$recipients = explode(',', App::getEnv('_APP_USERS_STATS_RECIPIENTS', ''));
|
||||||
|
|
||||||
|
foreach ($recipients as $recipient) {
|
||||||
|
$mail->addAddress($recipient);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Attachments */
|
||||||
|
$mail->addAttachment($this->path);
|
||||||
|
|
||||||
|
/** Content */
|
||||||
|
$mail->Subject = "Cloud Report for {$this->date}";
|
||||||
|
$mail->Body = "Please find the daily cloud report atttached";
|
||||||
|
$mail->send();
|
||||||
|
Console::success('Email has been sent!');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Console::error("Message could not be sent. Mailer Error: {$mail->ErrorInfo}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
176
src/Appwrite/Platform/Tasks/CalcUsersStats.php
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Appwrite\Platform\Tasks;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Utopia\App;
|
||||||
|
use Utopia\Platform\Action;
|
||||||
|
use Utopia\Cache\Cache;
|
||||||
|
use Utopia\CLI\Console;
|
||||||
|
use Utopia\Database\Database;
|
||||||
|
use Utopia\Database\Query;
|
||||||
|
use League\Csv\Writer;
|
||||||
|
use PHPMailer\PHPMailer\PHPMailer;
|
||||||
|
use Utopia\Pools\Group;
|
||||||
|
use Utopia\Registry\Registry;
|
||||||
|
|
||||||
|
class CalcUsersStats extends Action
|
||||||
|
{
|
||||||
|
private array $columns = [
|
||||||
|
'Project ID',
|
||||||
|
'Project Name',
|
||||||
|
'Team ID',
|
||||||
|
'Team name',
|
||||||
|
'Users'
|
||||||
|
];
|
||||||
|
|
||||||
|
protected string $directory = '/usr/local';
|
||||||
|
protected string $path;
|
||||||
|
protected string $date;
|
||||||
|
|
||||||
|
public static function getName(): string
|
||||||
|
{
|
||||||
|
return 'calc-users-stats';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
|
||||||
|
$this
|
||||||
|
->desc('Get stats for projects')
|
||||||
|
->inject('pools')
|
||||||
|
->inject('cache')
|
||||||
|
->inject('dbForConsole')
|
||||||
|
->inject('register')
|
||||||
|
->callback(function (Group $pools, Cache $cache, Database $dbForConsole, Registry $register) {
|
||||||
|
$this->action($pools, $cache, $dbForConsole, $register);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function action(Group $pools, Cache $cache, Database $dbForConsole, Registry $register): void
|
||||||
|
{
|
||||||
|
//docker compose exec -t appwrite calc-users-stats
|
||||||
|
|
||||||
|
Console::title('Cloud Users calculation V1');
|
||||||
|
Console::success(APP_NAME . ' cloud Users calculation has started');
|
||||||
|
|
||||||
|
/* Initialise new Utopia app */
|
||||||
|
$app = new App('UTC');
|
||||||
|
$console = $app->getResource('console');
|
||||||
|
|
||||||
|
/** CSV stuff */
|
||||||
|
$this->date = date('Y-m-d');
|
||||||
|
$this->path = "{$this->directory}/users_stats_{$this->date}.csv";
|
||||||
|
$csv = Writer::createFromPath($this->path, 'w');
|
||||||
|
$csv->insertOne($this->columns);
|
||||||
|
|
||||||
|
/** Database connections */
|
||||||
|
$totalProjects = $dbForConsole->count('projects');
|
||||||
|
Console::success("Found a total of: {$totalProjects} projects");
|
||||||
|
|
||||||
|
$projects = [$console];
|
||||||
|
$count = 0;
|
||||||
|
$limit = 30;
|
||||||
|
$sum = 30;
|
||||||
|
$offset = 0;
|
||||||
|
while (!empty($projects)) {
|
||||||
|
foreach ($projects as $project) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skip user projects with id 'console'
|
||||||
|
*/
|
||||||
|
if ($project->getId() === 'console') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console::info("Getting stats for {$project->getId()}");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = $project->getAttribute('database');
|
||||||
|
$adapter = $pools
|
||||||
|
->get($db)
|
||||||
|
->pop()
|
||||||
|
->getResource();
|
||||||
|
|
||||||
|
$dbForProject = new Database($adapter, $cache);
|
||||||
|
$dbForProject->setDefaultDatabase('appwrite');
|
||||||
|
$dbForProject->setNamespace('_' . $project->getInternalId());
|
||||||
|
|
||||||
|
/** Get Project ID */
|
||||||
|
$stats['Project ID'] = $project->getId();
|
||||||
|
|
||||||
|
/** Get Project Name */
|
||||||
|
$stats['Project Name'] = $project->getAttribute('name');
|
||||||
|
|
||||||
|
|
||||||
|
/** Get Team Name and Id */
|
||||||
|
$teamId = $project->getAttribute('teamId', null);
|
||||||
|
$teamName = null;
|
||||||
|
if ($teamId) {
|
||||||
|
$team = $dbForConsole->getDocument('teams', $teamId);
|
||||||
|
$teamName = $team->getAttribute('name');
|
||||||
|
}
|
||||||
|
|
||||||
|
$stats['Team ID'] = $teamId;
|
||||||
|
$stats['Team name'] = $teamName;
|
||||||
|
|
||||||
|
/** Get Total Users */
|
||||||
|
$stats['users'] = $dbForProject->count('users', []);
|
||||||
|
|
||||||
|
$csv->insertOne(array_values($stats));
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
Console::error('Failed to update project ("' . $project->getId() . '") version with error: ' . $th->getMessage());
|
||||||
|
} finally {
|
||||||
|
$pools
|
||||||
|
->get($db)
|
||||||
|
->reclaim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sum = \count($projects);
|
||||||
|
|
||||||
|
$projects = $dbForConsole->find('projects', [
|
||||||
|
Query::limit($limit),
|
||||||
|
Query::offset($offset),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$offset = $offset + $limit;
|
||||||
|
$count = $count + $sum;
|
||||||
|
}
|
||||||
|
Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects...');
|
||||||
|
$pools
|
||||||
|
->get('console')
|
||||||
|
->reclaim();
|
||||||
|
|
||||||
|
/** @var PHPMailer $mail */
|
||||||
|
$mail = $register->get('smtp');
|
||||||
|
|
||||||
|
$mail->clearAddresses();
|
||||||
|
$mail->clearAllRecipients();
|
||||||
|
$mail->clearReplyTos();
|
||||||
|
$mail->clearAttachments();
|
||||||
|
$mail->clearBCCs();
|
||||||
|
$mail->clearCCs();
|
||||||
|
|
||||||
|
try {
|
||||||
|
/** Addresses */
|
||||||
|
$mail->setFrom(App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), 'Appwrite Cloud Hamster');
|
||||||
|
$recipients = explode(',', App::getEnv('_APP_USERS_STATS_RECIPIENTS', ''));
|
||||||
|
|
||||||
|
foreach ($recipients as $recipient) {
|
||||||
|
$mail->addAddress($recipient);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Attachments */
|
||||||
|
$mail->addAttachment($this->path);
|
||||||
|
|
||||||
|
/** Content */
|
||||||
|
$mail->Subject = "Cloud Report for {$this->date}";
|
||||||
|
$mail->Body = "Please find the daily cloud report atttached";
|
||||||
|
$mail->send();
|
||||||
|
Console::success('Email has been sent!');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
Console::error("Message could not be sent. Mailer Error: {$mail->ErrorInfo}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
src/Appwrite/Platform/Tasks/ClearCardCache.php
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Appwrite\Platform\Tasks;
|
||||||
|
|
||||||
|
use Utopia\Cache\Adapter\Filesystem;
|
||||||
|
use Utopia\Cache\Cache;
|
||||||
|
use Utopia\Platform\Action;
|
||||||
|
use Utopia\CLI\Console;
|
||||||
|
use Utopia\Database\Query;
|
||||||
|
use Utopia\Database\Database;
|
||||||
|
use Utopia\Database\Validator\Authorization;
|
||||||
|
use Utopia\Database\Validator\UID;
|
||||||
|
|
||||||
|
class ClearCardCache extends Action
|
||||||
|
{
|
||||||
|
public static function getName(): string
|
||||||
|
{
|
||||||
|
return 'clear-card-cache';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->desc('Deletes card cache for specific user')
|
||||||
|
->param('userId', '', new UID(), 'User UID.', false)
|
||||||
|
->inject('dbForConsole')
|
||||||
|
->callback(fn (string $userId, Database $dbForConsole) => $this->action($userId, $dbForConsole));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function action(string $userId, Database $dbForConsole): void
|
||||||
|
{
|
||||||
|
Authorization::disable();
|
||||||
|
Authorization::setDefaultStatus(false);
|
||||||
|
|
||||||
|
Console::title('ClearCardCache V1');
|
||||||
|
Console::success(APP_NAME . ' ClearCardCache v1 has started');
|
||||||
|
$resources = ['card/' . $userId, 'card-back/' . $userId, 'card-og/' . $userId];
|
||||||
|
|
||||||
|
$caches = Authorization::skip(fn () => $dbForConsole->find('cache', [
|
||||||
|
Query::equal('resource', $resources),
|
||||||
|
Query::limit(100)
|
||||||
|
]));
|
||||||
|
|
||||||
|
$count = \count($caches);
|
||||||
|
Console::info("Going to delete {$count} cache records in 10 seconds...");
|
||||||
|
\sleep(10);
|
||||||
|
|
||||||
|
foreach ($caches as $cache) {
|
||||||
|
$key = $cache->getId();
|
||||||
|
|
||||||
|
$cacheFolder = new Cache(
|
||||||
|
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-console')
|
||||||
|
);
|
||||||
|
|
||||||
|
$cacheFolder->purge($key);
|
||||||
|
|
||||||
|
Authorization::skip(fn () => $dbForConsole->deleteDocument('cache', $cache->getId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Console::success(APP_NAME . ' ClearCardCache v1 has finished');
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace Appwrite\Platform\Tasks;
|
namespace Appwrite\Platform\Tasks;
|
||||||
|
|
||||||
|
use Appwrite\Network\Validator\Origin;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Utopia\App;
|
use Utopia\App;
|
||||||
use Utopia\Platform\Action;
|
use Utopia\Platform\Action;
|
||||||
|
@ -12,6 +13,7 @@ use Utopia\Database\Query;
|
||||||
use Utopia\Database\Validator\Authorization;
|
use Utopia\Database\Validator\Authorization;
|
||||||
use Utopia\Analytics\Adapter\Mixpanel;
|
use Utopia\Analytics\Adapter\Mixpanel;
|
||||||
use Utopia\Analytics\Event;
|
use Utopia\Analytics\Event;
|
||||||
|
use Utopia\Config\Config;
|
||||||
use Utopia\Database\Document;
|
use Utopia\Database\Document;
|
||||||
use Utopia\Pools\Group;
|
use Utopia\Pools\Group;
|
||||||
|
|
||||||
|
@ -98,6 +100,12 @@ class Hamster extends Action
|
||||||
/** Get Total Functions */
|
/** Get Total Functions */
|
||||||
$statsPerProject['custom_functions'] = $dbForProject->count('functions', [], APP_LIMIT_COUNT);
|
$statsPerProject['custom_functions'] = $dbForProject->count('functions', [], APP_LIMIT_COUNT);
|
||||||
|
|
||||||
|
foreach (\array_keys(Config::getParam('runtimes')) as $runtime) {
|
||||||
|
$statsPerProject['custom_functions_' . $runtime] = $dbForProject->count('functions', [
|
||||||
|
Query::equal('runtime', [$runtime]),
|
||||||
|
], APP_LIMIT_COUNT);
|
||||||
|
}
|
||||||
|
|
||||||
/** Get Total Deployments */
|
/** Get Total Deployments */
|
||||||
$statsPerProject['custom_deployments'] = $dbForProject->count('deployments', [], APP_LIMIT_COUNT);
|
$statsPerProject['custom_deployments'] = $dbForProject->count('deployments', [], APP_LIMIT_COUNT);
|
||||||
|
|
||||||
|
@ -136,7 +144,10 @@ class Hamster extends Action
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get Domains */
|
/** Get Domains */
|
||||||
$statsPerProject['custom_domains'] = $dbForProject->count('domains', [], APP_LIMIT_COUNT);
|
$statsPerProject['custom_domains'] = $dbForConsole->count('domains', [
|
||||||
|
Query::equal('projectInternalId', [$project->getInternalId()]),
|
||||||
|
Query::limit(APP_LIMIT_COUNT)
|
||||||
|
]);
|
||||||
|
|
||||||
/** Get Platforms */
|
/** Get Platforms */
|
||||||
$platforms = $dbForConsole->find('platforms', [
|
$platforms = $dbForConsole->find('platforms', [
|
||||||
|
@ -152,7 +163,7 @@ class Hamster extends Action
|
||||||
return $platform['type'] === 'android';
|
return $platform['type'] === 'android';
|
||||||
}));
|
}));
|
||||||
|
|
||||||
$statsPerProject['custom_platforms_iOS'] = sizeof(array_filter($platforms, function ($platform) {
|
$statsPerProject['custom_platforms_apple'] = sizeof(array_filter($platforms, function ($platform) {
|
||||||
return str_contains($platform['type'], 'apple');
|
return str_contains($platform['type'], 'apple');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -160,6 +171,19 @@ class Hamster extends Action
|
||||||
return str_contains($platform['type'], 'flutter');
|
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 */
|
/** Get Usage $statsPerProject */
|
||||||
$periods = [
|
$periods = [
|
||||||
'infinity' => [
|
'infinity' => [
|
||||||
|
|
129
src/Appwrite/Platform/Tasks/PatchDeleteProjectCollections.php
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Appwrite\Platform\Tasks;
|
||||||
|
|
||||||
|
use Utopia\App;
|
||||||
|
use Utopia\Platform\Action;
|
||||||
|
use Utopia\Cache\Cache;
|
||||||
|
use Utopia\CLI\Console;
|
||||||
|
use Utopia\Database\Database;
|
||||||
|
use Utopia\Database\Query;
|
||||||
|
use Utopia\Pools\Group;
|
||||||
|
use Utopia\Validator\Numeric;
|
||||||
|
|
||||||
|
class PatchDeleteProjectCollections extends Action
|
||||||
|
{
|
||||||
|
private array $names = [
|
||||||
|
'webhooks',
|
||||||
|
'platforms',
|
||||||
|
'schedules',
|
||||||
|
'projects',
|
||||||
|
'domains',
|
||||||
|
'certificates',
|
||||||
|
'keys',
|
||||||
|
'realtime',
|
||||||
|
];
|
||||||
|
|
||||||
|
public static function getName(): string
|
||||||
|
{
|
||||||
|
return 'patch-delete-project-collections';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
|
||||||
|
$this
|
||||||
|
->desc('Delete unnecessary project collections')
|
||||||
|
->param('offset', 0, new Numeric(), 'Resume deletion from param pos', true)
|
||||||
|
->inject('pools')
|
||||||
|
->inject('cache')
|
||||||
|
->inject('dbForConsole')
|
||||||
|
->callback(function (int $offset, Group $pools, Cache $cache, Database $dbForConsole) {
|
||||||
|
$this->action($offset, $pools, $cache, $dbForConsole);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function action(int $offset, Group $pools, Cache $cache, Database $dbForConsole): void
|
||||||
|
{
|
||||||
|
//docker compose exec -t appwrite patch-delete-project-collections
|
||||||
|
|
||||||
|
Console::title('Delete project collections V1');
|
||||||
|
Console::success(APP_NAME . ' delete project collections has started');
|
||||||
|
|
||||||
|
/* Initialise new Utopia app */
|
||||||
|
$app = new App('UTC');
|
||||||
|
$console = $app->getResource('console');
|
||||||
|
|
||||||
|
/** Database connections */
|
||||||
|
$totalProjects = $dbForConsole->count('projects');
|
||||||
|
Console::success("Found a total of: {$totalProjects} projects");
|
||||||
|
|
||||||
|
$projects = [$console];
|
||||||
|
$count = 0;
|
||||||
|
$limit = 50;
|
||||||
|
$sum = 50;
|
||||||
|
$offset = $offset;
|
||||||
|
while (!empty($projects)) {
|
||||||
|
foreach ($projects as $project) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skip user projects with id 'console'
|
||||||
|
*/
|
||||||
|
if ($project->getId() === 'console') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console::info("Deleting collections for {$project->getId()}");
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = $project->getAttribute('database');
|
||||||
|
$adapter = $pools
|
||||||
|
->get($db)
|
||||||
|
->pop()
|
||||||
|
->getResource();
|
||||||
|
|
||||||
|
$dbForProject = new Database($adapter, $cache);
|
||||||
|
$dbForProject->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
|
||||||
|
$dbForProject->setNamespace('_' . $project->getInternalId());
|
||||||
|
|
||||||
|
foreach ($this->names as $name) {
|
||||||
|
if (empty($name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($dbForProject->exists(App::getEnv('_APP_DB_SCHEMA', 'appwrite'), $name)) {
|
||||||
|
if ($dbForProject->deleteCollection($name)) {
|
||||||
|
Console::log('Deleted ' . $name);
|
||||||
|
} else {
|
||||||
|
Console::error('Failed to delete ' . $name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
Console::error('Failed on project ("' . $project->getId() . '") version with error: ' . $th->getMessage());
|
||||||
|
} finally {
|
||||||
|
$pools
|
||||||
|
->get($db)
|
||||||
|
->reclaim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sum = \count($projects);
|
||||||
|
|
||||||
|
$projects = $dbForConsole->find('projects', [
|
||||||
|
Query::limit($limit),
|
||||||
|
Query::offset($offset),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!empty($projects)) {
|
||||||
|
Console::log('Querying..... offset=' . $offset . ' , limit=' . $limit . ', count=' . $count);
|
||||||
|
}
|
||||||
|
|
||||||
|
$offset = $offset + $limit;
|
||||||
|
$count = $count + $sum;
|
||||||
|
}
|
||||||
|
Console::log('Iterated through ' . $count - 1 . '/' . $totalProjects . ' projects...');
|
||||||
|
$pools
|
||||||
|
->get('console')
|
||||||
|
->reclaim();
|
||||||
|
}
|
||||||
|
}
|
|
@ -174,6 +174,7 @@ abstract class Worker
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
protected static $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
|
protected static $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
|
||||||
|
|
||||||
protected function getProjectDB(Document $project): Database
|
protected function getProjectDB(Document $project): Database
|
||||||
{
|
{
|
||||||
global $register;
|
global $register;
|
||||||
|
@ -218,6 +219,14 @@ abstract class Worker
|
||||||
|
|
||||||
$pools = $register->get('pools'); /** @var Group $pools */
|
$pools = $register->get('pools'); /** @var Group $pools */
|
||||||
|
|
||||||
|
$databaseName = 'console';
|
||||||
|
|
||||||
|
if (isset(self::$databases[$databaseName])) {
|
||||||
|
$database = self::$databases[$databaseName];
|
||||||
|
$database->setNamespace('console');
|
||||||
|
return $database;
|
||||||
|
}
|
||||||
|
|
||||||
$dbAdapter = $pools
|
$dbAdapter = $pools
|
||||||
->get('console')
|
->get('console')
|
||||||
->pop()
|
->pop()
|
||||||
|
@ -226,6 +235,8 @@ abstract class Worker
|
||||||
|
|
||||||
$database = new Database($dbAdapter, $this->getCache());
|
$database = new Database($dbAdapter, $this->getCache());
|
||||||
|
|
||||||
|
self::$databases[$databaseName] = $database;
|
||||||
|
|
||||||
$database->setNamespace('console');
|
$database->setNamespace('console');
|
||||||
|
|
||||||
return $database;
|
return $database;
|
||||||
|
|
|
@ -22,7 +22,9 @@ class Base extends Queries
|
||||||
*/
|
*/
|
||||||
public function __construct(string $collection, array $allowedAttributes)
|
public function __construct(string $collection, array $allowedAttributes)
|
||||||
{
|
{
|
||||||
$collection = Config::getParam('collections', [])[$collection];
|
$config = Config::getParam('collections', []);
|
||||||
|
$collections = array_merge($config['console'], $config['projects'], $config['buckets'], $config['databases']);
|
||||||
|
$collection = $collections[$collection];
|
||||||
// array for constant lookup time
|
// array for constant lookup time
|
||||||
$allowedAttributesLookup = [];
|
$allowedAttributesLookup = [];
|
||||||
foreach ($allowedAttributes as $attribute) {
|
foreach ($allowedAttributes as $attribute) {
|
||||||
|
|
|
@ -160,17 +160,14 @@ class Client
|
||||||
* @param array $params
|
* @param array $params
|
||||||
* @param array $headers
|
* @param array $headers
|
||||||
* @param bool $decode
|
* @param bool $decode
|
||||||
* @return array|string
|
* @return array
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function call(string $method, string $path = '', array $headers = [], array $params = [], bool $decode = true)
|
public function call(string $method, string $path = '', array $headers = [], array $params = [], bool $decode = true): array
|
||||||
{
|
{
|
||||||
$headers = array_merge($this->headers, $headers);
|
$headers = array_merge($this->headers, $headers);
|
||||||
$ch = curl_init($this->endpoint . $path . (($method == self::METHOD_GET && !empty($params)) ? '?' . http_build_query($params) : ''));
|
$ch = curl_init($this->endpoint . $path . (($method == self::METHOD_GET && !empty($params)) ? '?' . http_build_query($params) : ''));
|
||||||
$responseHeaders = [];
|
$responseHeaders = [];
|
||||||
$responseStatus = -1;
|
|
||||||
$responseType = '';
|
|
||||||
$responseBody = '';
|
|
||||||
|
|
||||||
switch ($headers['content-type']) {
|
switch ($headers['content-type']) {
|
||||||
case 'application/json':
|
case 'application/json':
|
||||||
|
@ -220,7 +217,7 @@ class Client
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow self signed certificates
|
// Allow self-signed certificates
|
||||||
if ($this->selfSigned) {
|
if ($this->selfSigned) {
|
||||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||||
|
@ -230,22 +227,18 @@ class Client
|
||||||
$responseType = $responseHeaders['content-type'] ?? '';
|
$responseType = $responseHeaders['content-type'] ?? '';
|
||||||
$responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
$responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
|
||||||
if ($decode) {
|
if ($decode && substr($responseType, 0, strpos($responseType, ';')) == 'application/json') {
|
||||||
switch (substr($responseType, 0, strpos($responseType, ';'))) {
|
$json = json_decode($responseBody, true);
|
||||||
case 'application/json':
|
|
||||||
$json = json_decode($responseBody, true);
|
|
||||||
|
|
||||||
if ($json === null) {
|
if ($json === null) {
|
||||||
throw new Exception('Failed to parse response: ' . $responseBody);
|
throw new Exception('Failed to parse response: ' . $responseBody);
|
||||||
}
|
|
||||||
|
|
||||||
$responseBody = $json;
|
|
||||||
$json = null;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$responseBody = $json;
|
||||||
|
$json = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((curl_errno($ch)/* || 200 != $responseStatus*/)) {
|
if ((curl_errno($ch))) {
|
||||||
throw new Exception(curl_error($ch) . ' with status code ' . $responseStatus, $responseStatus);
|
throw new Exception(curl_error($ch) . ' with status code ' . $responseStatus, $responseStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,7 +266,7 @@ class Client
|
||||||
{
|
{
|
||||||
$cookies = [];
|
$cookies = [];
|
||||||
|
|
||||||
parse_str(strtr($cookie, array('&' => '%26', '+' => '%2B', ';' => '&')), $cookies);
|
parse_str(strtr($cookie, ['&' => '%26', '+' => '%2B', ';' => '&']), $cookies);
|
||||||
|
|
||||||
return $cookies;
|
return $cookies;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,12 @@ class HTTPTest extends Scope
|
||||||
use ProjectNone;
|
use ProjectNone;
|
||||||
use SideNone;
|
use SideNone;
|
||||||
|
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->client->setEndpoint('http://localhost');
|
||||||
|
}
|
||||||
|
|
||||||
public function testOptions()
|
public function testOptions()
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -32,24 +38,6 @@ class HTTPTest extends Scope
|
||||||
$this->assertEmpty($response['body']);
|
$this->assertEmpty($response['body']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testError()
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Test for SUCCESS
|
|
||||||
*/
|
|
||||||
$this->markTestIncomplete('This test needs to be updated for the new console.');
|
|
||||||
// $response = $this->client->call(Client::METHOD_GET, '/error', \array_merge([
|
|
||||||
// 'origin' => 'http://localhost',
|
|
||||||
// 'content-type' => 'application/json',
|
|
||||||
// ]), []);
|
|
||||||
|
|
||||||
// $this->assertEquals(404, $response['headers']['status-code']);
|
|
||||||
// $this->assertEquals('Not Found', $response['body']['message']);
|
|
||||||
// $this->assertEquals(Exception::GENERAL_ROUTE_NOT_FOUND, $response['body']['type']);
|
|
||||||
// $this->assertEquals(404, $response['body']['code']);
|
|
||||||
// $this->assertEquals('dev', $response['body']['version']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testHumans()
|
public function testHumans()
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
|
@ -57,7 +45,7 @@ class HTTPTest extends Scope
|
||||||
*/
|
*/
|
||||||
$response = $this->client->call(Client::METHOD_GET, '/humans.txt', \array_merge([
|
$response = $this->client->call(Client::METHOD_GET, '/humans.txt', \array_merge([
|
||||||
'origin' => 'http://localhost',
|
'origin' => 'http://localhost',
|
||||||
]), []);
|
]));
|
||||||
|
|
||||||
$this->assertEquals(200, $response['headers']['status-code']);
|
$this->assertEquals(200, $response['headers']['status-code']);
|
||||||
$this->assertStringContainsString('# humanstxt.org/', $response['body']);
|
$this->assertStringContainsString('# humanstxt.org/', $response['body']);
|
||||||
|
@ -70,7 +58,7 @@ class HTTPTest extends Scope
|
||||||
*/
|
*/
|
||||||
$response = $this->client->call(Client::METHOD_GET, '/robots.txt', \array_merge([
|
$response = $this->client->call(Client::METHOD_GET, '/robots.txt', \array_merge([
|
||||||
'origin' => 'http://localhost',
|
'origin' => 'http://localhost',
|
||||||
]), []);
|
]));
|
||||||
|
|
||||||
$this->assertEquals(200, $response['headers']['status-code']);
|
$this->assertEquals(200, $response['headers']['status-code']);
|
||||||
$this->assertStringContainsString('# robotstxt.org/', $response['body']);
|
$this->assertStringContainsString('# robotstxt.org/', $response['body']);
|
||||||
|
@ -87,7 +75,7 @@ class HTTPTest extends Scope
|
||||||
*/
|
*/
|
||||||
$response = $this->client->call(Client::METHOD_GET, '/.well-known/acme-challenge/8DdIKX257k6Dih5s_saeVMpTnjPJdKO5Ase0OCiJrIg', \array_merge([
|
$response = $this->client->call(Client::METHOD_GET, '/.well-known/acme-challenge/8DdIKX257k6Dih5s_saeVMpTnjPJdKO5Ase0OCiJrIg', \array_merge([
|
||||||
'origin' => 'http://localhost',
|
'origin' => 'http://localhost',
|
||||||
]), []);
|
]));
|
||||||
|
|
||||||
$this->assertEquals(404, $response['headers']['status-code']);
|
$this->assertEquals(404, $response['headers']['status-code']);
|
||||||
// 'Unknown path', but validation passed
|
// 'Unknown path', but validation passed
|
||||||
|
@ -97,7 +85,7 @@ class HTTPTest extends Scope
|
||||||
*/
|
*/
|
||||||
$response = $this->client->call(Client::METHOD_GET, '/.well-known/acme-challenge/../../../../../../../etc/passwd', \array_merge([
|
$response = $this->client->call(Client::METHOD_GET, '/.well-known/acme-challenge/../../../../../../../etc/passwd', \array_merge([
|
||||||
'origin' => 'http://localhost',
|
'origin' => 'http://localhost',
|
||||||
]), []);
|
]));
|
||||||
|
|
||||||
$this->assertEquals(400, $response['headers']['status-code']);
|
$this->assertEquals(400, $response['headers']['status-code']);
|
||||||
|
|
||||||
|
@ -171,4 +159,15 @@ class HTTPTest extends Scope
|
||||||
$this->assertIsString($body['server-ruby']);
|
$this->assertIsString($body['server-ruby']);
|
||||||
$this->assertIsString($body['console-cli']);
|
$this->assertIsString($body['console-cli']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testDefaultOAuth2()
|
||||||
|
{
|
||||||
|
$response = $this->client->call(Client::METHOD_GET, '/auth/oauth2/success', $this->getHeaders());
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response['headers']['status-code']);
|
||||||
|
|
||||||
|
$response = $this->client->call(Client::METHOD_GET, '/auth/oauth2/failure', $this->getHeaders());
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response['headers']['status-code']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,7 @@ trait ProjectCustom
|
||||||
'name' => 'Webhook Test',
|
'name' => 'Webhook Test',
|
||||||
'events' => [
|
'events' => [
|
||||||
'databases.*',
|
'databases.*',
|
||||||
'functions.*',
|
// 'functions.*', TODO @christyjacob4 : enable test once we allow functions.* events
|
||||||
'buckets.*',
|
'buckets.*',
|
||||||
'teams.*',
|
'teams.*',
|
||||||
'users.*'
|
'users.*'
|
||||||
|
|
|
@ -17,10 +17,7 @@ abstract class Scope extends TestCase
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
$this->client = new Client();
|
$this->client = new Client();
|
||||||
|
$this->client->setEndpoint($this->endpoint);
|
||||||
$this->client
|
|
||||||
->setEndpoint($this->endpoint)
|
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
|
@ -45,10 +42,10 @@ abstract class Scope extends TestCase
|
||||||
{
|
{
|
||||||
sleep(2);
|
sleep(2);
|
||||||
|
|
||||||
$resquest = json_decode(file_get_contents('http://request-catcher:5000/__last_request__'), true);
|
$request = json_decode(file_get_contents('http://request-catcher:5000/__last_request__'), true);
|
||||||
$resquest['data'] = json_decode($resquest['data'], true);
|
$request['data'] = json_decode($request['data'], true);
|
||||||
|
|
||||||
return $resquest;
|
return $request;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -252,6 +252,8 @@ class AccountCustomClientTest extends Scope
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->assertEquals($response['headers']['status-code'], 200);
|
$this->assertEquals($response['headers']['status-code'], 200);
|
||||||
|
$this->assertStringContainsString('a_session_' . $this->getProject()['$id'] . '=deleted', $response['headers']['set-cookie']);
|
||||||
|
$this->assertEquals('[]', $response['headers']['x-fallback-cookies']);
|
||||||
|
|
||||||
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
$response = $this->client->call(Client::METHOD_GET, '/account', array_merge([
|
||||||
'origin' => 'http://localhost',
|
'origin' => 'http://localhost',
|
||||||
|
|
|
@ -981,6 +981,12 @@ trait DatabasesBase
|
||||||
|
|
||||||
$this->assertEquals(400, $document4['headers']['status-code']);
|
$this->assertEquals(400, $document4['headers']['status-code']);
|
||||||
|
|
||||||
|
// Delete document 4 with incomplete path
|
||||||
|
$this->assertEquals(404, $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
], $this->getHeaders()))['headers']['status-code']);
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2869,7 +2875,7 @@ trait DatabasesBase
|
||||||
$databaseId = $database['body']['$id'];
|
$databaseId = $database['body']['$id'];
|
||||||
|
|
||||||
// Create collection
|
// Create collection
|
||||||
$movies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/', array_merge([
|
$movies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
|
||||||
'content-type' => 'application/json',
|
'content-type' => 'application/json',
|
||||||
'x-appwrite-project' => $this->getProject()['$id'],
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
'x-appwrite-key' => $this->getProject()['apiKey']
|
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Tests\E2E\Services\Projects;
|
namespace Tests\E2E\Services\Projects;
|
||||||
|
|
||||||
use Appwrite\Auth\Auth;
|
use Appwrite\Auth\Auth;
|
||||||
|
use Appwrite\Extend\Exception;
|
||||||
use Tests\E2E\Scopes\Scope;
|
use Tests\E2E\Scopes\Scope;
|
||||||
use Tests\E2E\Scopes\ProjectConsole;
|
use Tests\E2E\Scopes\ProjectConsole;
|
||||||
use Tests\E2E\Scopes\SideClient;
|
use Tests\E2E\Scopes\SideClient;
|
||||||
|
@ -96,7 +97,37 @@ class ProjectsConsoleClientTest extends Scope
|
||||||
|
|
||||||
$this->assertEquals(400, $response['headers']['status-code']);
|
$this->assertEquals(400, $response['headers']['status-code']);
|
||||||
|
|
||||||
return ['projectId' => $projectId];
|
return [
|
||||||
|
'projectId' => $projectId,
|
||||||
|
'teamId' => $team['body']['$id']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testCreateProject
|
||||||
|
*/
|
||||||
|
public function testCreateDuplicateProject($data)
|
||||||
|
{
|
||||||
|
$teamId = $data['teamId'] ?? '';
|
||||||
|
$projectId = $data['projectId'] ?? '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for FAILURE
|
||||||
|
*/
|
||||||
|
$response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
], $this->getHeaders()), [
|
||||||
|
'projectId' => $projectId,
|
||||||
|
'name' => 'Project Duplicate',
|
||||||
|
'teamId' => $teamId,
|
||||||
|
'region' => 'default'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals(409, $response['headers']['status-code']);
|
||||||
|
$this->assertEquals(409, $response['body']['code']);
|
||||||
|
$this->assertEquals(Exception::PROJECT_ALREADY_EXISTS, $response['body']['type']);
|
||||||
|
$this->assertEquals('Project with the requested ID already exists.', $response['body']['message']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -781,11 +812,11 @@ class ProjectsConsoleClientTest extends Scope
|
||||||
|
|
||||||
$this->assertEquals($response['headers']['status-code'], 501);
|
$this->assertEquals($response['headers']['status-code'], 501);
|
||||||
|
|
||||||
$response = $this->client->call(Client::METHOD_POST, '/account/anonymous', array_merge([
|
$response = $this->client->call(Client::METHOD_POST, '/account/sessions/anonymous', array_merge([
|
||||||
'origin' => 'http://localhost',
|
'origin' => 'http://localhost',
|
||||||
'content-type' => 'application/json',
|
'content-type' => 'application/json',
|
||||||
'x-appwrite-project' => $id,
|
'x-appwrite-project' => $id,
|
||||||
]), []);
|
]));
|
||||||
|
|
||||||
$this->assertEquals($response['headers']['status-code'], 501);
|
$this->assertEquals($response['headers']['status-code'], 501);
|
||||||
|
|
||||||
|
@ -841,6 +872,19 @@ class ProjectsConsoleClientTest extends Scope
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
$email = uniqid() . 'user@localhost.test';
|
||||||
|
|
||||||
|
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
||||||
|
'origin' => 'http://localhost',
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $id,
|
||||||
|
]), [
|
||||||
|
'userId' => ID::unique(),
|
||||||
|
'email' => $email,
|
||||||
|
'password' => $password,
|
||||||
|
'name' => $name,
|
||||||
|
]);
|
||||||
|
|
||||||
$this->assertEquals($response['headers']['status-code'], 501);
|
$this->assertEquals($response['headers']['status-code'], 501);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -856,6 +900,8 @@ class ProjectsConsoleClientTest extends Scope
|
||||||
$this->assertEquals(200, $response['headers']['status-code']);
|
$this->assertEquals(200, $response['headers']['status-code']);
|
||||||
$this->assertNotEmpty($response['body']['$id']);
|
$this->assertNotEmpty($response['body']['$id']);
|
||||||
|
|
||||||
|
$email = uniqid() . 'user@localhost.test';
|
||||||
|
|
||||||
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
|
||||||
'origin' => 'http://localhost',
|
'origin' => 'http://localhost',
|
||||||
'content-type' => 'application/json',
|
'content-type' => 'application/json',
|
||||||
|
|
|
@ -497,7 +497,7 @@ class RealtimeConsoleClientTest extends Scope
|
||||||
$this->assertArrayHasKey('timestamp', $response['data']);
|
$this->assertArrayHasKey('timestamp', $response['data']);
|
||||||
$this->assertCount(1, $response['data']['channels']);
|
$this->assertCount(1, $response['data']['channels']);
|
||||||
$this->assertContains('console', $response['data']['channels']);
|
$this->assertContains('console', $response['data']['channels']);
|
||||||
$this->assertContains("functions.{$functionId}.deployments.{$deploymentId}.create", $response['data']['events']);
|
// $this->assertContains("functions.{$functionId}.deployments.{$deploymentId}.create", $response['data']['events']); TODO @christyjacob4 : enable test once we allow functions.* events
|
||||||
$this->assertNotEmpty($response['data']['payload']);
|
$this->assertNotEmpty($response['data']['payload']);
|
||||||
|
|
||||||
$client->close();
|
$client->close();
|
||||||
|
|
|
@ -415,10 +415,10 @@ class WebhooksCustomServerTest extends Scope
|
||||||
$this->assertEquals($webhook['method'], 'POST');
|
$this->assertEquals($webhook['method'], 'POST');
|
||||||
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
|
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
|
||||||
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
|
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
|
||||||
$this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString('functions.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.create", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
||||||
|
@ -474,10 +474,10 @@ class WebhooksCustomServerTest extends Scope
|
||||||
$this->assertEquals($webhook['method'], 'POST');
|
$this->assertEquals($webhook['method'], 'POST');
|
||||||
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
|
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
|
||||||
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
|
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
|
||||||
$this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString('functions.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
||||||
|
@ -529,12 +529,12 @@ class WebhooksCustomServerTest extends Scope
|
||||||
$this->assertEquals($webhook['method'], 'POST');
|
$this->assertEquals($webhook['method'], 'POST');
|
||||||
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
|
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
|
||||||
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
|
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
|
||||||
$this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
||||||
|
@ -572,16 +572,16 @@ class WebhooksCustomServerTest extends Scope
|
||||||
$this->assertEquals($webhook['method'], 'POST');
|
$this->assertEquals($webhook['method'], 'POST');
|
||||||
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
|
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
|
||||||
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
|
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
|
||||||
$this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString('functions.*.deployments.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*.deployments.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.*.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.*.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.deployments.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.deployments.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
||||||
|
@ -620,16 +620,16 @@ class WebhooksCustomServerTest extends Scope
|
||||||
$this->assertEquals($webhook['method'], 'POST');
|
$this->assertEquals($webhook['method'], 'POST');
|
||||||
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
|
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
|
||||||
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
|
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
|
||||||
$this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString('functions.*.executions.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*.executions.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.*.executions.{$executionId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.*.executions.{$executionId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.executions.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.executions.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.executions.{$executionId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.executions.{$executionId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
||||||
|
@ -643,16 +643,16 @@ class WebhooksCustomServerTest extends Scope
|
||||||
$this->assertEquals($webhook['method'], 'POST');
|
$this->assertEquals($webhook['method'], 'POST');
|
||||||
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
|
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
|
||||||
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
|
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
|
||||||
$this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString('functions.*.executions.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*.executions.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.*.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.*.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.executions.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.executions.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
||||||
|
@ -688,16 +688,16 @@ class WebhooksCustomServerTest extends Scope
|
||||||
$this->assertEquals($webhook['method'], 'POST');
|
$this->assertEquals($webhook['method'], 'POST');
|
||||||
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
|
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
|
||||||
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
|
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
|
||||||
$this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString('functions.*.deployments.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*.deployments.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.*.deployments.{$deploymentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.*.deployments.{$deploymentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.deployments.*.delete", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.deployments.*.delete", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
||||||
|
@ -733,10 +733,10 @@ class WebhooksCustomServerTest extends Scope
|
||||||
$this->assertEquals($webhook['method'], 'POST');
|
$this->assertEquals($webhook['method'], 'POST');
|
||||||
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
|
$this->assertEquals($webhook['headers']['Content-Type'], 'application/json');
|
||||||
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
|
$this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io');
|
||||||
$this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString('functions.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString('functions.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
||||||
$this->assertStringContainsString("functions.{$id}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']);
|
// $this->assertStringContainsString("functions.{$id}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected);
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']);
|
||||||
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
$this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']);
|
||||||
|
|
|
@ -50,7 +50,7 @@ class EventValidatorTest extends TestCase
|
||||||
$this->assertTrue($this->object->isValid('databases.books'));
|
$this->assertTrue($this->object->isValid('databases.books'));
|
||||||
$this->assertTrue($this->object->isValid('databases.books.collections.chapters'));
|
$this->assertTrue($this->object->isValid('databases.books.collections.chapters'));
|
||||||
$this->assertTrue($this->object->isValid('databases.books.collections.*'));
|
$this->assertTrue($this->object->isValid('databases.books.collections.*'));
|
||||||
$this->assertTrue($this->object->isValid('functions.*'));
|
// $this->assertTrue($this->object->isValid('functions.*')); TODO @christyjacob4 : enable test once we allow functions.* events
|
||||||
$this->assertTrue($this->object->isValid('buckets.*'));
|
$this->assertTrue($this->object->isValid('buckets.*'));
|
||||||
$this->assertTrue($this->object->isValid('teams.*'));
|
$this->assertTrue($this->object->isValid('teams.*'));
|
||||||
$this->assertTrue($this->object->isValid('users.*'));
|
$this->assertTrue($this->object->isValid('users.*'));
|
||||||
|
|
|
@ -15,16 +15,18 @@ class CollectionsTest extends TestCase
|
||||||
|
|
||||||
public function testDuplicateRules(): void
|
public function testDuplicateRules(): void
|
||||||
{
|
{
|
||||||
foreach ($this->collections as $key => $collection) {
|
foreach ($this->collections as $key => $sections) {
|
||||||
if (array_key_exists('attributes', $collection)) {
|
foreach ($sections as $key => $collection) {
|
||||||
foreach ($collection['attributes'] as $check) {
|
if (array_key_exists('attributes', $collection)) {
|
||||||
$occurrences = 0;
|
foreach ($collection['attributes'] as $check) {
|
||||||
foreach ($collection['attributes'] as $attribute) {
|
$occurrences = 0;
|
||||||
if ($attribute['$id'] == $check['$id']) {
|
foreach ($collection['attributes'] as $attribute) {
|
||||||
$occurrences++;
|
if ($attribute['$id'] == $check['$id']) {
|
||||||
|
$occurrences++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
$this->assertEquals(1, $occurrences);
|
||||||
}
|
}
|
||||||
$this->assertEquals(1, $occurrences);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|