diff --git a/.env b/.env index fff40e5f7..89b6252c0 100644 --- a/.env +++ b/.env @@ -41,6 +41,7 @@ _APP_SMTP_PORT=1025 _APP_SMTP_SECURE= _APP_SMTP_USERNAME= _APP_SMTP_PASSWORD= +_APP_USERS_STATS_RECIPIENTS= _APP_HAMSTER_INTERVAL=86400 _APP_HAMSTER_TIME=21:00 _APP_MIXPANEL_TOKEN= @@ -76,3 +77,5 @@ _APP_GRAPHQL_MAX_DEPTH=3 _APP_REGION=default _APP_DOCKER_HUB_USERNAME= _APP_DOCKER_HUB_PASSWORD= +_APP_CONSOLE_GITHUB_SECRET= +_APP_CONSOLE_GITHUB_APP_ID= diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..4d7fbb7b7 --- /dev/null +++ b/.github/workflows/publish.yml @@ -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 }} \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 6785e119c..4a4bf6aef 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "app/console"] path = app/console url = https://github.com/appwrite/console - branch = 2.2.2 + branch = cloud-cards diff --git a/Dockerfile b/Dockerfile index c7ea1e196..4eddc2076 100755 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ ENV VITE_APPWRITE_GROWTH_ENDPOINT=$VITE_APPWRITE_GROWTH_ENDPOINT RUN npm ci RUN npm run build -FROM appwrite/base:0.2.0 as final +FROM appwrite/base:0.2.2 as final LABEL maintainer="team@appwrite.io" @@ -91,6 +91,7 @@ COPY --from=node /usr/local/src/console/build /usr/src/code/console # Add Source Code COPY ./app /usr/src/code/app +COPY ./public /usr/src/code/public COPY ./bin /usr/local/bin COPY ./docs /usr/src/code/docs COPY ./src /usr/src/code/src @@ -112,7 +113,11 @@ RUN mkdir -p /storage/uploads && \ # Executables RUN chmod +x /usr/local/bin/doctor && \ chmod +x /usr/local/bin/patch-delete-schedule-updated-at-attribute && \ - chmod +x /usr/local/bin/maintenance && \ + chmod +x /usr/local/bin/clear-card-cache && \ + chmod +x /usr/local/bin/calc-users-stats && \ + chmod +x /usr/local/bin/calc-tier-stats && \ + chmod +x /usr/local/bin/patch-delete-project-collections && \ + chmod +x /usr/local/bin/maintenance && \ chmod +x /usr/local/bin/volume-sync && \ chmod +x /usr/local/bin/install && \ chmod +x /usr/local/bin/migrate && \ diff --git a/app/cli.php b/app/cli.php index 81fb1dff8..d1dd88577 100644 --- a/app/cli.php +++ b/app/cli.php @@ -61,7 +61,7 @@ CLI::setResource('dbForConsole', function ($pools, $cache) { $dbForConsole->setNamespace('console'); // Ensure tables exist - $collections = Config::getParam('collections', []); + $collections = Config::getParam('collections', [])['console']; $last = \array_key_last($collections); if (!($dbForConsole->exists($dbForConsole->getDefaultDatabase(), $last))) { /** TODO cache ready variable using registry */ diff --git a/app/config/cloud/contributors.json b/app/config/cloud/contributors.json new file mode 100644 index 000000000..efa36c3cb --- /dev/null +++ b/app/config/cloud/contributors.json @@ -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] \ No newline at end of file diff --git a/app/config/cloud/employees.json b/app/config/cloud/employees.json new file mode 100644 index 000000000..1575a0588 --- /dev/null +++ b/app/config/cloud/employees.json @@ -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" } +} diff --git a/app/config/cloud/heroes.json b/app/config/cloud/heroes.json new file mode 100644 index 000000000..3fc81b3b3 --- /dev/null +++ b/app/config/cloud/heroes.json @@ -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" } +} diff --git a/app/config/collections.php b/app/config/collections.php index 12904a1e2..5fd29d3ab 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -17,1297 +17,7 @@ $auth = Config::getParam('auth', []); * indexes => list of indexes */ -$collections = [ - 'databases' => [ - '$collection' => ID::custom(Database::METADATA), - '$id' => ID::custom('databases'), - 'name' => 'Databases', - 'attributes' => [ - [ - '$id' => ID::custom('name'), - 'type' => Database::VAR_STRING, - 'size' => 256, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('search'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 16384, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - ], - 'indexes' => [ - [ - '$id' => ID::custom('_fulltext_search'), - 'type' => Database::INDEX_FULLTEXT, - 'attributes' => ['search'], - 'lengths' => [], - 'orders' => [], - ], - [ - '$id' => ID::custom('_key_name'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['name'], - 'lengths' => [256], - 'orders' => [Database::ORDER_ASC], - ], - ], - ], - 'collections' => [ - '$collection' => ID::custom('databases'), - '$id' => ID::custom('collections'), - 'name' => 'Collections', - 'attributes' => [ - [ - '$id' => ID::custom('databaseInternalId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('databaseId'), - 'type' => Database::VAR_STRING, - 'signed' => true, - 'size' => Database::LENGTH_KEY, - 'format' => '', - 'filters' => [], - 'required' => true, - 'default' => null, - 'array' => false, - ], - [ - '$id' => ID::custom('name'), - 'type' => Database::VAR_STRING, - 'size' => 256, - 'required' => true, - 'signed' => true, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('enabled'), - 'type' => Database::VAR_BOOLEAN, - 'signed' => true, - 'size' => 0, - 'format' => '', - 'filters' => [], - 'required' => true, - 'default' => null, - 'array' => false, - ], - [ - '$id' => ID::custom('documentSecurity'), - 'type' => Database::VAR_BOOLEAN, - 'signed' => true, - 'size' => 0, - 'format' => '', - 'filters' => [], - 'required' => true, - 'default' => null, - 'array' => false, - ], - [ - '$id' => ID::custom('attributes'), - 'type' => Database::VAR_STRING, - 'size' => 1000000, - 'required' => false, - 'signed' => true, - 'array' => false, - 'filters' => ['subQueryAttributes'], - ], - [ - '$id' => ID::custom('indexes'), - 'type' => Database::VAR_STRING, - 'size' => 1000000, - 'required' => false, - 'signed' => true, - 'array' => false, - 'filters' => ['subQueryIndexes'], - ], - [ - '$id' => ID::custom('search'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 16384, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - ], - 'indexes' => [ - [ - '$id' => ID::custom('_fulltext_search'), - 'type' => Database::INDEX_FULLTEXT, - 'attributes' => ['search'], - 'lengths' => [], - 'orders' => [], - ], - [ - '$id' => ID::custom('_key_name'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['name'], - 'lengths' => [256], - 'orders' => [Database::ORDER_ASC], - ], - [ - '$id' => ID::custom('_key_enabled'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['enabled'], - 'lengths' => [], - 'orders' => [Database::ORDER_ASC], - ], - [ - '$id' => ID::custom('_key_documentSecurity'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['documentSecurity'], - 'lengths' => [], - 'orders' => [Database::ORDER_ASC], - ], - ], - ], - - 'attributes' => [ - '$collection' => ID::custom(Database::METADATA), - '$id' => ID::custom('attributes'), - 'name' => 'Attributes', - 'attributes' => [ - [ - '$id' => ID::custom('databaseInternalId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('databaseId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => false, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('collectionInternalId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('collectionId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('key'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('type'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 256, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('status'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 16, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('size'), - 'type' => Database::VAR_INTEGER, - 'format' => '', - 'size' => 0, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('required'), - 'type' => Database::VAR_BOOLEAN, - 'format' => '', - 'size' => 0, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('default'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 16384, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => ['casting'], - ], - [ - '$id' => ID::custom('signed'), - 'type' => Database::VAR_BOOLEAN, - 'size' => 0, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('array'), - 'type' => Database::VAR_BOOLEAN, - 'size' => 0, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('format'), - 'type' => Database::VAR_STRING, - 'size' => 64, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('formatOptions'), - 'type' => Database::VAR_STRING, - 'size' => 16384, - 'signed' => true, - 'required' => false, - 'default' => new stdClass(), - 'array' => false, - 'filters' => ['json', 'range', 'enum'], - ], - [ - '$id' => ID::custom('filters'), - 'type' => Database::VAR_STRING, - 'size' => 64, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => true, - 'filters' => [], - ], - ], - 'indexes' => [ - [ - '$id' => ID::custom('_key_db_collection'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['databaseInternalId', 'collectionInternalId'], - 'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY], - 'orders' => [Database::ORDER_ASC, Database::ORDER_ASC], - ], - ], - ], - - 'indexes' => [ - '$collection' => ID::custom(Database::METADATA), - '$id' => ID::custom('indexes'), - 'name' => 'Indexes', - 'attributes' => [ - [ - '$id' => ID::custom('databaseInternalId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('databaseId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => false, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('collectionInternalId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('collectionId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('key'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('type'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 16, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('status'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 16, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('attributes'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => true, - 'filters' => [], - ], - [ - '$id' => ID::custom('lengths'), - 'type' => Database::VAR_INTEGER, - 'format' => '', - 'size' => 0, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => true, - 'filters' => [], - ], - [ - '$id' => ID::custom('orders'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 4, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => true, - 'filters' => [], - ], - ], - 'indexes' => [ - [ - '$id' => ID::custom('_key_db_collection'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['databaseInternalId', 'collectionInternalId'], - 'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY], - 'orders' => [Database::ORDER_ASC, Database::ORDER_ASC], - ], - ], - ], - - 'projects' => [ - '$collection' => ID::custom(Database::METADATA), - '$id' => ID::custom('projects'), - 'name' => 'Projects', - 'attributes' => [ - [ - '$id' => ID::custom('teamInternalId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('teamId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('name'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 128, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('region'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 128, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('description'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 256, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('database'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 256, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('logo'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('url'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 16384, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('version'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 16, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('legalName'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 256, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('legalCountry'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 256, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('legalState'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 256, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('legalCity'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 256, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('legalAddress'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 256, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('legalTaxId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 256, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('services'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 16384, - 'signed' => true, - 'required' => false, - 'default' => [], - 'array' => false, - 'filters' => ['json'], - ], - [ - '$id' => ID::custom('auths'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 16384, - 'signed' => true, - 'required' => false, - 'default' => [], - 'array' => false, - 'filters' => ['json'], - ], - [ - '$id' => ID::custom('authProviders'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 16384, - 'signed' => true, - 'required' => false, - 'default' => [], - 'array' => false, - 'filters' => ['json', 'encrypt'], - ], - [ - '$id' => ID::custom('platforms'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 16384, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => ['subQueryPlatforms'], - ], - [ - '$id' => ID::custom('webhooks'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 16384, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => ['subQueryWebhooks'], - ], - [ - '$id' => ID::custom('keys'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 16384, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => ['subQueryKeys'], - ], - [ - '$id' => ID::custom('domains'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 16384, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => ['subQueryDomains'], - ], - [ - '$id' => ID::custom('search'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 16384, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - ], - 'indexes' => [ - [ - '$id' => ID::custom('_key_search'), - 'type' => Database::INDEX_FULLTEXT, - 'attributes' => ['search'], - 'lengths' => [], - 'orders' => [], - ], - [ - '$id' => ID::custom('_key_name'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['name'], - 'lengths' => [128], - 'orders' => [Database::ORDER_ASC], - ], - [ - '$id' => ID::custom('_key_team'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['teamId'], - 'lengths' => [Database::LENGTH_KEY], - 'orders' => [Database::ORDER_ASC], - ], - ], - ], - - 'schedules' => [ - '$collection' => ID::custom(Database::METADATA), - '$id' => ID::custom('schedules'), - 'name' => 'schedules', - 'attributes' => [ - [ - '$id' => ID::custom('resourceType'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 100, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('resourceInternalId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('resourceId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('resourceUpdatedAt'), - 'type' => Database::VAR_DATETIME, - 'format' => '', - 'size' => 0, - 'signed' => false, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => ['datetime'], - ], - [ - '$id' => ID::custom('projectId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('schedule'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 100, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('active'), - 'type' => Database::VAR_BOOLEAN, - 'signed' => true, - 'size' => 0, - 'format' => '', - 'filters' => [], - 'required' => false, - 'default' => null, - 'array' => false, - ], - [ - '$id' => ID::custom('region'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 10, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - ], - 'indexes' => [ - [ - '$id' => ID::custom('_key_region_resourceType_resourceUpdatedAt'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['region', 'resourceType','resourceUpdatedAt'], - 'lengths' => [], - 'orders' => [], - ], - [ - '$id' => ID::custom('_key_region_resourceType_projectId_resourceId'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['region', 'resourceType', 'projectId', 'resourceId'], - 'lengths' => [], - 'orders' => [], - ], - ], - ], - - 'platforms' => [ - '$collection' => ID::custom(Database::METADATA), - '$id' => ID::custom('platforms'), - 'name' => 'platforms', - 'attributes' => [ - [ - '$id' => ID::custom('projectInternalId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('projectId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('type'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 16, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('name'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 256, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('key'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('store'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 256, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('hostname'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 256, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ] - ], - 'indexes' => [ - [ - '$id' => ID::custom('_key_project'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['projectInternalId'], - 'lengths' => [Database::LENGTH_KEY], - 'orders' => [Database::ORDER_ASC], - ], - ], - ], - - 'domains' => [ - '$collection' => ID::custom(Database::METADATA), - '$id' => ID::custom('domains'), - 'name' => 'domains', - 'attributes' => [ - [ - '$id' => ID::custom('projectInternalId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('projectId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('updated'), - 'type' => Database::VAR_DATETIME, - 'format' => '', - 'size' => 0, - 'signed' => false, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => ['datetime'], - ], - [ - '$id' => ID::custom('domain'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('tld'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('registerable'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('verification'), - 'type' => Database::VAR_BOOLEAN, - 'format' => '', - 'size' => 0, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('certificateId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - ], - 'indexes' => [ - [ - '$id' => ID::custom('_key_project'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['projectInternalId'], - 'lengths' => [Database::LENGTH_KEY], - 'orders' => [Database::ORDER_ASC], - ], - ], - ], - - 'keys' => [ - '$collection' => ID::custom(Database::METADATA), - '$id' => ID::custom('keys'), - 'name' => 'keys', - 'attributes' => [ - [ - '$id' => ID::custom('projectInternalId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('projectId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => 0, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('name'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('scopes'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => true, - 'filters' => [], - ], - [ - '$id' => ID::custom('secret'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 512, // Output of \bin2hex(\random_bytes(128)) => string(256) doubling for encryption - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => ['encrypt'], - ], - [ - '$id' => ID::custom('expire'), - 'type' => Database::VAR_DATETIME, - 'format' => '', - 'size' => 0, - 'signed' => false, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => ['datetime'], - ], - [ - '$id' => ID::custom('accessedAt'), - 'type' => Database::VAR_DATETIME, - 'format' => '', - 'size' => 0, - 'signed' => false, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => ['datetime'], - ], - [ - '$id' => ID::custom('sdks'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => true, - 'filters' => [], - ], - ], - 'indexes' => [ - [ - '$id' => ID::custom('_key_project'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['projectInternalId'], - 'lengths' => [Database::LENGTH_KEY], - 'orders' => [Database::ORDER_ASC], - ], - [ - '$id' => '_key_accessedAt', - 'type' => Database::INDEX_KEY, - 'attributes' => ['accessedAt'], - 'lengths' => [], - 'orders' => [], - ], - ], - ], - - 'webhooks' => [ - '$collection' => ID::custom(Database::METADATA), - '$id' => ID::custom('webhooks'), - 'name' => 'webhooks', - 'attributes' => [ - [ - '$id' => ID::custom('projectInternalId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('projectId'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('name'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('url'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('httpUser'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('httpPass'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, // TODO will the length suffice after encryption? - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => ['encrypt'], - ], - [ - '$id' => ID::custom('security'), - 'type' => Database::VAR_BOOLEAN, - 'format' => '', - 'size' => 0, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('events'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => true, - 'filters' => [], - ], - [ - '$id' => ID::custom('signatureKey'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 2048, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - ], - 'indexes' => [ - [ - '$id' => ID::custom('_key_project'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['projectInternalId'], - 'lengths' => [Database::LENGTH_KEY], - 'orders' => [Database::ORDER_ASC], - ] - ], - ], - + $commonCollections = [ 'users' => [ '$collection' => ID::custom(Database::METADATA), '$id' => ID::custom('users'), @@ -2186,6 +896,610 @@ $collections = [ ], ], + 'buckets' => [ + '$collection' => ID::custom(Database::METADATA), + '$id' => ID::custom('buckets'), + 'name' => 'Buckets', + 'attributes' => [ + [ + '$id' => ID::custom('enabled'), + 'type' => Database::VAR_BOOLEAN, + 'signed' => true, + 'size' => 0, + 'format' => '', + 'filters' => [], + 'required' => true, + 'array' => false, + ], + [ + '$id' => ID::custom('name'), + 'type' => Database::VAR_STRING, + 'signed' => true, + 'size' => 128, + 'format' => '', + 'filters' => [], + 'required' => true, + 'array' => false, + ], + [ + '$id' => ID::custom('fileSecurity'), + 'type' => Database::VAR_BOOLEAN, + 'signed' => true, + 'size' => 1, + 'format' => '', + 'filters' => [], + 'required' => false, + 'array' => false, + ], + [ + '$id' => ID::custom('maximumFileSize'), + 'type' => Database::VAR_INTEGER, + 'signed' => false, + 'size' => 8, + 'format' => '', + 'filters' => [], + 'required' => true, + 'array' => false, + ], + [ + '$id' => ID::custom('allowedFileExtensions'), + 'type' => Database::VAR_STRING, + 'signed' => true, + 'size' => 64, + 'format' => '', + 'filters' => [], + 'required' => true, + 'array' => true, + ], + [ + '$id' => 'compression', + 'type' => Database::VAR_STRING, + 'signed' => true, + 'size' => 10, + 'format' => '', + 'filters' => [], + 'required' => true, + 'array' => false, + ], + [ + '$id' => ID::custom('encryption'), + 'type' => Database::VAR_BOOLEAN, + 'signed' => true, + 'size' => 0, + 'format' => '', + 'filters' => [], + 'required' => true, + 'array' => false, + ], + [ + '$id' => ID::custom('antivirus'), + 'type' => Database::VAR_BOOLEAN, + 'signed' => true, + 'size' => 0, + 'format' => '', + 'filters' => [], + 'required' => true, + 'array' => false, + ], + [ + '$id' => ID::custom('search'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + ], + 'indexes' => [ + [ + '$id' => ID::custom('_fulltext_name'), + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => ['name'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_search'), + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => ['search'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_enabled'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['enabled'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_name'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['name'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_fileSecurity'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['fileSecurity'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_maximumFileSize'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['maximumFileSize'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_encryption'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['encryption'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_antivirus'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['antivirus'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + ] + ], + + 'stats' => [ + '$collection' => ID::custom(Database::METADATA), + '$id' => ID::custom('stats'), + 'name' => 'Stats', + 'attributes' => [ + [ + '$id' => ID::custom('metric'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 255, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('region'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 255, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('value'), + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 8, + 'signed' => false, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('time'), + 'type' => Database::VAR_DATETIME, + 'format' => '', + 'size' => 0, + 'signed' => false, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['datetime'], + ], + [ + '$id' => ID::custom('period'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 4, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('type'), + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 1, + 'signed' => false, + 'required' => true, + 'default' => 0, // 0 -> count, 1 -> sum + 'array' => false, + 'filters' => [], + ], + ], + 'indexes' => [ + [ + '$id' => ID::custom('_key_time'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['time'], + 'lengths' => [], + 'orders' => [Database::ORDER_DESC], + ], + [ + '$id' => ID::custom('_key_period_time'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['period', 'time'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_metric_period_time'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['metric', 'period', 'time'], + 'lengths' => [], + 'orders' => [Database::ORDER_DESC], + ], + ], + ], + ]; + + $projectCollections = array_merge([ + 'databases' => [ + '$collection' => ID::custom(Database::METADATA), + '$id' => ID::custom('databases'), + 'name' => 'Databases', + 'attributes' => [ + [ + '$id' => ID::custom('name'), + 'type' => Database::VAR_STRING, + 'size' => 256, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('search'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + ], + 'indexes' => [ + [ + '$id' => ID::custom('_fulltext_search'), + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => ['search'], + 'lengths' => [], + 'orders' => [], + ], + [ + '$id' => ID::custom('_key_name'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['name'], + 'lengths' => [256], + 'orders' => [Database::ORDER_ASC], + ], + ], + ], + + 'attributes' => [ + '$collection' => ID::custom(Database::METADATA), + '$id' => ID::custom('attributes'), + 'name' => 'Attributes', + 'attributes' => [ + [ + '$id' => ID::custom('databaseInternalId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('databaseId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => false, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('collectionInternalId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('collectionId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('key'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('type'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('status'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('size'), + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('required'), + 'type' => Database::VAR_BOOLEAN, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('default'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['casting'], + ], + [ + '$id' => ID::custom('signed'), + 'type' => Database::VAR_BOOLEAN, + 'size' => 0, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('array'), + 'type' => Database::VAR_BOOLEAN, + 'size' => 0, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('format'), + 'type' => Database::VAR_STRING, + 'size' => 64, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('formatOptions'), + 'type' => Database::VAR_STRING, + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => new stdClass(), + 'array' => false, + 'filters' => ['json', 'range', 'enum'], + ], + [ + '$id' => ID::custom('filters'), + 'type' => Database::VAR_STRING, + 'size' => 64, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => true, + 'filters' => [], + ], + ], + 'indexes' => [ + [ + '$id' => ID::custom('_key_db_collection'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['databaseInternalId', 'collectionInternalId'], + 'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC, Database::ORDER_ASC], + ], + ], + ], + + 'indexes' => [ + '$collection' => ID::custom(Database::METADATA), + '$id' => ID::custom('indexes'), + 'name' => 'Indexes', + 'attributes' => [ + [ + '$id' => ID::custom('databaseInternalId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('databaseId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => false, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('collectionInternalId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('collectionId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('key'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('type'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('status'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('attributes'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => true, + 'filters' => [], + ], + [ + '$id' => ID::custom('lengths'), + 'type' => Database::VAR_INTEGER, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => true, + 'filters' => [], + ], + [ + '$id' => ID::custom('orders'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 4, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => true, + 'filters' => [], + ], + ], + 'indexes' => [ + [ + '$id' => ID::custom('_key_db_collection'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['databaseInternalId', 'collectionInternalId'], + 'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC, Database::ORDER_ASC], + ], + ], + ], + 'functions' => [ '$collection' => ID::custom(Database::METADATA), '$id' => ID::custom('functions'), @@ -2922,6 +2236,970 @@ $collections = [ ], ], + 'variables' => [ + '$collection' => Database::METADATA, + '$id' => 'variables', + 'name' => 'variables', + 'attributes' => [ + [ + '$id' => 'functionInternalId', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'functionId', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'key', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'value', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 8192, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [ 'encrypt' ] + ], + [ + '$id' => ID::custom('search'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + ], + 'indexes' => [ + [ + '$id' => '_key_function', + 'type' => Database::INDEX_KEY, + 'attributes' => ['functionInternalId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => '_key_uniqueKey', + 'type' => Database::INDEX_UNIQUE, + 'attributes' => ['functionInternalId', 'key'], + 'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC, Database::ORDER_ASC], + ], + [ + '$id' => '_key_key', + 'type' => Database::INDEX_KEY, + 'attributes' => ['key'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_fulltext_search'), + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => ['search'], + 'lengths' => [], + 'orders' => [], + ], + ], + ], + + 'cache' => [ + '$collection' => Database::METADATA, + '$id' => 'cache', + 'name' => 'Cache', + 'attributes' => [ + [ + '$id' => 'resource', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 255, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => 'accessedAt', + 'type' => Database::VAR_DATETIME, + 'format' => '', + 'size' => 0, + 'signed' => false, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['datetime'], + ], + [ + '$id' => 'signature', + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 255, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + ], + 'indexes' => [ + [ + '$id' => '_key_accessedAt', + 'type' => Database::INDEX_KEY, + 'attributes' => ['accessedAt'], + 'lengths' => [], + 'orders' => [], + ], + [ + '$id' => '_key_resource', + 'type' => Database::INDEX_KEY, + 'attributes' => ['resource'], + 'lengths' => [], + 'orders' => [], + ], + ], + ], + ], $commonCollections); + + $consoleCollections = array_merge([ + 'projects' => [ + '$collection' => ID::custom(Database::METADATA), + '$id' => ID::custom('projects'), + 'name' => 'Projects', + 'attributes' => [ + [ + '$id' => ID::custom('teamInternalId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('teamId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('name'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 128, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('region'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 128, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('description'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('database'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('logo'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('url'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('version'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('legalName'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('legalCountry'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('legalState'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('legalCity'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('legalAddress'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('legalTaxId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('services'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => [], + 'array' => false, + 'filters' => ['json'], + ], + [ + '$id' => ID::custom('auths'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => [], + 'array' => false, + 'filters' => ['json'], + ], + [ + '$id' => ID::custom('authProviders'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => [], + 'array' => false, + 'filters' => ['json', 'encrypt'], + ], + [ + '$id' => ID::custom('platforms'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['subQueryPlatforms'], + ], + [ + '$id' => ID::custom('webhooks'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['subQueryWebhooks'], + ], + [ + '$id' => ID::custom('keys'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['subQueryKeys'], + ], + [ + '$id' => ID::custom('domains'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['subQueryDomains'], + ], + [ + '$id' => ID::custom('search'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + ], + 'indexes' => [ + [ + '$id' => ID::custom('_key_search'), + 'type' => Database::INDEX_FULLTEXT, + 'attributes' => ['search'], + 'lengths' => [], + 'orders' => [], + ], + [ + '$id' => ID::custom('_key_name'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['name'], + 'lengths' => [128], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_team'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['teamId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ], + ], + ], + + 'schedules' => [ + '$collection' => ID::custom(Database::METADATA), + '$id' => ID::custom('schedules'), + 'name' => 'schedules', + 'attributes' => [ + [ + '$id' => ID::custom('resourceType'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 100, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('resourceId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('resourceUpdatedAt'), + 'type' => Database::VAR_DATETIME, + 'format' => '', + 'size' => 0, + 'signed' => false, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['datetime'], + ], + [ + '$id' => ID::custom('projectId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('schedule'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 100, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('active'), + 'type' => Database::VAR_BOOLEAN, + 'signed' => true, + 'size' => 0, + 'format' => '', + 'filters' => [], + 'required' => false, + 'default' => null, + 'array' => false, + ], + [ + '$id' => ID::custom('region'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 10, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + ], + 'indexes' => [ + [ + '$id' => ID::custom('_key_region_resourceType_resourceUpdatedAt'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['region', 'resourceType','resourceUpdatedAt'], + 'lengths' => [], + 'orders' => [], + ], + [ + '$id' => ID::custom('_key_region_resourceType_projectId_resourceId'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['region', 'resourceType', 'projectId', 'resourceId'], + 'lengths' => [], + 'orders' => [], + ], + ], + ], + + 'platforms' => [ + '$collection' => ID::custom(Database::METADATA), + '$id' => ID::custom('platforms'), + 'name' => 'platforms', + 'attributes' => [ + [ + '$id' => ID::custom('projectInternalId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('projectId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('type'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('name'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('key'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('store'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('hostname'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 256, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ] + ], + 'indexes' => [ + [ + '$id' => ID::custom('_key_project'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['projectInternalId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ], + ], + ], + + 'domains' => [ + '$collection' => ID::custom(Database::METADATA), + '$id' => ID::custom('domains'), + 'name' => 'domains', + 'attributes' => [ + [ + '$id' => ID::custom('projectInternalId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('projectId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('updated'), + 'type' => Database::VAR_DATETIME, + 'format' => '', + 'size' => 0, + 'signed' => false, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['datetime'], + ], + [ + '$id' => ID::custom('domain'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('tld'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('registerable'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('verification'), + 'type' => Database::VAR_BOOLEAN, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('certificateId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + ], + 'indexes' => [ + [ + '$id' => ID::custom('_key_project'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['projectInternalId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ], + ], + ], + + 'keys' => [ + '$collection' => ID::custom(Database::METADATA), + '$id' => ID::custom('keys'), + 'name' => 'keys', + 'attributes' => [ + [ + '$id' => ID::custom('projectInternalId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('projectId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => 0, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('name'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('scopes'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => true, + 'filters' => [], + ], + [ + '$id' => ID::custom('secret'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 512, // Output of \bin2hex(\random_bytes(128)) => string(256) doubling for encryption + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => ['encrypt'], + ], + [ + '$id' => ID::custom('expire'), + 'type' => Database::VAR_DATETIME, + 'format' => '', + 'size' => 0, + 'signed' => false, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['datetime'], + ], + [ + '$id' => ID::custom('accessedAt'), + 'type' => Database::VAR_DATETIME, + 'format' => '', + 'size' => 0, + 'signed' => false, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['datetime'], + ], + [ + '$id' => ID::custom('sdks'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => true, + 'filters' => [], + ], + ], + 'indexes' => [ + [ + '$id' => ID::custom('_key_project'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['projectInternalId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => '_key_accessedAt', + 'type' => Database::INDEX_KEY, + 'attributes' => ['accessedAt'], + 'lengths' => [], + 'orders' => [], + ], + ], + ], + + 'webhooks' => [ + '$collection' => ID::custom(Database::METADATA), + '$id' => ID::custom('webhooks'), + 'name' => 'webhooks', + 'attributes' => [ + [ + '$id' => ID::custom('projectInternalId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('projectId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('name'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('url'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('httpUser'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('httpPass'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, // TODO will the length suffice after encryption? + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['encrypt'], + ], + [ + '$id' => ID::custom('security'), + 'type' => Database::VAR_BOOLEAN, + 'format' => '', + 'size' => 0, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('events'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => true, + 'default' => null, + 'array' => true, + 'filters' => [], + ], + [ + '$id' => ID::custom('signatureKey'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 2048, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + ], + 'indexes' => [ + [ + '$id' => ID::custom('_key_project'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['projectInternalId'], + 'lengths' => [Database::LENGTH_KEY], + 'orders' => [Database::ORDER_ASC], + ] + ], + ], + 'certificates' => [ '$collection' => ID::custom(Database::METADATA), '$id' => ID::custom('certificates'), @@ -3008,286 +3286,6 @@ $collections = [ ], ], - 'buckets' => [ - '$collection' => ID::custom(Database::METADATA), - '$id' => ID::custom('buckets'), - 'name' => 'Buckets', - 'attributes' => [ - [ - '$id' => ID::custom('enabled'), - 'type' => Database::VAR_BOOLEAN, - 'signed' => true, - 'size' => 0, - 'format' => '', - 'filters' => [], - 'required' => true, - 'array' => false, - ], - [ - '$id' => ID::custom('name'), - 'type' => Database::VAR_STRING, - 'signed' => true, - 'size' => 128, - 'format' => '', - 'filters' => [], - 'required' => true, - 'array' => false, - ], - [ - '$id' => ID::custom('fileSecurity'), - 'type' => Database::VAR_BOOLEAN, - 'signed' => true, - 'size' => 1, - 'format' => '', - 'filters' => [], - 'required' => false, - 'array' => false, - ], - [ - '$id' => ID::custom('maximumFileSize'), - 'type' => Database::VAR_INTEGER, - 'signed' => false, - 'size' => 8, - 'format' => '', - 'filters' => [], - 'required' => true, - 'array' => false, - ], - [ - '$id' => ID::custom('allowedFileExtensions'), - 'type' => Database::VAR_STRING, - 'signed' => true, - 'size' => 64, - 'format' => '', - 'filters' => [], - 'required' => true, - 'array' => true, - ], - [ - '$id' => 'compression', - 'type' => Database::VAR_STRING, - 'signed' => true, - 'size' => 10, - 'format' => '', - 'filters' => [], - 'required' => true, - 'array' => false, - ], - [ - '$id' => ID::custom('encryption'), - 'type' => Database::VAR_BOOLEAN, - 'signed' => true, - 'size' => 0, - 'format' => '', - 'filters' => [], - 'required' => true, - 'array' => false, - ], - [ - '$id' => ID::custom('antivirus'), - 'type' => Database::VAR_BOOLEAN, - 'signed' => true, - 'size' => 0, - 'format' => '', - 'filters' => [], - 'required' => true, - 'array' => false, - ], - [ - '$id' => ID::custom('search'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 16384, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - ], - 'indexes' => [ - [ - '$id' => ID::custom('_fulltext_name'), - 'type' => Database::INDEX_FULLTEXT, - 'attributes' => ['name'], - 'lengths' => [], - 'orders' => [Database::ORDER_ASC], - ], - [ - '$id' => ID::custom('_key_search'), - 'type' => Database::INDEX_FULLTEXT, - 'attributes' => ['search'], - 'lengths' => [], - 'orders' => [Database::ORDER_ASC], - ], - [ - '$id' => ID::custom('_key_enabled'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['enabled'], - 'lengths' => [], - 'orders' => [Database::ORDER_ASC], - ], - [ - '$id' => ID::custom('_key_name'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['name'], - 'lengths' => [], - 'orders' => [Database::ORDER_ASC], - ], - [ - '$id' => ID::custom('_key_fileSecurity'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['fileSecurity'], - 'lengths' => [], - 'orders' => [Database::ORDER_ASC], - ], - [ - '$id' => ID::custom('_key_maximumFileSize'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['maximumFileSize'], - 'lengths' => [], - 'orders' => [Database::ORDER_ASC], - ], - [ - '$id' => ID::custom('_key_encryption'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['encryption'], - 'lengths' => [], - 'orders' => [Database::ORDER_ASC], - ], - [ - '$id' => ID::custom('_key_antivirus'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['antivirus'], - 'lengths' => [], - 'orders' => [Database::ORDER_ASC], - ], - ] - ], - - 'stats' => [ - '$collection' => ID::custom(Database::METADATA), - '$id' => ID::custom('stats'), - 'name' => 'Stats', - 'attributes' => [ - [ - '$id' => ID::custom('metric'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 255, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('region'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 255, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('value'), - 'type' => Database::VAR_INTEGER, - 'format' => '', - 'size' => 8, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => ID::custom('time'), - 'type' => Database::VAR_DATETIME, - 'format' => '', - 'size' => 0, - 'signed' => false, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => ['datetime'], - ], - [ - '$id' => ID::custom('period'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 4, - 'signed' => true, - 'required' => true, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - ], - 'indexes' => [ - [ - '$id' => ID::custom('_key_time'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['time'], - 'lengths' => [], - 'orders' => [Database::ORDER_DESC], - ], - [ - '$id' => ID::custom('_key_period_time'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['period', 'time'], - 'lengths' => [], - 'orders' => [Database::ORDER_ASC], - ], - [ - '$id' => ID::custom('_key_metric_period_time'), - 'type' => Database::INDEX_UNIQUE, - 'attributes' => ['metric', 'period', 'time'], - 'lengths' => [], - 'orders' => [Database::ORDER_DESC], - ], - ], - ], - 'statsLogger' => [ - '$collection' => ID::custom(Database::METADATA), - '$id' => ID::custom('statsLogger'), - 'name' => 'StatsLogger', - 'attributes' => [ - [ - '$id' => ID::custom('time'), - 'type' => Database::VAR_DATETIME, - 'format' => '', - 'size' => 0, - 'signed' => false, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => ['datetime'], - ], - [ - '$id' => ID::custom('metrics'), - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 5012, - 'signed' => true, - 'required' => false, - 'default' => [], - 'array' => false, - 'filters' => ['json'], - ], - ], - 'indexes' => [ - [ - '$id' => ID::custom('_key_time'), - 'type' => Database::INDEX_KEY, - 'attributes' => ['time'], - 'lengths' => [], - 'orders' => [Database::ORDER_DESC], - ], - ], - ], 'realtime' => [ '$collection' => ID::custom(Database::METADATA), '$id' => ID::custom('realtime'), @@ -3337,63 +3335,9 @@ $collections = [ ], ] ], + ], $commonCollections); - 'cache' => [ - '$collection' => Database::METADATA, - '$id' => 'cache', - 'name' => 'Cache', - 'attributes' => [ - [ - '$id' => 'resource', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 255, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'accessedAt', - 'type' => Database::VAR_DATETIME, - 'format' => '', - 'size' => 0, - 'signed' => false, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => ['datetime'], - ], - [ - '$id' => 'signature', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 255, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - ], - 'indexes' => [ - [ - '$id' => '_key_accessedAt', - 'type' => Database::INDEX_KEY, - 'attributes' => ['accessedAt'], - 'lengths' => [], - 'orders' => [], - ], - [ - '$id' => '_key_resource', - 'type' => Database::INDEX_KEY, - 'attributes' => ['resource'], - 'lengths' => [], - 'orders' => [], - ], - ], - ], + $bucketCollections = [ 'files' => [ '$collection' => ID::custom('buckets'), '$id' => ID::custom('files'), @@ -3657,14 +3601,16 @@ $collections = [ ], ] ], + ]; - 'variables' => [ - '$collection' => Database::METADATA, - '$id' => 'variables', - 'name' => 'variables', + $dbCollections = [ + 'collections' => [ + '$collection' => ID::custom('databases'), + '$id' => ID::custom('collections'), + 'name' => 'Collections', 'attributes' => [ [ - '$id' => 'functionInternalId', + '$id' => ID::custom('databaseInternalId'), 'type' => Database::VAR_STRING, 'format' => '', 'size' => Database::LENGTH_KEY, @@ -3675,37 +3621,64 @@ $collections = [ 'filters' => [], ], [ - '$id' => 'functionId', + '$id' => ID::custom('databaseId'), 'type' => Database::VAR_STRING, - 'format' => '', + 'signed' => true, 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, - 'filters' => [], - ], - [ - '$id' => 'key', - 'type' => Database::VAR_STRING, 'format' => '', - 'size' => Database::LENGTH_KEY, - 'signed' => true, - 'required' => false, - 'default' => null, - 'array' => false, 'filters' => [], - ], - [ - '$id' => 'value', - 'type' => Database::VAR_STRING, - 'format' => '', - 'size' => 8192, - 'signed' => true, 'required' => true, 'default' => null, 'array' => false, - 'filters' => [ 'encrypt' ] + ], + [ + '$id' => ID::custom('name'), + 'type' => Database::VAR_STRING, + 'size' => 256, + 'required' => true, + 'signed' => true, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('enabled'), + 'type' => Database::VAR_BOOLEAN, + 'signed' => true, + 'size' => 0, + 'format' => '', + 'filters' => [], + 'required' => true, + 'default' => null, + 'array' => false, + ], + [ + '$id' => ID::custom('documentSecurity'), + 'type' => Database::VAR_BOOLEAN, + 'signed' => true, + 'size' => 0, + 'format' => '', + 'filters' => [], + 'required' => true, + 'default' => null, + 'array' => false, + ], + [ + '$id' => ID::custom('attributes'), + 'type' => Database::VAR_STRING, + 'size' => 1000000, + 'required' => false, + 'signed' => true, + 'array' => false, + 'filters' => ['subQueryAttributes'], + ], + [ + '$id' => ID::custom('indexes'), + 'type' => Database::VAR_STRING, + 'size' => 1000000, + 'required' => false, + 'signed' => true, + 'array' => false, + 'filters' => ['subQueryIndexes'], ], [ '$id' => ID::custom('search'), @@ -3720,27 +3693,6 @@ $collections = [ ], ], 'indexes' => [ - [ - '$id' => '_key_function', - 'type' => Database::INDEX_KEY, - 'attributes' => ['functionInternalId'], - 'lengths' => [Database::LENGTH_KEY], - 'orders' => [Database::ORDER_ASC], - ], - [ - '$id' => '_key_uniqueKey', - 'type' => Database::INDEX_UNIQUE, - 'attributes' => ['functionInternalId', 'key'], - 'lengths' => [Database::LENGTH_KEY, Database::LENGTH_KEY], - 'orders' => [Database::ORDER_ASC, Database::ORDER_ASC], - ], - [ - '$id' => '_key_key', - 'type' => Database::INDEX_KEY, - 'attributes' => ['key'], - 'lengths' => [Database::LENGTH_KEY], - 'orders' => [Database::ORDER_ASC], - ], [ '$id' => ID::custom('_fulltext_search'), 'type' => Database::INDEX_FULLTEXT, @@ -3748,8 +3700,37 @@ $collections = [ 'lengths' => [], 'orders' => [], ], + [ + '$id' => ID::custom('_key_name'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['name'], + 'lengths' => [256], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_enabled'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['enabled'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], + [ + '$id' => ID::custom('_key_documentSecurity'), + 'type' => Database::INDEX_KEY, + 'attributes' => ['documentSecurity'], + 'lengths' => [], + 'orders' => [Database::ORDER_ASC], + ], ], ], -]; + ]; -return $collections; + + $collections = [ + 'projects' => $projectCollections, + 'console' => $consoleCollections, + 'buckets' => $bucketCollections, + 'databases' => $dbCollections + ]; + + return $collections; diff --git a/app/config/errors.php b/app/config/errors.php index f90d1dd77..6f6b48750 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -112,7 +112,7 @@ return [ ], Exception::USER_BLOCKED => [ 'name' => Exception::USER_BLOCKED, - 'description' => 'The current user has been blocked. You can unblock the user from the Appwrite console.', + 'description' => 'The current user has been blocked.', 'code' => 401, ], Exception::USER_INVALID_TOKEN => [ @@ -484,6 +484,11 @@ return [ 'description' => 'Project with the requested ID could not be found. Please check the value of the X-Appwrite-Project header to ensure the correct project ID is being used.', 'code' => 404, ], + Exception::PROJECT_ALREADY_EXISTS => [ + 'name' => Exception::PROJECT_ALREADY_EXISTS, + 'description' => 'Project with the requested ID already exists.', + 'code' => 409, + ], Exception::PROJECT_UNKNOWN => [ 'name' => Exception::PROJECT_UNKNOWN, 'description' => 'The project ID is either missing or not valid. Please check the value of the X-Appwrite-Project header to ensure the correct project ID is being used.', @@ -541,9 +546,14 @@ return [ ], Exception::DOMAIN_ALREADY_EXISTS => [ 'name' => Exception::DOMAIN_ALREADY_EXISTS, - 'description' => 'A Domain with the requested ID already exists.', + 'description' => 'The requested domain is currently in use by a project.', 'code' => 409, ], + Exception::DOMAIN_FORBIDDEN => [ + 'name' => Exception::DOMAIN_FORBIDDEN, + 'description' => 'The requested domain cannot be used as a custom domain.', + 'code' => 403, + ], Exception::VARIABLE_NOT_FOUND => [ 'name' => Exception::VARIABLE_NOT_FOUND, 'description' => 'Variable with the requested ID could not be found.', diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index b2f6f7adb..d986140bf 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -572,6 +572,10 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $name = $oauth2->getUserName($accessToken); $email = $oauth2->getUserEmail($accessToken); + if (empty($email)) { + throw new Exception(Exception::USER_UNAUTHORIZED, 'OAuth provider failed to return email.'); + } + /** * Is verified is not used yet, since we don't know after an accout is created anymore if it was verified or not. */ @@ -1813,6 +1817,12 @@ App::patch('/v1/account/status') $response->addHeader('X-Fallback-Cookies', \json_encode([])); } + $protocol = $request->getProtocol(); + $response + ->addCookie(Auth::$cookieName . '_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ; + $response->dynamic($user, Response::MODEL_ACCOUNT); }); diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index 19daaea20..00425a501 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -7,9 +7,16 @@ use Appwrite\Utopia\Response; use chillerlan\QRCode\QRCode; use chillerlan\QRCode\QROptions; use Utopia\App; +use Utopia\CLI\Console; use Utopia\Config\Config; +use Utopia\Database\Database; +use Utopia\Database\DateTime; use Utopia\Database\Document; +use Utopia\Database\Validator\Authorization; +use Utopia\Database\Validator\UID; use Utopia\Image\Image; +use Utopia\Logger\Log; +use Utopia\Logger\Logger; use Utopia\Validator\Boolean; use Utopia\Validator\HexColor; use Utopia\Validator\Range; @@ -49,11 +56,139 @@ $avatarCallback = function (string $type, string $code, int $width, int $height, $response ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT') ->setContentType('image/png') - ->file($data) - ; + ->file($data); unset($image); }; +$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForConsole, ?Logger $logger) { + try { + $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); + + $sessions = $user->getAttribute('sessions', []); + + $gitHubSession = null; + foreach ($sessions as $session) { + if ($session->getAttribute('provider', '') === 'github') { + $gitHubSession = $session; + break; + } + } + + if (empty($gitHubSession)) { + throw new Exception(Exception::GENERAL_UNKNOWN, 'GitHub session not found.'); + } + + $provider = $gitHubSession->getAttribute('provider', ''); + $accessToken = $gitHubSession->getAttribute('providerAccessToken'); + $accessTokenExpiry = $gitHubSession->getAttribute('providerAccessTokenExpiry'); + $refreshToken = $gitHubSession->getAttribute('providerRefreshToken'); + + $appId = $project->getAttribute('authProviders', [])[$provider . 'Appid'] ?? ''; + $appSecret = $project->getAttribute('authProviders', [])[$provider . 'Secret'] ?? '{}'; + + $className = 'Appwrite\\Auth\\OAuth2\\' . \ucfirst($provider); + + if (!\class_exists($className)) { + throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED); + } + + $oauth2 = new $className($appId, $appSecret, '', [], []); + + $isExpired = new \DateTime($accessTokenExpiry) < new \DateTime('now'); + if ($isExpired) { + try { + $oauth2->refreshTokens($refreshToken); + + $accessToken = $oauth2->getAccessToken(''); + $refreshToken = $oauth2->getRefreshToken(''); + + $verificationId = $oauth2->getUserID($accessToken); + + if (empty($verificationId)) { + throw new \Exception("Locked tokens."); // Race codition, handeled in catch + } + + $gitHubSession + ->setAttribute('providerAccessToken', $accessToken) + ->setAttribute('providerRefreshToken', $refreshToken) + ->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry(''))); + + Authorization::skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession)); + + $dbForProject->deleteCachedDocument('users', $user->getId()); + } catch (Throwable $err) { + $index = 0; + do { + $previousAccessToken = $gitHubSession->getAttribute('providerAccessToken'); + + $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); + $sessions = $user->getAttribute('sessions', []); + + $gitHubSession = new Document(); + foreach ($sessions as $session) { + if ($session->getAttribute('provider', '') === 'github') { + $gitHubSession = $session; + break; + } + } + + $accessToken = $gitHubSession->getAttribute('providerAccessToken'); + + if ($accessToken !== $previousAccessToken) { + break; + } + + $index++; + \usleep(500000); + } while ($index < 10); + } + } + + $oauth2 = new $className($appId, $appSecret, '', [], []); + $githubUser = $oauth2->getUserSlug($accessToken); + $githubId = $oauth2->getUserID($accessToken); + + return [ + 'name' => $githubUser, + 'id' => $githubId + ]; + } catch (Exception $error) { + if ($logger) { + $version = App::getEnv('_APP_VERSION', 'UNKNOWN'); + + $log = new Log(); + $log->setNamespace('console'); + $log->setServer(\gethostname()); + $log->setVersion($version); + $log->setType(Log::TYPE_ERROR); + $log->setMessage($error->getMessage()); + + $log->addTag('code', $error->getCode()); + $log->addTag('verboseType', get_class($error)); + + $log->addExtra('file', $error->getFile()); + $log->addExtra('line', $error->getLine()); + $log->addExtra('trace', $error->getTraceAsString()); + $log->addExtra('detailedTrace', $error->getTrace()); + + $log->setAction('avatarsGetGitHub'); + + $isProduction = App::getEnv('_APP_ENV', 'development') === 'production'; + $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); + + $responseCode = $logger->addLog($log); + Console::info('GitHub error log pushed with status code: ' . $responseCode); + } + + Console::warning("Failed: {$error->getMessage()}"); + Console::warning($error->getTraceAsString()); + + return []; + } + + return []; +}; + App::get('/v1/avatars/credit-cards/:code') ->desc('Get Credit Card Icon') ->groups(['api', 'avatars']) @@ -160,8 +295,7 @@ App::get('/v1/avatars/image') $response ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT') ->setContentType('image/png') - ->file($data) - ; + ->file($data); unset($image); }); @@ -274,8 +408,7 @@ App::get('/v1/avatars/favicon') $response ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT') ->setContentType('image/x-icon') - ->file($data) - ; + ->file($data); } $fetch = @\file_get_contents($outputHref, false); @@ -292,8 +425,7 @@ App::get('/v1/avatars/favicon') $response ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + 60 * 60 * 24 * 30) . ' GMT') ->setContentType('image/png') - ->file($data) - ; + ->file($data); unset($image); }); @@ -334,8 +466,7 @@ App::get('/v1/avatars/qr') $response ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache ->setContentType('image/png') - ->send($image->output('png', 9)) - ; + ->send($image->output('png', 9)); }); App::get('/v1/avatars/initials') @@ -419,6 +550,680 @@ App::get('/v1/avatars/initials') $response ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache ->setContentType('image/png') - ->file($image->getImageBlob()) - ; + ->file($image->getImageBlob()); + }); + +App::get('/v1/cards/cloud') + ->desc('Get Front Of Cloud Card') + ->groups(['api', 'avatars']) + ->label('scope', 'avatars.read') + ->label('cache', true) + ->label('cache.resourceType', 'cards/cloud') + ->label('cache.resource', 'card/{request.userId}') + ->label('docs', false) + ->label('origin', '*') + ->param('userId', '', new UID(), 'User ID.', true) + ->param('mock', '', new WhiteList(['employee', 'employee-2digit', 'hero', 'contributor', 'normal', 'platinum', 'normal-no-github', 'normal-long']), 'Mocking behaviour.', true) + ->param('width', 0, new Range(0, 512), 'Resize image width, Pass an integer between 0 to 512.', true) + ->param('height', 0, new Range(0, 320), 'Resize image height, Pass an integer between 0 to 320.', true) + ->inject('user') + ->inject('project') + ->inject('dbForProject') + ->inject('dbForConsole') + ->inject('response') + ->inject('heroes') + ->inject('contributors') + ->inject('employees') + ->inject('logger') + ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) { + $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); + + if ($user->isEmpty() && empty($mock)) { + throw new Exception(Exception::USER_NOT_FOUND); + } + + if (!$mock) { + $name = $user->getAttribute('name', 'Anonymous'); + $email = $user->getAttribute('email', ''); + $createdAt = new \DateTime($user->getCreatedAt()); + + $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger); + $githubName = $gitHub['name'] ?? ''; + $githubId = $gitHub['id'] ?? ''; + + $isHero = \array_key_exists($email, $heroes); + $isContributor = \in_array($githubId, $contributors); + $isEmployee = \array_key_exists($email, $employees); + $employeeNumber = $isEmployee ? $employees[$email]['spot'] : ''; + + if ($isHero) { + $createdAt = new \DateTime($heroes[$email]['memberSince'] ?? ''); + } elseif ($isEmployee) { + $createdAt = new \DateTime($employees[$email]['memberSince'] ?? ''); + } + + if (!$isEmployee && !empty($githubName)) { + $employeeGitHub = \array_search(\strtolower($githubName), \array_map(fn ($employee) => \strtolower($employee['gitHub']) ?? '', $employees)); + if (!empty($employeeGitHub)) { + $isEmployee = true; + $employeeNumber = $isEmployee ? $employees[$employeeGitHub]['spot'] : ''; + $createdAt = new \DateTime($employees[$employeeGitHub]['memberSince'] ?? ''); + } + } + + $isPlatinum = $user->getInternalId() % 100 === 0; + } else { + $name = $mock === 'normal-long' ? 'Sir First Walter O\'Brian Junior' : 'Walter O\'Brian'; + $createdAt = new \DateTime('now'); + $githubName = $mock === 'normal-no-github' ? '' : ($mock === 'normal-long' ? 'sir-first-walterobrian-junior' : 'walterobrian'); + $isHero = $mock === 'hero'; + $isContributor = $mock === 'contributor'; + $isEmployee = \str_starts_with($mock, 'employee'); + $employeeNumber = match ($mock) { + 'employee' => '1', + 'employee-2digit' => '18', + default => '' + }; + + $isPlatinum = $mock === 'platinum'; + } + + if ($isEmployee) { + $isContributor = false; + $isHero = false; + } + + if ($isHero) { + $isContributor = false; + $isEmployee = false; + } + + if ($isContributor) { + $isHero = false; + $isEmployee = false; + } + + $isGolden = $isEmployee || $isHero || $isContributor; + $isPlatinum = $isGolden ? false : $isPlatinum; + $memberSince = \strtoupper('Member since ' . $createdAt->format('M') . ' ' . $createdAt->format('d') . ', ' . $createdAt->format('o')); + + $imagePath = $isGolden ? 'front-golden.png' : ($isPlatinum ? 'front-platinum.png' : 'front.png'); + + $baseImage = new \Imagick(__DIR__ . '/../../../public/images/cards/cloud/' . $imagePath); + + if ($isEmployee) { + $image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/employee.png'); + $image->setGravity(Imagick::GRAVITY_CENTER); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 793, 35); + + $text = new \ImagickDraw(); + $text->setTextAlignment(Imagick::ALIGN_CENTER); + $text->setFont(__DIR__ . '/../../../public/fonts/Inter-Bold.ttf'); + $text->setFillColor(new \ImagickPixel('#FFFADF')); + $text->setFontSize(\strlen($employeeNumber) <= 2 ? 54 : 48); + $text->setFontWeight(700); + $metricsText = $baseImage->queryFontMetrics($text, $employeeNumber); + + $hashtag = new \ImagickDraw(); + $hashtag->setTextAlignment(Imagick::ALIGN_CENTER); + $hashtag->setFont(__DIR__ . '/../../../public/fonts/Inter-Bold.ttf'); + $hashtag->setFillColor(new \ImagickPixel('#FFFADF')); + $hashtag->setFontSize(28); + $hashtag->setFontWeight(700); + $metricsHashtag = $baseImage->queryFontMetrics($hashtag, '#'); + + $startX = 898; + $totalWidth = $metricsHashtag['textWidth'] + 12 + $metricsText['textWidth']; + + $hashtagX = ($metricsHashtag['textWidth'] / 2); + $textX = $hashtagX + 12 + ($metricsText['textWidth'] / 2); + + $hashtagX -= $totalWidth / 2; + $textX -= $totalWidth / 2; + + $hashtagX += $startX; + $textX += $startX; + + $baseImage->annotateImage($hashtag, $hashtagX, 150, 0, '#'); + $baseImage->annotateImage($text, $textX, 150, 0, $employeeNumber); + } + + if ($isContributor) { + $image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/contributor.png'); + $image->setGravity(Imagick::GRAVITY_CENTER); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 793, 34); + } + + if ($isHero) { + $image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/hero.png'); + $image->setGravity(Imagick::GRAVITY_CENTER); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 793, 34); + } + + setlocale(LC_ALL, "en_US.utf8"); + // $name = \iconv("utf-8", "ascii//TRANSLIT", $name); + // $memberSince = \iconv("utf-8", "ascii//TRANSLIT", $memberSince); + // $githubName = \iconv("utf-8", "ascii//TRANSLIT", $githubName); + + $text = new \ImagickDraw(); + $text->setTextAlignment(Imagick::ALIGN_CENTER); + $text->setFont(__DIR__ . '/../../../public/fonts/Poppins-Bold.ttf'); + $text->setFillColor(new \ImagickPixel('#FFFFFF')); + + if (\strlen($name) > 32) { + $name = \substr($name, 0, 32); + } + + if (\strlen($name) <= 23) { + $text->setFontSize(80); + $scalingDown = false; + } else { + $text->setFontSize(54); + $scalingDown = true; + } + $text->setFontWeight(700); + $baseImage->annotateImage($text, 512, 477, 0, $name); + + $text = new \ImagickDraw(); + $text->setTextAlignment(Imagick::ALIGN_CENTER); + $text->setFont(__DIR__ . '/../../../public/fonts/Inter-SemiBold.ttf'); + $text->setFillColor(new \ImagickPixel($isGolden || $isPlatinum ? '#FFFFFF' : '#FFB9CC')); + $text->setFontSize(27); + $text->setFontWeight(600); + $text->setTextKerning(1.08); + $baseImage->annotateImage($text, 512, 541, 0, \strtoupper($memberSince)); + + if (!empty($githubName)) { + $text = new \ImagickDraw(); + $text->setTextAlignment(Imagick::ALIGN_CENTER); + $text->setFont(__DIR__ . '/../../../public/fonts/Inter-Regular.ttf'); + $text->setFillColor(new \ImagickPixel('#FFFFFF')); + $text->setFontSize($scalingDown ? 28 : 32); + $text->setFontWeight(400); + $metrics = $baseImage->queryFontMetrics($text, $githubName); + + $baseImage->annotateImage($text, 512 + 20 + 4, 373 + ($scalingDown ? 2 : 0), 0, $githubName); + + $image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/github.png'); + $image->setGravity(Imagick::GRAVITY_CENTER); + $precisionFix = 5; + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 512 - ($metrics['textWidth'] / 2) - 20 - 4, 373 - ($metrics['textHeight'] - $precisionFix)); + } + + if (!empty($width) || !empty($height)) { + $baseImage->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1); + } + + $response + ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache + ->setContentType('image/png') + ->file($baseImage->getImageBlob()); + }); + +App::get('/v1/cards/cloud-back') + ->desc('Get Back Of Cloud Card') + ->groups(['api', 'avatars']) + ->label('scope', 'avatars.read') + ->label('cache', true) + ->label('cache.resourceType', 'cards/cloud-back') + ->label('cache.resource', 'card-back/{request.userId}') + ->label('docs', false) + ->label('origin', '*') + ->param('userId', '', new UID(), 'User ID.', true) + ->param('mock', '', new WhiteList(['golden', 'normal', 'platinum']), 'Mocking behaviour.', true) + ->param('width', 0, new Range(0, 512), 'Resize image width, Pass an integer between 0 to 512.', true) + ->param('height', 0, new Range(0, 320), 'Resize image height, Pass an integer between 0 to 320.', true) + ->inject('user') + ->inject('project') + ->inject('dbForProject') + ->inject('dbForConsole') + ->inject('response') + ->inject('heroes') + ->inject('contributors') + ->inject('employees') + ->inject('logger') + ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) { + $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); + + if ($user->isEmpty() && empty($mock)) { + throw new Exception(Exception::USER_NOT_FOUND); + } + + if (!$mock) { + $userId = $user->getId(); + $email = $user->getAttribute('email', ''); + + $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger); + $githubId = $gitHub['id'] ?? ''; + + $isHero = \array_key_exists($email, $heroes); + $isContributor = \in_array($githubId, $contributors); + $isEmployee = \array_key_exists($email, $employees); + + $isGolden = $isEmployee || $isHero || $isContributor; + $isPlatinum = $user->getInternalId() % 100 === 0; + } else { + $userId = '63e0bcf3c3eb803ba530'; + + $isGolden = $mock === 'golden'; + $isPlatinum = $mock === 'platinum'; + } + + $userId = 'UID ' . $userId; + + $isPlatinum = $isGolden ? false : $isPlatinum; + + $imagePath = $isGolden ? 'back-golden.png' : ($isPlatinum ? 'back-platinum.png' : 'back.png'); + + $baseImage = new \Imagick(__DIR__ . '/../../../public/images/cards/cloud/' . $imagePath); + + setlocale(LC_ALL, "en_US.utf8"); + // $userId = \iconv("utf-8", "ascii//TRANSLIT", $userId); + + $text = new \ImagickDraw(); + $text->setTextAlignment(Imagick::ALIGN_CENTER); + $text->setFont(__DIR__ . '/../../../public/fonts/SourceCodePro-Regular.ttf'); + $text->setFillColor(new \ImagickPixel($isGolden ? '#664A1E' : ($isPlatinum ? '#555555' : '#E8E9F0'))); + $text->setFontSize(28); + $text->setFontWeight(400); + $baseImage->annotateImage($text, 512, 596, 0, $userId); + + if (!empty($width) || !empty($height)) { + $baseImage->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1); + } + + $response + ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache + ->setContentType('image/png') + ->file($baseImage->getImageBlob()); + }); + +App::get('/v1/cards/cloud-og') + ->desc('Get OG Image From Cloud Card') + ->groups(['api', 'avatars']) + ->label('scope', 'avatars.read') + ->label('cache', true) + ->label('cache.resourceType', 'cards/cloud-og') + ->label('cache.resource', 'card-og/{request.userId}') + ->label('docs', false) + ->label('origin', '*') + ->param('userId', '', new UID(), 'User ID.', true) + ->param('mock', '', new WhiteList(['employee', 'employee-2digit', 'hero', 'contributor', 'normal', 'platinum', 'normal-no-github', 'normal-long', 'normal-long-right', 'normal-long-middle', 'normal-bg2', 'normal-bg3', 'normal-right', 'normal-middle', 'platinum-right', 'platinum-middle', 'hero-middle', 'hero-right', 'contributor-right', 'employee-right', 'contributor-middle', 'employee-middle', 'employee-2digit-middle', 'employee-2digit-right']), 'Mocking behaviour.', true) + ->param('width', 0, new Range(0, 1024), 'Resize image card width, Pass an integer between 0 to 1024.', true) + ->param('height', 0, new Range(0, 1024), 'Resize image card height, Pass an integer between 0 to 1024.', true) + ->inject('user') + ->inject('project') + ->inject('dbForProject') + ->inject('dbForConsole') + ->inject('response') + ->inject('heroes') + ->inject('contributors') + ->inject('employees') + ->inject('logger') + ->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForConsole, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) { + $user = Authorization::skip(fn () => $dbForConsole->getDocument('users', $userId)); + + if ($user->isEmpty() && empty($mock)) { + throw new Exception(Exception::USER_NOT_FOUND); + } + + if (!$mock) { + $internalId = $user->getInternalId(); + $bgVariation = $internalId % 3 === 0 ? '1' : ($internalId % 3 === 1 ? '2' : '3'); + $cardVariation = $internalId % 3 === 0 ? '1' : ($internalId % 3 === 1 ? '2' : '3'); + + $name = $user->getAttribute('name', 'Anonymous'); + $email = $user->getAttribute('email', ''); + $createdAt = new \DateTime($user->getCreatedAt()); + + $gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForConsole, $logger); + $githubName = $gitHub['name'] ?? ''; + $githubId = $gitHub['id'] ?? ''; + + $isHero = \array_key_exists($email, $heroes); + $isContributor = \in_array($githubId, $contributors); + $isEmployee = \array_key_exists($email, $employees); + $employeeNumber = $isEmployee ? $employees[$email]['spot'] : ''; + + if ($isHero) { + $createdAt = new \DateTime($heroes[$email]['memberSince'] ?? ''); + } elseif ($isEmployee) { + $createdAt = new \DateTime($employees[$email]['memberSince'] ?? ''); + } + + if (!$isEmployee && !empty($githubName)) { + $employeeGitHub = \array_search(\strtolower($githubName), \array_map(fn ($employee) => \strtolower($employee['gitHub']) ?? '', $employees)); + if (!empty($employeeGitHub)) { + $isEmployee = true; + $employeeNumber = $isEmployee ? $employees[$employeeGitHub]['spot'] : ''; + $createdAt = new \DateTime($employees[$employeeGitHub]['memberSince'] ?? ''); + } + } + + $isPlatinum = $user->getInternalId() % 100 === 0; + } else { + $bgVariation = \str_ends_with($mock, '-bg2') ? '2' : (\str_ends_with($mock, '-bg3') ? '3' : '1'); + $cardVariation = \str_ends_with($mock, '-right') ? '2' : (\str_ends_with($mock, '-middle') ? '3' : '1'); + $name = \str_starts_with($mock, 'normal-long') ? 'Sir First Walter O\'Brian Junior' : 'Walter O\'Brian'; + $createdAt = new \DateTime('now'); + $githubName = $mock === 'normal-no-github' ? '' : (\str_starts_with($mock, 'normal-long') ? 'sir-first-walterobrian-junior' : 'walterobrian'); + $isHero = \str_starts_with($mock, 'hero'); + $isContributor = \str_starts_with($mock, 'contributor'); + $isEmployee = \str_starts_with($mock, 'employee'); + $employeeNumber = match ($mock) { + 'employee' => '1', + 'employee-right' => '1', + 'employee-middle' => '1', + 'employee-2digit' => '18', + 'employee-2digit-right' => '18', + 'employee-2digit-middle' => '18', + default => '' + }; + + $isPlatinum = \str_starts_with($mock, 'platinum'); + } + + if ($isEmployee) { + $isContributor = false; + $isHero = false; + } + + if ($isHero) { + $isContributor = false; + $isEmployee = false; + } + + if ($isContributor) { + $isHero = false; + $isEmployee = false; + } + + $isGolden = $isEmployee || $isHero || $isContributor; + $isPlatinum = $isGolden ? false : $isPlatinum; + $memberSince = \strtoupper('Member since ' . $createdAt->format('M') . ' ' . $createdAt->format('d') . ', ' . $createdAt->format('o')); + + $baseImage = new \Imagick(__DIR__ . "/../../../public/images/cards/cloud/og-background{$bgVariation}.png"); + + $cardType = $isGolden ? '-golden' : ($isPlatinum ? '-platinum' : ''); + + $image = new Imagick(__DIR__ . "/../../../public/images/cards/cloud/og-card{$cardType}{$cardVariation}.png"); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 1008 / 2 - $image->getImageWidth() / 2, 1008 / 2 - $image->getImageHeight() / 2); + + $imageLogo = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/og-background-logo.png'); + $imageShadow = new Imagick(__DIR__ . "/../../../public/images/cards/cloud/og-shadow{$cardType}.png"); + if ($cardVariation === '1') { + $baseImage->compositeImage($imageLogo, Imagick::COMPOSITE_OVER, 32, 1008 - $imageLogo->getImageHeight() - 32); + $baseImage->compositeImage($imageShadow, Imagick::COMPOSITE_OVER, -450, 700); + } elseif ($cardVariation === '2') { + $baseImage->compositeImage($imageLogo, Imagick::COMPOSITE_OVER, 1008 - $imageLogo->getImageWidth() - 32, 1008 - $imageLogo->getImageHeight() - 32); + $baseImage->compositeImage($imageShadow, Imagick::COMPOSITE_OVER, -20, 710); + } else { + $baseImage->compositeImage($imageLogo, Imagick::COMPOSITE_OVER, 1008 - $imageLogo->getImageWidth() - 32, 1008 - $imageLogo->getImageHeight() - 32); + $baseImage->compositeImage($imageShadow, Imagick::COMPOSITE_OVER, -135, 710); + } + + if ($isEmployee) { + $file = $cardVariation === '3' ? 'employee-skew.png' : 'employee.png'; + $image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/' . $file); + $image->setGravity(Imagick::GRAVITY_CENTER); + + $hashtag = new \ImagickDraw(); + $hashtag->setTextAlignment(Imagick::ALIGN_LEFT); + $hashtag->setFont(__DIR__ . '/../../../public/fonts/Inter-Bold.ttf'); + $hashtag->setFillColor(new \ImagickPixel('#FFFADF')); + $hashtag->setFontSize(20); + $hashtag->setFontWeight(700); + + $text = new \ImagickDraw(); + $text->setTextAlignment(Imagick::ALIGN_LEFT); + $text->setFont(__DIR__ . '/../../../public/fonts/Inter-Bold.ttf'); + $text->setFillColor(new \ImagickPixel('#FFFADF')); + $text->setFontSize(\strlen($employeeNumber) <= 1 ? 36 : 28); + $text->setFontWeight(700); + + if ($cardVariation === '3') { + $hashtag->setFontSize(16); + $text->setFontSize(\strlen($employeeNumber) <= 1 ? 30 : 26); + + $hashtag->skewY(20); + $hashtag->skewX(20); + $text->skewY(20); + $text->skewX(20); + } + + $metricsHashtag = $baseImage->queryFontMetrics($hashtag, '#'); + $metricsText = $baseImage->queryFontMetrics($text, $employeeNumber); + + $group = new Imagick(); + $groupWidth = $metricsHashtag['textWidth'] + 6 + $metricsText['textWidth']; + + if ($cardVariation === '1') { + $group->newImage($groupWidth, $metricsText['textHeight'], '#00000000'); + $group->annotateImage($hashtag, 0, $metricsText['textHeight'], 0, '#'); + $group->annotateImage($text, $metricsHashtag['textWidth'] + 6, $metricsText['textHeight'], 0, $employeeNumber); + + $image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1); + $image->rotateImage(new ImagickPixel('#00000000'), -20); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 612, 203); + + $group->rotateImage(new ImagickPixel('#00000000'), -22); + + if (\strlen($employeeNumber) <= 1) { + $baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 660, 245); + } else { + $baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 655, 247); + } + } elseif ($cardVariation === '2') { + $group->newImage($groupWidth, $metricsText['textHeight'], '#00000000'); + $group->annotateImage($hashtag, 0, $metricsText['textHeight'], 0, '#'); + $group->annotateImage($text, $metricsHashtag['textWidth'] + 6, $metricsText['textHeight'], 0, $employeeNumber); + + $image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1); + $image->rotateImage(new ImagickPixel('#00000000'), 30); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 715, 425); + + $group->rotateImage(new ImagickPixel('#00000000'), 32); + + if (\strlen($employeeNumber) <= 1) { + $baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 775, 465); + } else { + $baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 767, 470); + } + } else { + $group->newImage(300, 300, '#00000000'); + + $hashtag->annotation(0, $metricsText['textHeight'], '#'); + $text->annotation($metricsHashtag['textWidth'] + 2, $metricsText['textHeight'], $employeeNumber); + + $group->drawImage($hashtag); + $group->drawImage($text); + + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 640, 293); + + if (\strlen($employeeNumber) <= 1) { + $baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 670, 317); + } else { + $baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, 663, 322); + } + } + } + + if ($isContributor) { + $file = $cardVariation === '3' ? 'contributor-skew.png' : 'contributor.png'; + $image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/' . $file); + $image->setGravity(Imagick::GRAVITY_CENTER); + + if ($cardVariation === '1') { + $image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1); + $image->rotateImage(new ImagickPixel('#00000000'), -20); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 612, 203); + } elseif ($cardVariation === '2') { + $image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1); + $image->rotateImage(new ImagickPixel('#00000000'), 30); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 715, 425); + } else { + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 640, 293); + } + } + + if ($isHero) { + $file = $cardVariation === '3' ? 'hero-skew.png' : 'hero.png'; + $image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/' . $file); + $image->setGravity(Imagick::GRAVITY_CENTER); + + if ($cardVariation === '1') { + $image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1); + $image->rotateImage(new ImagickPixel('#00000000'), -20); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 612, 203); + } elseif ($cardVariation === '2') { + $image->resizeImage(120, 120, Imagick::FILTER_LANCZOS, 1); + $image->rotateImage(new ImagickPixel('#00000000'), 30); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 715, 425); + } else { + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 640, 293); + } + } + + setlocale(LC_ALL, "en_US.utf8"); + // $name = \iconv("utf-8", "ascii//TRANSLIT", $name); + // $memberSince = \iconv("utf-8", "ascii//TRANSLIT", $memberSince); + // $githubName = \iconv("utf-8", "ascii//TRANSLIT", $githubName); + + $textName = new \ImagickDraw(); + $textName->setTextAlignment(Imagick::ALIGN_CENTER); + $textName->setFont(__DIR__ . '/../../../public/fonts/Poppins-Bold.ttf'); + $textName->setFillColor(new \ImagickPixel('#FFFFFF')); + + if (\strlen($name) > 32) { + $name = \substr($name, 0, 32); + } + + if ($cardVariation === '1') { + if (\strlen($name) <= 23) { + $scalingDown = false; + $textName->setFontSize(54); + } else { + $scalingDown = true; + $textName->setFontSize(36); + } + } elseif ($cardVariation === '2') { + if (\strlen($name) <= 23) { + $scalingDown = false; + $textName->setFontSize(50); + } else { + $scalingDown = true; + $textName->setFontSize(34); + } + } else { + if (\strlen($name) <= 23) { + $scalingDown = false; + $textName->setFontSize(44); + } else { + $scalingDown = true; + $textName->setFontSize(32); + } + } + + $textName->setFontWeight(700); + + $textMember = new \ImagickDraw(); + $textMember->setTextAlignment(Imagick::ALIGN_CENTER); + $textMember->setFont(__DIR__ . '/../../../public/fonts/Inter-Medium.ttf'); + $textMember->setFillColor(new \ImagickPixel($isGolden || $isPlatinum ? '#FFFFFF' : '#FFB9CC')); + $textMember->setFontWeight(500); + $textMember->setTextKerning(1.12); + + if ($cardVariation === '1') { + $textMember->setFontSize(21); + + $baseImage->annotateImage($textName, 550, 600, -22, $name); + $baseImage->annotateImage($textMember, 585, 635, -22, $memberSince); + } elseif ($cardVariation === '2') { + $textMember->setFontSize(20); + + $baseImage->annotateImage($textName, 435, 590, 31.37, $name); + $baseImage->annotateImage($textMember, 412, 628, 31.37, $memberSince); + } else { + $textMember->setFontSize(16); + + $textName->skewY(20); + $textName->skewX(20); + $textName->annotation(320, 700, $name); + + $textMember->skewY(20); + $textMember->skewX(20); + $textMember->annotation(330, 735, $memberSince); + + $baseImage->drawImage($textName); + $baseImage->drawImage($textMember); + } + + if (!empty($githubName)) { + $text = new \ImagickDraw(); + $text->setTextAlignment(Imagick::ALIGN_LEFT); + $text->setFont(__DIR__ . '/../../../public/fonts/Inter-Regular.ttf'); + $text->setFillColor(new \ImagickPixel('#FFFFFF')); + $text->setFontSize($scalingDown ? 16 : 20); + $text->setFontWeight(400); + + if ($cardVariation === '1') { + $metrics = $baseImage->queryFontMetrics($text, $githubName); + + $group = new Imagick(); + $groupWidth = $metrics['textWidth'] + 32 + 4; + $group->newImage($groupWidth, $metrics['textHeight'] + 10, '#00000000'); + $image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/github.png'); + $image->setGravity(Imagick::GRAVITY_CENTER); + $image->resizeImage(32, 32, Imagick::FILTER_LANCZOS, 1); + $precisionFix = -1; + + $group->compositeImage($image, Imagick::COMPOSITE_OVER, 0, 0); + $group->annotateImage($text, 32 + 4, $metrics['textHeight'] - $precisionFix, 0, $githubName); + + $group->rotateImage(new ImagickPixel('#00000000'), -22); + $x = 510 - $group->getImageWidth() / 2; + $y = 530 - $group->getImageHeight() / 2; + $baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, $x, $y); + } elseif ($cardVariation === '2') { + $metrics = $baseImage->queryFontMetrics($text, $githubName); + + $group = new Imagick(); + $groupWidth = $metrics['textWidth'] + 32 + 4; + $group->newImage($groupWidth, $metrics['textHeight'] + 10, '#00000000'); + $image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/github.png'); + $image->setGravity(Imagick::GRAVITY_CENTER); + $image->resizeImage(32, 32, Imagick::FILTER_LANCZOS, 1); + $precisionFix = -1; + + $group->compositeImage($image, Imagick::COMPOSITE_OVER, 0, 0); + $group->annotateImage($text, 32 + 4, $metrics['textHeight'] - $precisionFix, 0, $githubName); + + $group->rotateImage(new ImagickPixel('#00000000'), 31.11); + $x = 485 - $group->getImageWidth() / 2; + $y = 530 - $group->getImageHeight() / 2; + $baseImage->compositeImage($group, Imagick::COMPOSITE_OVER, $x, $y); + } else { + $text->skewY(20); + $text->skewX(20); + $text->setTextAlignment(\Imagick::ALIGN_CENTER); + + $text->annotation(320 + 15 + 2, 640, $githubName); + $metrics = $baseImage->queryFontMetrics($text, $githubName); + + $image = new Imagick(__DIR__ . '/../../../public/images/cards/cloud/github-skew.png'); + $image->setGravity(Imagick::GRAVITY_CENTER); + $baseImage->compositeImage($image, Imagick::COMPOSITE_OVER, 512 - ($metrics['textWidth'] / 2), 518 + \strlen($githubName) * 1.3); + + $baseImage->drawImage($text); + } + } + + if (!empty($width) || !empty($height)) { + $baseImage->resizeImage($width, $height, Imagick::FILTER_LANCZOS, 1); + } + + $response + ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)) . ' GMT') // 45 days cache + ->setContentType('image/png') + ->file($baseImage->getImageBlob()); }); diff --git a/app/controllers/api/databases.php b/app/controllers/api/databases.php index 4601baf03..4c243c4c8 100644 --- a/app/controllers/api/databases.php +++ b/app/controllers/api/databases.php @@ -176,7 +176,7 @@ App::post('/v1/databases') ])); $database = $dbForProject->getDocument('databases', $databaseId); - $collections = Config::getParam('collections', [])['collections'] ?? []; + $collections = (Config::getParam('collections', [])['databases'] ?? [])['collections'] ?? []; if (empty($collections)) { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'The "collections" collection is not configured.'); } diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 30bae50ac..d2f6fc6c0 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -18,17 +18,18 @@ use Utopia\Config\Config; use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; -use Utopia\Database\DateTime; use Utopia\Database\Helpers\Permission; use Utopia\Database\Query; use Utopia\Database\Helpers\Role; use Utopia\Database\Validator\DatetimeValidator; +use Utopia\Database\DateTime; use Utopia\Database\Validator\UID; use Utopia\Domains\Domain; use Appwrite\Extend\Exception; use Appwrite\Utopia\Database\Validator\Queries\Projects; use Utopia\Cache\Cache; use Utopia\Pools\Group; +use Utopia\Database\Exception\Duplicate; use Utopia\Validator\ArrayList; use Utopia\Validator\Boolean; use Utopia\Validator\Hostname; @@ -88,46 +89,81 @@ App::post('/v1/projects') } $projectId = ($projectId == 'unique()') ? ID::unique() : $projectId; + + $backups['database_db_fra1_02'] = ['from' => '7:30', 'to' => '8:15']; + $backups['database_db_fra1_03'] = ['from' => '10:30', 'to' => '11:15']; + $backups['database_db_fra1_04'] = ['from' => '13:30', 'to' => '14:15']; + $backups['database_db_fra1_05'] = ['from' => '4:30', 'to' => '5:15']; + $databases = Config::getParam('pools-database', []); - $database = $databases[array_rand($databases)]; + + /** + * Extract db from list while backing + */ + if (count($databases) > 1) { + $now = new \DateTime(); + + foreach ($databases as $index => $database) { + if (empty($backups[$database])) { + continue; + } + $backup = $backups[$database]; + $from = \DateTime::createFromFormat('H:i', $backup['from']); + $to = \DateTime::createFromFormat('H:i', $backup['to']); + if ($now >= $from && $now <= $to) { + unset($databases[$index]); + break; + } + } + } + + if ($index = array_search('database_db_fra1_05', $databases)) { + $database = $databases[$index]; + } else { + $database = $databases[array_rand($databases)]; + } if ($projectId === 'console') { throw new Exception(Exception::PROJECT_RESERVED_PROJECT, "'console' is a reserved project."); } - $project = $dbForConsole->createDocument('projects', new Document([ - '$id' => $projectId, - '$permissions' => [ - Permission::read(Role::team(ID::custom($teamId))), - Permission::update(Role::team(ID::custom($teamId), 'owner')), - Permission::update(Role::team(ID::custom($teamId), 'developer')), - Permission::delete(Role::team(ID::custom($teamId), 'owner')), - Permission::delete(Role::team(ID::custom($teamId), 'developer')), - ], - 'name' => $name, - 'teamInternalId' => $team->getInternalId(), - 'teamId' => $team->getId(), - 'region' => $region, - 'description' => $description, - 'logo' => $logo, - 'url' => $url, - 'version' => APP_VERSION_STABLE, - 'legalName' => $legalName, - 'legalCountry' => $legalCountry, - 'legalState' => $legalState, - 'legalCity' => $legalCity, - 'legalAddress' => $legalAddress, - 'legalTaxId' => ID::custom($legalTaxId), - 'services' => new stdClass(), - 'platforms' => null, - 'authProviders' => [], - 'webhooks' => null, - 'keys' => null, - 'domains' => null, - 'auths' => $auths, - 'search' => implode(' ', [$projectId, $name]), - 'database' => $database, - ])); + try { + $project = $dbForConsole->createDocument('projects', new Document([ + '$id' => $projectId, + '$permissions' => [ + Permission::read(Role::team(ID::custom($teamId))), + Permission::update(Role::team(ID::custom($teamId), 'owner')), + Permission::update(Role::team(ID::custom($teamId), 'developer')), + Permission::delete(Role::team(ID::custom($teamId), 'owner')), + Permission::delete(Role::team(ID::custom($teamId), 'developer')), + ], + 'name' => $name, + 'teamInternalId' => $team->getInternalId(), + 'teamId' => $team->getId(), + 'region' => $region, + 'description' => $description, + 'logo' => $logo, + 'url' => $url, + 'version' => APP_VERSION_STABLE, + 'legalName' => $legalName, + 'legalCountry' => $legalCountry, + 'legalState' => $legalState, + 'legalCity' => $legalCity, + 'legalAddress' => $legalAddress, + 'legalTaxId' => ID::custom($legalTaxId), + 'services' => new stdClass(), + 'platforms' => null, + 'authProviders' => [], + 'webhooks' => null, + 'keys' => null, + 'domains' => null, + 'auths' => $auths, + 'search' => implode(' ', [$projectId, $name]), + 'database' => $database + ])); + } catch (Duplicate $th) { + throw new Exception(Exception::PROJECT_ALREADY_EXISTS); + } $dbForProject = new Database($pools->get($database)->pop()->getResource(), $cache); $dbForProject->setNamespace("_{$project->getInternalId()}"); @@ -140,7 +176,7 @@ App::post('/v1/projects') $adapter->setup(); /** @var array $collections */ - $collections = Config::getParam('collections', []); + $collections = Config::getParam('collections', [])['projects'] ?? []; foreach ($collections as $key => $collection) { if (($collection['$collection'] ?? '') !== Database::METADATA) { @@ -1221,9 +1257,12 @@ App::post('/v1/projects/:projectId/domains') throw new Exception(Exception::PROJECT_NOT_FOUND); } + if ($domain === App::getEnv('_APP_DOMAIN', '') || $domain === App::getEnv('_APP_DOMAIN_TARGET', '')) { + throw new Exception(Exception::DOMAIN_FORBIDDEN); + } + $document = $dbForConsole->findOne('domains', [ - Query::equal('domain', [$domain]), - Query::equal('projectInternalId', [$project->getInternalId()]), + Query::equal('domain', [$domain]) ]); if ($document && !$document->isEmpty()) { @@ -1421,6 +1460,10 @@ App::delete('/v1/projects/:projectId/domains/:domainId') throw new Exception(Exception::DOMAIN_NOT_FOUND); } + if ($domain->getAttribute('domain') === App::getEnv('_APP_DOMAIN', '') || $domain->getAttribute('domain') === App::getEnv('_APP_DOMAIN_TARGET', '')) { + throw new Exception(Exception::DOMAIN_FORBIDDEN); + } + $dbForConsole->deleteDocument('domains', $domain->getId()); $dbForConsole->deleteCachedDocument('projects', $project->getId()); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index ab5febf8f..e545fcb2a 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -79,7 +79,7 @@ App::post('/v1/storage/buckets') $permissions = Permission::aggregate($permissions); try { - $files = Config::getParam('collections', [])['files'] ?? []; + $files = (Config::getParam('collections', [])['buckets'] ?? [])['files'] ?? []; if (empty($files)) { throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Files collection is not configured.'); } @@ -352,8 +352,7 @@ App::post('/v1/storage/buckets/:bucketId/files') ->inject('mode') ->inject('deviceFiles') ->inject('deviceLocal') - ->inject('deletes') - ->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal, Delete $deletes) { + ->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $events, string $mode, Device $deviceFiles, Device $deviceLocal) { $bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId)); @@ -649,11 +648,6 @@ App::post('/v1/storage/buckets/:bucketId/files') ->setContext('bucket', $bucket) ; - $deletes - ->setType(DELETE_TYPE_CACHE_BY_RESOURCE) - ->setResource('file/' . $file->getId()) - ; - $metadata = null; // was causing leaks as it was passed by reference $response diff --git a/app/controllers/general.php b/app/controllers/general.php index afb9b9f33..365995990 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -425,6 +425,7 @@ App::error() $log->setType(Log::TYPE_ERROR); $log->setMessage($error->getMessage()); + $log->addTag('database', $project->getAttribute('database', 'console')); $log->addTag('method', $route->getMethod()); $log->addTag('url', $route->getPath()); $log->addTag('verboseType', get_class($error)); @@ -437,7 +438,7 @@ App::error() $log->addExtra('line', $error->getLine()); $log->addExtra('trace', $error->getTraceAsString()); $log->addExtra('detailedTrace', $error->getTrace()); - $log->addExtra('roles', Authorization::$roles); + $log->addExtra('roles', Authorization::getRoles()); $action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD"); $log->setAction($action); @@ -580,7 +581,7 @@ App::get('/humans.txt') $response->text($template->render(false)); }); -App::get('/.well-known/acme-challenge') +App::get('/.well-known/acme-challenge/*') ->desc('SSL Verification') ->label('scope', 'public') ->label('docs', false) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 9fe0574df..566a67a0f 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -549,7 +549,7 @@ App::shutdown() ]) ; $signature = md5($data); - $cacheLog = $dbForProject->getDocument('cache', $key); + $cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key)); $accessedAt = $cacheLog->getAttribute('accessedAt', ''); $now = DateTime::now(); if ($cacheLog->isEmpty()) { diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php index 8115f0928..571049d9e 100644 --- a/app/controllers/web/console.php +++ b/app/controllers/web/console.php @@ -1,19 +1,61 @@ alias('/') + ->alias('auth/*') ->alias('/invite') ->alias('/login') ->alias('/recover') - ->alias('/register') + ->alias('/register/*') ->groups(['web']) ->label('permission', 'public') ->label('scope', 'home') + ->inject('request') ->inject('response') - ->action(function (Response $response) { + ->action(function (Request $request, Response $response) { $fallback = file_get_contents(__DIR__ . '/../../../console/index.html'); + + // Card SSR + if (\str_starts_with($request->getURI(), '/card')) { + $urlCunks = \explode('/', $request->getURI()); + $userId = $urlCunks[\count($urlCunks) - 1] ?? ''; + + $domain = $request->getProtocol() . '://' . $request->getHostname(); + + if (!empty($userId)) { + $ogImageUrl = $domain . '/v1/cards/cloud-og?userId=' . $userId; + } else { + $ogImageUrl = $domain . '/v1/cards/cloud-og?mock=normal'; + } + + $ogTags = [ + 'Appwrite Cloud Card', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + ]; + + $fallback = \str_replace('', \implode('', $ogTags), $fallback); + } + $response->html($fallback); }); diff --git a/app/http.php b/app/http.php index b1ed21b8d..8be7f88e8 100644 --- a/app/http.php +++ b/app/http.php @@ -85,10 +85,8 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) { Console::success('[Setup] - Server database init started...'); - /** @var array $collections */ - $collections = Config::getParam('collections', []); - try { + $cache = $app->getResource('cache'); /** @var Utopia\Cache\Cache $cache */ Console::success('[Setup] - Creating database: appwrite...'); $dbForConsole->create(); } catch (\Exception $e) { @@ -105,7 +103,10 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) { $adapter->setup(); } - foreach ($collections as $key => $collection) { + /** @var array $collections */ + $collections = Config::getParam('collections', []); + $consoleCollections = $collections['console']; + foreach ($consoleCollections as $key => $collection) { if (($collection['$collection'] ?? '') !== Database::METADATA) { continue; } @@ -177,7 +178,7 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) { $bucket = $dbForConsole->getDocument('buckets', 'default'); Console::success('[Setup] - Creating files collection for default bucket...'); - $files = $collections['files'] ?? []; + $files = $collections['buckets']['files'] ?? []; if (empty($files)) { throw new Exception('Files collection is not configured.'); } diff --git a/app/init.php b/app/init.php index f799ec41e..ee0f35b3b 100644 --- a/app/init.php +++ b/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)) : [], 'authWhitelistIPs' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [], + 'authProviders' => [ + 'githubEnabled' => true, + 'githubSecret' => App::getEnv('_APP_CONSOLE_GITHUB_SECRET', ''), + 'githubAppid' => App::getEnv('_APP_CONSOLE_GITHUB_APP_ID', '') + ], ]); }, []); @@ -1289,3 +1294,21 @@ App::setResource('schema', function ($utopia, $dbForProject) { $params, ); }, ['utopia', 'dbForProject']); + +App::setResource('contributors', function () { + $path = 'app/config/cloud/contributors.json'; + $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : []; + return $list; +}, []); + +App::setResource('employees', function () { + $path = 'app/config/cloud/employees.json'; + $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : []; + return $list; +}, []); + +App::setResource('heroes', function () { + $path = 'app/config/cloud/heroes.json'; + $list = (file_exists($path)) ? json_decode(file_get_contents($path), true) : []; + return $list; +}, []); diff --git a/app/views/general/error.phtml b/app/views/general/error.phtml index d5a11246f..69858f29e 100644 --- a/app/views/general/error.phtml +++ b/app/views/general/error.phtml @@ -17,49 +17,96 @@ $title = $this->getParam('title', '') <?php echo $title; ?> + + + + + + - + +
+
+
+

Error

+

+ Error ID: + +
+

Back to

+ -
-

Error

- -

- - Error ID: - - -
- -

Back to

- - - -
- -

Error Trace

- - - - $value) : ?> - - - - + +
+

Error Trace

+ +
- - - - - -
+ $value) : ?> + + + + + +
+ + + + + +
- - -
- +
+ + +
+ Go back +
+
+
+ Powered by + + + + + + + + +
+
+ + \ No newline at end of file diff --git a/app/worker.php b/app/worker.php index 78671d731..6e0f689a4 100644 --- a/app/worker.php +++ b/app/worker.php @@ -126,16 +126,15 @@ $server ->error() ->inject('error') ->inject('logger') - ->action(function (Throwable $error, Logger $logger) { + ->inject('log') + ->action(function (Throwable $error, Logger $logger, Log $log) { $version = App::getEnv('_APP_VERSION', 'UNKNOWN'); if ($error instanceof PDOException) { throw $error; } - if ($error->getCode() >= 500 || $error->getCode() === 0) { - $log = new Log(); - + if ($logger && ($error->getCode() >= 500 || $error->getCode() === 0)) { $log->setNamespace("appwrite-worker"); $log->setServer(\gethostname()); $log->setVersion($version); @@ -153,7 +152,8 @@ $server $isProduction = App::getEnv('_APP_ENV', 'development') === 'production'; $log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING); - $logger->addLog($log); + $responseCode = $logger->addLog($log); + Console::info('Usage stats log pushed with status code: ' . $responseCode); } Console::error('[Error] Type: ' . get_class($error)); diff --git a/app/workers/deletes.php b/app/workers/deletes.php index 1f338087d..acc8b9cec 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -113,10 +113,10 @@ class DeletesV1 extends Worker break; case DELETE_TYPE_CACHE_BY_RESOURCE: - $this->deleteCacheByResource($this->args['resource']); + $this->deleteCacheByResource($project, $this->args['resource']); break; case DELETE_TYPE_CACHE_BY_TIMESTAMP: - $this->deleteCacheByDate(); + $this->deleteCacheByDate($this->args['datetime']); break; case DELETE_TYPE_SCHEDULES: $this->deleteSchedules($this->args['datetime']); @@ -164,32 +164,54 @@ class DeletesV1 extends Worker } /** + * @param Document $project * @param string $resource + * @throws Exception */ - protected function deleteCacheByResource(string $resource): void + protected function deleteCacheByResource(Document $project, string $resource): void { - $this->deleteCacheFiles([ - Query::equal('resource', [$resource]), - ]); + $projectId = $project->getId(); + $dbForProject = $this->getProjectDB($project); + $document = $dbForProject->findOne('cache', [Query::equal('resource', [$resource])]); + + if ($document) { + $cache = new Cache( + new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId) + ); + + $this->deleteById( + $document, + $dbForProject, + function ($document) use ($cache, $projectId) { + $path = APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId . DIRECTORY_SEPARATOR . $document->getId(); + + if ($cache->purge($document->getId())) { + Console::success('Deleting cache file: ' . $path); + } else { + Console::error('Failed to delete cache file: ' . $path); + } + } + ); + } } - protected function deleteCacheByDate(): void + /** + * @param string $datetime + * @throws Exception + */ + protected function deleteCacheByDate(string $datetime): void { - $this->deleteCacheFiles([ - Query::lessThan('accessedAt', $this->args['datetime']), - ]); - } - - protected function deleteCacheFiles($query): void - { - $this->deleteForProjectIds(function (Document $project) use ($query) { - + $this->deleteForProjectIds(function (Document $project) use ($datetime) { $projectId = $project->getId(); $dbForProject = $this->getProjectDB($project); $cache = new Cache( new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $projectId) ); + $query = [ + Query::lessThan('accessedAt', $datetime), + ]; + $this->deleteByGroup( 'cache', $query, @@ -207,9 +229,10 @@ class DeletesV1 extends Worker }); } + /** * @param Document $document database document - * @param Document $projectId + * @param Document $project */ protected function deleteDatabase(Document $document, Document $project): void { @@ -662,19 +685,23 @@ class DeletesV1 extends Worker $executionStart = \microtime(true); - while ($sum === $limit) { - $chunk++; + try { + while ($sum === $limit) { + $chunk++; - $results = $database->find($collection, \array_merge([Query::limit($limit)], $queries)); + $results = $database->find($collection, \array_merge([Query::limit($limit)], $queries)); - $sum = count($results); + $sum = count($results); - Console::info('Deleting chunk #' . $chunk . '. Found ' . $sum . ' documents'); + Console::info('Deleting chunk #' . $chunk . '. Found ' . $sum . ' documents in collection ' . $database->getNamespace() . '_' . $collection); - foreach ($results as $document) { - $this->deleteById($document, $database, $callback); - $count++; + foreach ($results as $document) { + $this->deleteById($document, $database, $callback); + $count++; + } } + } catch (\Exception $e) { + Console::error($e->getMessage()); } $executionEnd = \microtime(true); diff --git a/app/workers/functions.php b/app/workers/functions.php index 851defb5c..e38c6e67a 100644 --- a/app/workers/functions.php +++ b/app/workers/functions.php @@ -18,6 +18,7 @@ use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; +use Utopia\Logger\Log; use Utopia\Queue\Server; use Utopia\Database\Helpers\Role; @@ -26,6 +27,7 @@ Authorization::setDefaultStatus(false); Server::setResource('execute', function () { return function ( + Log $log, Func $queueForFunctions, Database $dbForProject, Usage $queueForUsage, @@ -39,12 +41,14 @@ Server::setResource('execute', function () { string $eventData = null, string $executionId = null, ) { - $user ??= new Document(); $functionId = $function->getId(); $functionInternalId = $function->getInternalId(); $deploymentId = $function->getAttribute('deployment', ''); + $log->addTag('functionId', $functionId); + $log->addTag('projectId', $project->getId()); + /** Check if deployment exists */ $deployment = $dbForProject->getDocument('deployments', $deploymentId); $deploymentInternalId = $deployment->getInternalId(); @@ -165,10 +169,8 @@ Server::setResource('execute', function () { ->setAttribute('statusCode', $th->getCode()) ->setAttribute('stderr', $th->getMessage()); - Console::error($th->getTraceAsString()); - Console::error($th->getFile()); - Console::error($th->getLine()); - Console::error($th->getMessage()); + $error = $th->getMessage(); + $errorCode = $th->getCode(); } $execution = $dbForProject->updateDocument('executions', $executionId, $execution); @@ -259,8 +261,7 @@ $server->job() while ($sum >= $limit) { $functions = $dbForProject->find('functions', [ Query::limit($limit), - Query::offset($offset), - Query::orderAsc('name'), + Query::offset($offset) ]); $sum = \count($functions); @@ -303,6 +304,7 @@ $server->job() $execution = new Document($payload['execution'] ?? []); $user = new Document($payload['user'] ?? []); $execute( + log: $log, project: $project, function: $function, dbForProject: $dbForProject, @@ -319,6 +321,7 @@ $server->job() break; case 'schedule': $execute( + log: $log, project: $project, function: $function, dbForProject: $dbForProject, diff --git a/bin/calc-tier-stats b/bin/calc-tier-stats new file mode 100644 index 000000000..c7fb71e6f --- /dev/null +++ b/bin/calc-tier-stats @@ -0,0 +1,3 @@ +#!/bin/sh + +php /usr/src/code/app/cli.php calc-tier-stats $@ \ No newline at end of file diff --git a/bin/calc-users-stats b/bin/calc-users-stats new file mode 100644 index 000000000..60472ed7a --- /dev/null +++ b/bin/calc-users-stats @@ -0,0 +1,3 @@ +#!/bin/sh + +php /usr/src/code/app/cli.php calc-users-stats $@ \ No newline at end of file diff --git a/bin/clear-card-cache b/bin/clear-card-cache new file mode 100644 index 000000000..b39bc1365 --- /dev/null +++ b/bin/clear-card-cache @@ -0,0 +1,3 @@ +#!/bin/sh + +php /usr/src/code/app/cli.php clear-card-cache $@ \ No newline at end of file diff --git a/bin/patch-delete-project-collections b/bin/patch-delete-project-collections new file mode 100644 index 000000000..8bf0736cf --- /dev/null +++ b/bin/patch-delete-project-collections @@ -0,0 +1,3 @@ +#!/bin/sh + +php /usr/src/code/app/cli.php patch-delete-project-collections $@ \ No newline at end of file diff --git a/composer.json b/composer.json index dc7cab83c..1d84101aa 100644 --- a/composer.json +++ b/composer.json @@ -50,18 +50,18 @@ "utopia-php/cli": "0.15.*", "utopia-php/config": "0.2.*", "utopia-php/database": "0.30.*", - "utopia-php/queue": "0.5.*", + "utopia-php/domains": "1.1.*", + "utopia-php/dsn": "0.1.*", + "utopia-php/framework": "0.26.*", + "utopia-php/image": "0.5.*", + "utopia-php/locale": "0.4.*", + "utopia-php/logger": "0.3.*", + "utopia-php/messaging": "0.1.*", "utopia-php/orchestration": "0.9.*", "utopia-php/platform": "0.3.*", "utopia-php/pools": "0.4.*", "utopia-php/preloader": "0.2.*", - "utopia-php/domains": "1.1.*", - "utopia-php/framework": "0.26.*", - "utopia-php/image": "0.5.*", - "utopia-php/dsn": "0.1.*", - "utopia-php/locale": "0.4.*", - "utopia-php/logger": "0.3.*", - "utopia-php/messaging": "0.1.*", + "utopia-php/queue": "0.5.*", "utopia-php/registry": "0.5.*", "utopia-php/storage": "0.14.*", "utopia-php/swoole": "0.5.*", @@ -73,7 +73,8 @@ "chillerlan/php-qrcode": "4.3.3", "adhocore/jwt": "1.1.2", "webonyx/graphql-php": "14.11.*", - "slickdeals/statsd": "3.1.0" + "slickdeals/statsd": "3.1.0", + "league/csv": "9.7.1" }, "repositories": [ { diff --git a/composer.lock b/composer.lock index db08a9345..2b699470c 100644 --- a/composer.lock +++ b/composer.lock @@ -300,16 +300,16 @@ }, { "name": "colinmollenhour/credis", - "version": "v1.14.0", + "version": "v1.15.0", "source": { "type": "git", "url": "https://github.com/colinmollenhour/credis.git", - "reference": "dccc8a46586475075fbb012d8bd523b8a938c2dc" + "reference": "28810439de1d9597b7ba11794ed9479fb6f3de7c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/colinmollenhour/credis/zipball/dccc8a46586475075fbb012d8bd523b8a938c2dc", - "reference": "dccc8a46586475075fbb012d8bd523b8a938c2dc", + "url": "https://api.github.com/repos/colinmollenhour/credis/zipball/28810439de1d9597b7ba11794ed9479fb6f3de7c", + "reference": "28810439de1d9597b7ba11794ed9479fb6f3de7c", "shasum": "" }, "require": { @@ -341,9 +341,9 @@ "homepage": "https://github.com/colinmollenhour/credis", "support": { "issues": "https://github.com/colinmollenhour/credis/issues", - "source": "https://github.com/colinmollenhour/credis/tree/v1.14.0" + "source": "https://github.com/colinmollenhour/credis/tree/v1.15.0" }, - "time": "2022-11-09T01:18:39+00:00" + "time": "2023-04-18T15:34:23+00:00" }, { "name": "composer/package-versions-deprecated", @@ -600,6 +600,90 @@ }, "time": "2022-11-29T16:25:20+00:00" }, + { + "name": "league/csv", + "version": "9.7.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/csv.git", + "reference": "0ec57e8264ec92565974ead0d1724cf1026e10c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/0ec57e8264ec92565974ead0d1724cf1026e10c1", + "reference": "0ec57e8264ec92565974ead0d1724cf1026e10c1", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "php": "^7.3 || ^8.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "friendsofphp/php-cs-fixer": "^2.16", + "phpstan/phpstan": "^0.12.0", + "phpstan/phpstan-phpunit": "^0.12.0", + "phpstan/phpstan-strict-rules": "^0.12.0", + "phpunit/phpunit": "^9.5" + }, + "suggest": { + "ext-dom": "Required to use the XMLConverter and or the HTMLConverter classes", + "ext-iconv": "Needed to ease transcoding CSV using iconv stream filters" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "League\\Csv\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://github.com/nyamsprod/", + "role": "Developer" + } + ], + "description": "CSV data manipulation made easy in PHP", + "homepage": "http://csv.thephpleague.com", + "keywords": [ + "convert", + "csv", + "export", + "filter", + "import", + "read", + "transform", + "write" + ], + "support": { + "docs": "https://csv.thephpleague.com", + "issues": "https://github.com/thephpleague/csv/issues", + "rss": "https://github.com/thephpleague/csv/releases.atom", + "source": "https://github.com/thephpleague/csv" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2021-04-17T16:32:08+00:00" + }, { "name": "matomo/device-detector", "version": "6.0.0", @@ -1588,16 +1672,16 @@ }, { "name": "utopia-php/framework", - "version": "0.26.0", + "version": "0.26.3", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "e8da5576370366d3bf9c574ec855f8c96fe4f34e" + "reference": "7a18a2b0d6c8dba878c926b73650fd2c4eeaa70d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/e8da5576370366d3bf9c574ec855f8c96fe4f34e", - "reference": "e8da5576370366d3bf9c574ec855f8c96fe4f34e", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/7a18a2b0d6c8dba878c926b73650fd2c4eeaa70d", + "reference": "7a18a2b0d6c8dba878c926b73650fd2c4eeaa70d", "shasum": "" }, "require": { @@ -1626,9 +1710,9 @@ ], "support": { "issues": "https://github.com/utopia-php/framework/issues", - "source": "https://github.com/utopia-php/framework/tree/0.26.0" + "source": "https://github.com/utopia-php/framework/tree/0.26.3" }, - "time": "2023-01-13T08:14:43+00:00" + "time": "2023-06-03T14:01:15+00:00" }, { "name": "utopia-php/image", @@ -2099,16 +2183,16 @@ }, { "name": "utopia-php/queue", - "version": "0.5.2", + "version": "0.5.3", "source": { "type": "git", "url": "https://github.com/utopia-php/queue.git", - "reference": "310271c5cd477541208d7fa74a4dea64df8e04a0" + "reference": "8e8b6cb27172713fe5d8b7b092ce68516caf129a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/queue/zipball/310271c5cd477541208d7fa74a4dea64df8e04a0", - "reference": "310271c5cd477541208d7fa74a4dea64df8e04a0", + "url": "https://api.github.com/repos/utopia-php/queue/zipball/8e8b6cb27172713fe5d8b7b092ce68516caf129a", + "reference": "8e8b6cb27172713fe5d8b7b092ce68516caf129a", "shasum": "" }, "require": { @@ -2154,9 +2238,9 @@ ], "support": { "issues": "https://github.com/utopia-php/queue/issues", - "source": "https://github.com/utopia-php/queue/tree/0.5.2" + "source": "https://github.com/utopia-php/queue/tree/0.5.3" }, - "time": "2023-03-07T08:54:10+00:00" + "time": "2023-05-24T19:06:04+00:00" }, { "name": "utopia-php/registry", @@ -2608,25 +2692,29 @@ }, { "name": "doctrine/deprecations", - "version": "v1.0.0", + "version": "v1.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" + "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", - "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", + "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", "shasum": "" }, "require": { - "php": "^7.1|^8.0" + "php": "^7.1 || ^8.0" }, "require-dev": { "doctrine/coding-standard": "^9", - "phpunit/phpunit": "^7.5|^8.5|^9.5", - "psr/log": "^1|^2|^3" + "phpstan/phpstan": "1.4.10 || 1.10.15", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "0.18.4", + "psr/log": "^1 || ^2 || ^3", + "vimeo/psalm": "4.30.0 || 5.12.0" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" @@ -2645,9 +2733,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" + "source": "https://github.com/doctrine/deprecations/tree/v1.1.1" }, - "time": "2022-05-02T15:47:09+00:00" + "time": "2023-06-03T09:27:29+00:00" }, { "name": "doctrine/instantiator", @@ -2721,16 +2809,16 @@ }, { "name": "matthiasmullie/minify", - "version": "1.3.70", + "version": "1.3.71", "source": { "type": "git", "url": "https://github.com/matthiasmullie/minify.git", - "reference": "2807d9f9bece6877577ad44acb5c801bb3ae536b" + "reference": "ae42a47d7fecc1fbb7277b2f2d84c37a33edc3b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/2807d9f9bece6877577ad44acb5c801bb3ae536b", - "reference": "2807d9f9bece6877577ad44acb5c801bb3ae536b", + "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/ae42a47d7fecc1fbb7277b2f2d84c37a33edc3b1", + "reference": "ae42a47d7fecc1fbb7277b2f2d84c37a33edc3b1", "shasum": "" }, "require": { @@ -2780,7 +2868,7 @@ ], "support": { "issues": "https://github.com/matthiasmullie/minify/issues", - "source": "https://github.com/matthiasmullie/minify/tree/1.3.70" + "source": "https://github.com/matthiasmullie/minify/tree/1.3.71" }, "funding": [ { @@ -2788,7 +2876,7 @@ "type": "github" } ], - "time": "2022-12-09T12:56:44+00:00" + "time": "2023-04-25T20:33:03+00:00" }, { "name": "matthiasmullie/path-converter", @@ -2904,16 +2992,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.15.4", + "version": "v4.15.5", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" + "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", - "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/11e2663a5bc9db5d714eedb4277ee300403b4a9e", + "reference": "11e2663a5bc9db5d714eedb4277ee300403b4a9e", "shasum": "" }, "require": { @@ -2954,9 +3042,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.5" }, - "time": "2023-03-05T19:49:14+00:00" + "time": "2023-05-19T20:20:00+00:00" }, { "name": "phar-io/manifest", @@ -3307,22 +3395,24 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.16.1", + "version": "1.22.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "e27e92d939e2e3636f0a1f0afaba59692c0bf571" + "reference": "ec58baf7b3c7f1c81b3b00617c953249fb8cf30c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/e27e92d939e2e3636f0a1f0afaba59692c0bf571", - "reference": "e27e92d939e2e3636f0a1f0afaba59692c0bf571", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/ec58baf7b3c7f1c81b3b00617c953249fb8cf30c", + "reference": "ec58baf7b3c7f1c81b3b00617c953249fb8cf30c", "shasum": "" }, "require": { "php": "^7.2 || ^8.0" }, "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^1.5", @@ -3346,9 +3436,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.16.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.22.0" }, - "time": "2023-02-07T18:11:17+00:00" + "time": "2023-06-01T12:35:21+00:00" }, { "name": "phpunit/php-code-coverage", @@ -4071,16 +4161,16 @@ }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", "shasum": "" }, "require": { @@ -4125,7 +4215,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" }, "funding": [ { @@ -4133,7 +4223,7 @@ "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2023-05-07T05:35:17+00:00" }, { "name": "sebastian/environment", @@ -5100,16 +5190,16 @@ }, { "name": "twig/twig", - "version": "v3.5.1", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "a6e0510cc793912b451fd40ab983a1d28f611c15" + "reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/a6e0510cc793912b451fd40ab983a1d28f611c15", - "reference": "a6e0510cc793912b451fd40ab983a1d28f611c15", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd", + "reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd", "shasum": "" }, "require": { @@ -5118,15 +5208,10 @@ "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { - "psr/container": "^1.0", + "psr/container": "^1.0|^2.0", "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.5-dev" - } - }, "autoload": { "psr-4": { "Twig\\": "src/" @@ -5160,7 +5245,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.5.1" + "source": "https://github.com/twigphp/Twig/tree/v3.6.1" }, "funding": [ { @@ -5172,7 +5257,7 @@ "type": "tidelift" } ], - "time": "2023-02-08T07:49:20+00:00" + "time": "2023-06-08T12:52:13+00:00" } ], "aliases": [], diff --git a/docker-compose.yml b/docker-compose.yml index 625243c1f..059e1ac8c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -134,6 +134,7 @@ services: - _APP_SMTP_SECURE - _APP_SMTP_USERNAME - _APP_SMTP_PASSWORD + - _APP_USERS_STATS_RECIPIENTS - _APP_USAGE_STATS - _APP_STORAGE_LIMIT - _APP_STORAGE_PREVIEW_LIMIT @@ -163,6 +164,8 @@ services: - _APP_GRAPHQL_MAX_BATCH_SIZE - _APP_GRAPHQL_MAX_COMPLEXITY - _APP_GRAPHQL_MAX_DEPTH + - _APP_CONSOLE_GITHUB_APP_ID + - _APP_CONSOLE_GITHUB_SECRET appwrite-realtime: entrypoint: realtime @@ -478,6 +481,8 @@ services: - _APP_USAGE_STATS - _APP_DOCKER_HUB_USERNAME - _APP_DOCKER_HUB_PASSWORD + - _APP_LOGGING_CONFIG + - _APP_LOGGING_PROVIDER appwrite-worker-mails: entrypoint: worker-mails diff --git a/phpunit.xml b/phpunit.xml index 082025d1f..5b4bcdb99 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" - > +> diff --git a/public/fonts/Inter-Bold.ttf b/public/fonts/Inter-Bold.ttf new file mode 100644 index 000000000..8e82c70d1 Binary files /dev/null and b/public/fonts/Inter-Bold.ttf differ diff --git a/public/fonts/Inter-Medium.ttf b/public/fonts/Inter-Medium.ttf new file mode 100644 index 000000000..9a3396fc4 Binary files /dev/null and b/public/fonts/Inter-Medium.ttf differ diff --git a/public/fonts/Inter-Regular.ttf b/public/fonts/Inter-Regular.ttf new file mode 100644 index 000000000..8d4eebf20 Binary files /dev/null and b/public/fonts/Inter-Regular.ttf differ diff --git a/public/fonts/Inter-SemiBold.ttf b/public/fonts/Inter-SemiBold.ttf new file mode 100644 index 000000000..c6aeeb16a Binary files /dev/null and b/public/fonts/Inter-SemiBold.ttf differ diff --git a/public/fonts/Poppins-Bold.ttf b/public/fonts/Poppins-Bold.ttf new file mode 100644 index 000000000..00559eeb2 Binary files /dev/null and b/public/fonts/Poppins-Bold.ttf differ diff --git a/public/fonts/SourceCodePro-Regular.ttf b/public/fonts/SourceCodePro-Regular.ttf new file mode 100644 index 000000000..c37a7b78d Binary files /dev/null and b/public/fonts/SourceCodePro-Regular.ttf differ diff --git a/public/images/cards/cloud/back-golden.png b/public/images/cards/cloud/back-golden.png new file mode 100644 index 000000000..68f1114c4 Binary files /dev/null and b/public/images/cards/cloud/back-golden.png differ diff --git a/public/images/cards/cloud/back-platinum.png b/public/images/cards/cloud/back-platinum.png new file mode 100644 index 000000000..cd56d227a Binary files /dev/null and b/public/images/cards/cloud/back-platinum.png differ diff --git a/public/images/cards/cloud/back.png b/public/images/cards/cloud/back.png new file mode 100644 index 000000000..bb8e5b0b2 Binary files /dev/null and b/public/images/cards/cloud/back.png differ diff --git a/public/images/cards/cloud/contributor-skew.png b/public/images/cards/cloud/contributor-skew.png new file mode 100644 index 000000000..c80b40990 Binary files /dev/null and b/public/images/cards/cloud/contributor-skew.png differ diff --git a/public/images/cards/cloud/contributor.png b/public/images/cards/cloud/contributor.png new file mode 100644 index 000000000..203f0f18e Binary files /dev/null and b/public/images/cards/cloud/contributor.png differ diff --git a/public/images/cards/cloud/employee-skew.png b/public/images/cards/cloud/employee-skew.png new file mode 100644 index 000000000..487b50c1d Binary files /dev/null and b/public/images/cards/cloud/employee-skew.png differ diff --git a/public/images/cards/cloud/employee.png b/public/images/cards/cloud/employee.png new file mode 100644 index 000000000..587c1feb8 Binary files /dev/null and b/public/images/cards/cloud/employee.png differ diff --git a/public/images/cards/cloud/front-golden.png b/public/images/cards/cloud/front-golden.png new file mode 100644 index 000000000..6f7c251de Binary files /dev/null and b/public/images/cards/cloud/front-golden.png differ diff --git a/public/images/cards/cloud/front-platinum.png b/public/images/cards/cloud/front-platinum.png new file mode 100644 index 000000000..0bea38e9f Binary files /dev/null and b/public/images/cards/cloud/front-platinum.png differ diff --git a/public/images/cards/cloud/front.png b/public/images/cards/cloud/front.png new file mode 100644 index 000000000..19dcad74b Binary files /dev/null and b/public/images/cards/cloud/front.png differ diff --git a/public/images/cards/cloud/github-skew.png b/public/images/cards/cloud/github-skew.png new file mode 100644 index 000000000..8f836f9bd Binary files /dev/null and b/public/images/cards/cloud/github-skew.png differ diff --git a/public/images/cards/cloud/github.png b/public/images/cards/cloud/github.png new file mode 100644 index 000000000..b0ba724d4 Binary files /dev/null and b/public/images/cards/cloud/github.png differ diff --git a/public/images/cards/cloud/hero-skew.png b/public/images/cards/cloud/hero-skew.png new file mode 100644 index 000000000..3928f6635 Binary files /dev/null and b/public/images/cards/cloud/hero-skew.png differ diff --git a/public/images/cards/cloud/hero.png b/public/images/cards/cloud/hero.png new file mode 100644 index 000000000..811df0f8c Binary files /dev/null and b/public/images/cards/cloud/hero.png differ diff --git a/public/images/cards/cloud/og-background-logo.png b/public/images/cards/cloud/og-background-logo.png new file mode 100644 index 000000000..c426c8917 Binary files /dev/null and b/public/images/cards/cloud/og-background-logo.png differ diff --git a/public/images/cards/cloud/og-background1.png b/public/images/cards/cloud/og-background1.png new file mode 100644 index 000000000..cbd7221fe Binary files /dev/null and b/public/images/cards/cloud/og-background1.png differ diff --git a/public/images/cards/cloud/og-background2.png b/public/images/cards/cloud/og-background2.png new file mode 100644 index 000000000..f62d46f8e Binary files /dev/null and b/public/images/cards/cloud/og-background2.png differ diff --git a/public/images/cards/cloud/og-background3.png b/public/images/cards/cloud/og-background3.png new file mode 100644 index 000000000..3746cb620 Binary files /dev/null and b/public/images/cards/cloud/og-background3.png differ diff --git a/public/images/cards/cloud/og-card-golden1.png b/public/images/cards/cloud/og-card-golden1.png new file mode 100644 index 000000000..563d64f98 Binary files /dev/null and b/public/images/cards/cloud/og-card-golden1.png differ diff --git a/public/images/cards/cloud/og-card-golden2.png b/public/images/cards/cloud/og-card-golden2.png new file mode 100644 index 000000000..70331f1fc Binary files /dev/null and b/public/images/cards/cloud/og-card-golden2.png differ diff --git a/public/images/cards/cloud/og-card-golden3.png b/public/images/cards/cloud/og-card-golden3.png new file mode 100644 index 000000000..f03ee1d26 Binary files /dev/null and b/public/images/cards/cloud/og-card-golden3.png differ diff --git a/public/images/cards/cloud/og-card-platinum1.png b/public/images/cards/cloud/og-card-platinum1.png new file mode 100644 index 000000000..736ae50af Binary files /dev/null and b/public/images/cards/cloud/og-card-platinum1.png differ diff --git a/public/images/cards/cloud/og-card-platinum2.png b/public/images/cards/cloud/og-card-platinum2.png new file mode 100644 index 000000000..bfc975c40 Binary files /dev/null and b/public/images/cards/cloud/og-card-platinum2.png differ diff --git a/public/images/cards/cloud/og-card-platinum3.png b/public/images/cards/cloud/og-card-platinum3.png new file mode 100644 index 000000000..4f08ffcd0 Binary files /dev/null and b/public/images/cards/cloud/og-card-platinum3.png differ diff --git a/public/images/cards/cloud/og-card1.png b/public/images/cards/cloud/og-card1.png new file mode 100644 index 000000000..8cf9b7fd1 Binary files /dev/null and b/public/images/cards/cloud/og-card1.png differ diff --git a/public/images/cards/cloud/og-card2.png b/public/images/cards/cloud/og-card2.png new file mode 100644 index 000000000..eb5ad1d80 Binary files /dev/null and b/public/images/cards/cloud/og-card2.png differ diff --git a/public/images/cards/cloud/og-card3.png b/public/images/cards/cloud/og-card3.png new file mode 100644 index 000000000..1766b24d9 Binary files /dev/null and b/public/images/cards/cloud/og-card3.png differ diff --git a/public/images/cards/cloud/og-shadow-golden.png b/public/images/cards/cloud/og-shadow-golden.png new file mode 100644 index 000000000..1421d03a2 Binary files /dev/null and b/public/images/cards/cloud/og-shadow-golden.png differ diff --git a/public/images/cards/cloud/og-shadow-platinum.png b/public/images/cards/cloud/og-shadow-platinum.png new file mode 100644 index 000000000..29d8404dc Binary files /dev/null and b/public/images/cards/cloud/og-shadow-platinum.png differ diff --git a/public/images/cards/cloud/og-shadow.png b/public/images/cards/cloud/og-shadow.png new file mode 100644 index 000000000..ccb005c58 Binary files /dev/null and b/public/images/cards/cloud/og-shadow.png differ diff --git a/src/Appwrite/Auth/OAuth2/Github.php b/src/Appwrite/Auth/OAuth2/Github.php index c582617d6..8b9208fc0 100644 --- a/src/Appwrite/Auth/OAuth2/Github.php +++ b/src/Appwrite/Auth/OAuth2/Github.php @@ -158,6 +158,18 @@ class Github extends OAuth2 return $user['name'] ?? ''; } + /** + * @param string $accessToken + * + * @return string + */ + public function getUserSlug(string $accessToken): string + { + $user = $this->getUser($accessToken); + + return $user['login'] ?? ''; + } + /** * @param string $accessToken * @@ -171,13 +183,27 @@ class Github extends OAuth2 $emails = $this->request('GET', 'https://api.github.com/user/emails', ['Authorization: token ' . \urlencode($accessToken)]); $emails = \json_decode($emails, true); + + $verifiedEmail = null; + $primaryEmail = null; + foreach ($emails as $email) { if (isset($email['verified']) && $email['verified'] === true) { - $this->user['email'] = $email['email']; - $this->user['verified'] = $email['verified']; - break; + $verifiedEmail = $email; + + if (isset($email['primary']) && $email['primary'] === true) { + $primaryEmail = $email; + } } } + + if (!empty($primaryEmail)) { + $this->user['email'] = $primaryEmail['email']; + $this->user['verified'] = $primaryEmail['verified']; + } elseif (!empty($verifiedEmail)) { + $this->user['email'] = $verifiedEmail['email']; + $this->user['verified'] = $verifiedEmail['verified']; + } } return $this->user; diff --git a/src/Appwrite/Event/Validator/Event.php b/src/Appwrite/Event/Validator/Event.php index 4bf501f2d..3f2290048 100644 --- a/src/Appwrite/Event/Validator/Event.php +++ b/src/Appwrite/Event/Validator/Event.php @@ -7,6 +7,11 @@ use Utopia\Validator; class Event extends Validator { + /** + * @var string + */ + protected string $message = 'Event is not valid.'; + /** * Get Description. * @@ -16,7 +21,7 @@ class Event extends Validator */ public function getDescription(): string { - return 'Event is not valid.'; + return $this->message; } /** @@ -40,6 +45,12 @@ class Event extends Validator * Identify all sections of the pattern. */ $type = $parts[0] ?? false; + + if ($type == 'functions') { + $this->message = 'Triggering a function on a function event is not allowed.'; + return false; + } + $resource = $parts[1] ?? false; $hasSubResource = $count > 3 && ($events[$type]['$resource'] ?? false) && ($events[$type][$parts[2]]['$resource'] ?? false); $hasSubSubResource = $count > 5 && $hasSubResource && ($events[$type][$parts[2]][$parts[4]]['$resource'] ?? false); diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index d6014fbb1..a66c7a170 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -157,6 +157,7 @@ class Exception extends \Exception public const PROJECT_UNKNOWN = 'project_unknown'; public const PROJECT_PROVIDER_DISABLED = 'project_provider_disabled'; public const PROJECT_PROVIDER_UNSUPPORTED = 'project_provider_unsupported'; + public const PROJECT_ALREADY_EXISTS = 'project_already_exists'; public const PROJECT_INVALID_SUCCESS_URL = 'project_invalid_success_url'; public const PROJECT_INVALID_FAILURE_URL = 'project_invalid_failure_url'; public const PROJECT_RESERVED_PROJECT = 'project_reserved_project'; @@ -178,6 +179,7 @@ class Exception extends \Exception /** Domain */ public const DOMAIN_NOT_FOUND = 'domain_not_found'; public const DOMAIN_ALREADY_EXISTS = 'domain_already_exists'; + public const DOMAIN_FORBIDDEN = 'domain_forbidden'; public const DOMAIN_VERIFICATION_FAILED = 'domain_verification_failed'; public const DOMAIN_TARGET_INVALID = 'domain_target_invalid'; diff --git a/src/Appwrite/Platform/Services/Tasks.php b/src/Appwrite/Platform/Services/Tasks.php index 927abc63a..9e919127b 100644 --- a/src/Appwrite/Platform/Services/Tasks.php +++ b/src/Appwrite/Platform/Services/Tasks.php @@ -14,10 +14,14 @@ use Appwrite\Platform\Tasks\Specs; use Appwrite\Platform\Tasks\SSL; use Appwrite\Platform\Tasks\Hamster; use Appwrite\Platform\Tasks\PatchDeleteScheduleUpdatedAtAttribute; +use Appwrite\Platform\Tasks\ClearCardCache; use Appwrite\Platform\Tasks\Usage; use Appwrite\Platform\Tasks\Vars; use Appwrite\Platform\Tasks\Version; use Appwrite\Platform\Tasks\VolumeSync; +use Appwrite\Platform\Tasks\CalcUsersStats; +use Appwrite\Platform\Tasks\CalcTierStats; +use Appwrite\Platform\Tasks\PatchDeleteProjectCollections; class Tasks extends Service { @@ -33,11 +37,16 @@ class Tasks extends Service ->addAction(Install::getName(), new Install()) ->addAction(Maintenance::getName(), new Maintenance()) ->addAction(PatchCreateMissingSchedules::getName(), new PatchCreateMissingSchedules()) + ->addAction(ClearCardCache::getName(), new ClearCardCache()) ->addAction(PatchDeleteScheduleUpdatedAtAttribute::getName(), new PatchDeleteScheduleUpdatedAtAttribute()) ->addAction(Schedule::getName(), new Schedule()) ->addAction(Migrate::getName(), new Migrate()) ->addAction(SDKs::getName(), new SDKs()) ->addAction(VolumeSync::getName(), new VolumeSync()) - ->addAction(Specs::getName(), new Specs()); + ->addAction(Specs::getName(), new Specs()) + ->addAction(CalcUsersStats::getName(), new CalcUsersStats()) + ->addAction(CalcTierStats::getName(), new CalcTierStats()) + ->addAction(PatchDeleteProjectCollections::getName(), new PatchDeleteProjectCollections()) + ; } } diff --git a/src/Appwrite/Platform/Tasks/CalcTierStats.php b/src/Appwrite/Platform/Tasks/CalcTierStats.php new file mode 100644 index 000000000..d66070994 --- /dev/null +++ b/src/Appwrite/Platform/Tasks/CalcTierStats.php @@ -0,0 +1,351 @@ + '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}"); + } + } +} diff --git a/src/Appwrite/Platform/Tasks/CalcUsersStats.php b/src/Appwrite/Platform/Tasks/CalcUsersStats.php new file mode 100644 index 000000000..6310fe17b --- /dev/null +++ b/src/Appwrite/Platform/Tasks/CalcUsersStats.php @@ -0,0 +1,176 @@ +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}"); + } + } +} diff --git a/src/Appwrite/Platform/Tasks/ClearCardCache.php b/src/Appwrite/Platform/Tasks/ClearCardCache.php new file mode 100644 index 000000000..d3153b995 --- /dev/null +++ b/src/Appwrite/Platform/Tasks/ClearCardCache.php @@ -0,0 +1,62 @@ +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'); + } +} diff --git a/src/Appwrite/Platform/Tasks/Hamster.php b/src/Appwrite/Platform/Tasks/Hamster.php index 3e3cdd9d7..5b04c338e 100644 --- a/src/Appwrite/Platform/Tasks/Hamster.php +++ b/src/Appwrite/Platform/Tasks/Hamster.php @@ -2,6 +2,7 @@ namespace Appwrite\Platform\Tasks; +use Appwrite\Network\Validator\Origin; use Exception; use Utopia\App; use Utopia\Platform\Action; @@ -12,6 +13,7 @@ use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Analytics\Adapter\Mixpanel; use Utopia\Analytics\Event; +use Utopia\Config\Config; use Utopia\Database\Document; use Utopia\Pools\Group; @@ -98,6 +100,12 @@ class Hamster extends Action /** Get Total Functions */ $statsPerProject['custom_functions'] = $dbForProject->count('functions', [], APP_LIMIT_COUNT); + foreach (\array_keys(Config::getParam('runtimes')) as $runtime) { + $statsPerProject['custom_functions_' . $runtime] = $dbForProject->count('functions', [ + Query::equal('runtime', [$runtime]), + ], APP_LIMIT_COUNT); + } + /** Get Total Deployments */ $statsPerProject['custom_deployments'] = $dbForProject->count('deployments', [], APP_LIMIT_COUNT); @@ -136,7 +144,10 @@ class Hamster extends Action } /** Get Domains */ - $statsPerProject['custom_domains'] = $dbForProject->count('domains', [], APP_LIMIT_COUNT); + $statsPerProject['custom_domains'] = $dbForConsole->count('domains', [ + Query::equal('projectInternalId', [$project->getInternalId()]), + Query::limit(APP_LIMIT_COUNT) + ]); /** Get Platforms */ $platforms = $dbForConsole->find('platforms', [ @@ -152,7 +163,7 @@ class Hamster extends Action return $platform['type'] === 'android'; })); - $statsPerProject['custom_platforms_iOS'] = sizeof(array_filter($platforms, function ($platform) { + $statsPerProject['custom_platforms_apple'] = sizeof(array_filter($platforms, function ($platform) { return str_contains($platform['type'], 'apple'); })); @@ -160,6 +171,19 @@ class Hamster extends Action return str_contains($platform['type'], 'flutter'); })); + $flutterPlatforms = [Origin::CLIENT_TYPE_FLUTTER_ANDROID, Origin::CLIENT_TYPE_FLUTTER_IOS, Origin::CLIENT_TYPE_FLUTTER_MACOS, Origin::CLIENT_TYPE_FLUTTER_WINDOWS, Origin::CLIENT_TYPE_FLUTTER_LINUX]; + + foreach ($flutterPlatforms as $flutterPlatform) { + $statsPerProject['custom_platforms_' . $flutterPlatform] = sizeof(array_filter($platforms, function ($platform) use ($flutterPlatform) { + return $platform['type'] === $flutterPlatform; + })); + } + + $statsPerProject['custom_platforms_api_keys'] = $dbForConsole->count('keys', [ + Query::equal('projectInternalId', [$project->getInternalId()]), + Query::limit(APP_LIMIT_COUNT) + ]); + /** Get Usage $statsPerProject */ $periods = [ 'infinity' => [ diff --git a/src/Appwrite/Platform/Tasks/PatchDeleteProjectCollections.php b/src/Appwrite/Platform/Tasks/PatchDeleteProjectCollections.php new file mode 100644 index 000000000..a909e6859 --- /dev/null +++ b/src/Appwrite/Platform/Tasks/PatchDeleteProjectCollections.php @@ -0,0 +1,129 @@ +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(); + } +} diff --git a/src/Appwrite/Resque/Worker.php b/src/Appwrite/Resque/Worker.php index 182d78a5b..146500e74 100644 --- a/src/Appwrite/Resque/Worker.php +++ b/src/Appwrite/Resque/Worker.php @@ -174,6 +174,7 @@ abstract class Worker * @throws Exception */ protected static $databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools + protected function getProjectDB(Document $project): Database { global $register; @@ -218,6 +219,14 @@ abstract class Worker $pools = $register->get('pools'); /** @var Group $pools */ + $databaseName = 'console'; + + if (isset(self::$databases[$databaseName])) { + $database = self::$databases[$databaseName]; + $database->setNamespace('console'); + return $database; + } + $dbAdapter = $pools ->get('console') ->pop() @@ -226,6 +235,8 @@ abstract class Worker $database = new Database($dbAdapter, $this->getCache()); + self::$databases[$databaseName] = $database; + $database->setNamespace('console'); return $database; diff --git a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php index 89c82c8e8..154b77690 100644 --- a/src/Appwrite/Utopia/Database/Validator/Queries/Base.php +++ b/src/Appwrite/Utopia/Database/Validator/Queries/Base.php @@ -22,7 +22,9 @@ class Base extends Queries */ public function __construct(string $collection, array $allowedAttributes) { - $collection = Config::getParam('collections', [])[$collection]; + $config = Config::getParam('collections', []); + $collections = array_merge($config['console'], $config['projects'], $config['buckets'], $config['databases']); + $collection = $collections[$collection]; // array for constant lookup time $allowedAttributesLookup = []; foreach ($allowedAttributes as $attribute) { diff --git a/tests/e2e/Client.php b/tests/e2e/Client.php index e98472b2b..803c970d8 100644 --- a/tests/e2e/Client.php +++ b/tests/e2e/Client.php @@ -160,17 +160,14 @@ class Client * @param array $params * @param array $headers * @param bool $decode - * @return array|string + * @return array * @throws Exception */ - public function call(string $method, string $path = '', array $headers = [], array $params = [], bool $decode = true) + public function call(string $method, string $path = '', array $headers = [], array $params = [], bool $decode = true): array { $headers = array_merge($this->headers, $headers); $ch = curl_init($this->endpoint . $path . (($method == self::METHOD_GET && !empty($params)) ? '?' . http_build_query($params) : '')); $responseHeaders = []; - $responseStatus = -1; - $responseType = ''; - $responseBody = ''; switch ($headers['content-type']) { case 'application/json': @@ -220,7 +217,7 @@ class Client curl_setopt($ch, CURLOPT_POSTFIELDS, $query); } - // Allow self signed certificates + // Allow self-signed certificates if ($this->selfSigned) { curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); @@ -230,22 +227,18 @@ class Client $responseType = $responseHeaders['content-type'] ?? ''; $responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); - if ($decode) { - switch (substr($responseType, 0, strpos($responseType, ';'))) { - case 'application/json': - $json = json_decode($responseBody, true); + if ($decode && substr($responseType, 0, strpos($responseType, ';')) == 'application/json') { + $json = json_decode($responseBody, true); - if ($json === null) { - throw new Exception('Failed to parse response: ' . $responseBody); - } - - $responseBody = $json; - $json = null; - break; + if ($json === null) { + throw new Exception('Failed to parse response: ' . $responseBody); } + + $responseBody = $json; + $json = null; } - if ((curl_errno($ch)/* || 200 != $responseStatus*/)) { + if ((curl_errno($ch))) { throw new Exception(curl_error($ch) . ' with status code ' . $responseStatus, $responseStatus); } @@ -273,7 +266,7 @@ class Client { $cookies = []; - parse_str(strtr($cookie, array('&' => '%26', '+' => '%2B', ';' => '&')), $cookies); + parse_str(strtr($cookie, ['&' => '%26', '+' => '%2B', ';' => '&']), $cookies); return $cookies; } diff --git a/tests/e2e/General/HTTPTest.php b/tests/e2e/General/HTTPTest.php index 09f744f27..9b2ac0735 100644 --- a/tests/e2e/General/HTTPTest.php +++ b/tests/e2e/General/HTTPTest.php @@ -12,6 +12,12 @@ class HTTPTest extends Scope use ProjectNone; use SideNone; + public function setUp(): void + { + parent::setUp(); + $this->client->setEndpoint('http://localhost'); + } + public function testOptions() { /** @@ -32,24 +38,6 @@ class HTTPTest extends Scope $this->assertEmpty($response['body']); } - public function testError() - { - /** - * Test for SUCCESS - */ - $this->markTestIncomplete('This test needs to be updated for the new console.'); - // $response = $this->client->call(Client::METHOD_GET, '/error', \array_merge([ - // 'origin' => 'http://localhost', - // 'content-type' => 'application/json', - // ]), []); - - // $this->assertEquals(404, $response['headers']['status-code']); - // $this->assertEquals('Not Found', $response['body']['message']); - // $this->assertEquals(Exception::GENERAL_ROUTE_NOT_FOUND, $response['body']['type']); - // $this->assertEquals(404, $response['body']['code']); - // $this->assertEquals('dev', $response['body']['version']); - } - public function testHumans() { /** @@ -57,7 +45,7 @@ class HTTPTest extends Scope */ $response = $this->client->call(Client::METHOD_GET, '/humans.txt', \array_merge([ 'origin' => 'http://localhost', - ]), []); + ])); $this->assertEquals(200, $response['headers']['status-code']); $this->assertStringContainsString('# humanstxt.org/', $response['body']); @@ -70,7 +58,7 @@ class HTTPTest extends Scope */ $response = $this->client->call(Client::METHOD_GET, '/robots.txt', \array_merge([ 'origin' => 'http://localhost', - ]), []); + ])); $this->assertEquals(200, $response['headers']['status-code']); $this->assertStringContainsString('# robotstxt.org/', $response['body']); @@ -87,7 +75,7 @@ class HTTPTest extends Scope */ $response = $this->client->call(Client::METHOD_GET, '/.well-known/acme-challenge/8DdIKX257k6Dih5s_saeVMpTnjPJdKO5Ase0OCiJrIg', \array_merge([ 'origin' => 'http://localhost', - ]), []); + ])); $this->assertEquals(404, $response['headers']['status-code']); // 'Unknown path', but validation passed @@ -97,7 +85,7 @@ class HTTPTest extends Scope */ $response = $this->client->call(Client::METHOD_GET, '/.well-known/acme-challenge/../../../../../../../etc/passwd', \array_merge([ 'origin' => 'http://localhost', - ]), []); + ])); $this->assertEquals(400, $response['headers']['status-code']); @@ -171,4 +159,15 @@ class HTTPTest extends Scope $this->assertIsString($body['server-ruby']); $this->assertIsString($body['console-cli']); } + + public function testDefaultOAuth2() + { + $response = $this->client->call(Client::METHOD_GET, '/auth/oauth2/success', $this->getHeaders()); + + $this->assertEquals(200, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, '/auth/oauth2/failure', $this->getHeaders()); + + $this->assertEquals(200, $response['headers']['status-code']); + } } diff --git a/tests/e2e/Scopes/ProjectCustom.php b/tests/e2e/Scopes/ProjectCustom.php index 4d780d04c..dfdfcfe2a 100644 --- a/tests/e2e/Scopes/ProjectCustom.php +++ b/tests/e2e/Scopes/ProjectCustom.php @@ -104,7 +104,7 @@ trait ProjectCustom 'name' => 'Webhook Test', 'events' => [ 'databases.*', - 'functions.*', + // 'functions.*', TODO @christyjacob4 : enable test once we allow functions.* events 'buckets.*', 'teams.*', 'users.*' diff --git a/tests/e2e/Scopes/Scope.php b/tests/e2e/Scopes/Scope.php index 42057e8e9..14eb83897 100644 --- a/tests/e2e/Scopes/Scope.php +++ b/tests/e2e/Scopes/Scope.php @@ -17,10 +17,7 @@ abstract class Scope extends TestCase protected function setUp(): void { $this->client = new Client(); - - $this->client - ->setEndpoint($this->endpoint) - ; + $this->client->setEndpoint($this->endpoint); } protected function tearDown(): void @@ -45,10 +42,10 @@ abstract class Scope extends TestCase { sleep(2); - $resquest = json_decode(file_get_contents('http://request-catcher:5000/__last_request__'), true); - $resquest['data'] = json_decode($resquest['data'], true); + $request = json_decode(file_get_contents('http://request-catcher:5000/__last_request__'), true); + $request['data'] = json_decode($request['data'], true); - return $resquest; + return $request; } /** diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index bdaf3a199..35329e9cb 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -252,6 +252,8 @@ class AccountCustomClientTest extends Scope ]); $this->assertEquals($response['headers']['status-code'], 200); + $this->assertStringContainsString('a_session_' . $this->getProject()['$id'] . '=deleted', $response['headers']['set-cookie']); + $this->assertEquals('[]', $response['headers']['x-fallback-cookies']); $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([ 'origin' => 'http://localhost', diff --git a/tests/e2e/Services/Databases/DatabasesBase.php b/tests/e2e/Services/Databases/DatabasesBase.php index e81a89ac9..19bbb25dc 100644 --- a/tests/e2e/Services/Databases/DatabasesBase.php +++ b/tests/e2e/Services/Databases/DatabasesBase.php @@ -981,6 +981,12 @@ trait DatabasesBase $this->assertEquals(400, $document4['headers']['status-code']); + // Delete document 4 with incomplete path + $this->assertEquals(404, $this->client->call(Client::METHOD_DELETE, '/databases/' . $databaseId . '/collections/' . $data['moviesId'] . '/documents/', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()))['headers']['status-code']); + return $data; } @@ -2869,7 +2875,7 @@ trait DatabasesBase $databaseId = $database['body']['$id']; // Create collection - $movies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/', array_merge([ + $movies = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], 'x-appwrite-key' => $this->getProject()['apiKey'] diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index aba0c6e5b..3baf92a87 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -3,6 +3,7 @@ namespace Tests\E2E\Services\Projects; use Appwrite\Auth\Auth; +use Appwrite\Extend\Exception; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\ProjectConsole; use Tests\E2E\Scopes\SideClient; @@ -96,7 +97,37 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(400, $response['headers']['status-code']); - return ['projectId' => $projectId]; + return [ + 'projectId' => $projectId, + 'teamId' => $team['body']['$id'] + ]; + } + + /** + * @depends testCreateProject + */ + public function testCreateDuplicateProject($data) + { + $teamId = $data['teamId'] ?? ''; + $projectId = $data['projectId'] ?? ''; + + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'projectId' => $projectId, + 'name' => 'Project Duplicate', + 'teamId' => $teamId, + 'region' => 'default' + ]); + + $this->assertEquals(409, $response['headers']['status-code']); + $this->assertEquals(409, $response['body']['code']); + $this->assertEquals(Exception::PROJECT_ALREADY_EXISTS, $response['body']['type']); + $this->assertEquals('Project with the requested ID already exists.', $response['body']['message']); } /** @@ -781,11 +812,11 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals($response['headers']['status-code'], 501); - $response = $this->client->call(Client::METHOD_POST, '/account/anonymous', array_merge([ + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/anonymous', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', 'x-appwrite-project' => $id, - ]), []); + ])); $this->assertEquals($response['headers']['status-code'], 501); @@ -841,6 +872,19 @@ class ProjectsConsoleClientTest extends Scope 'name' => $name, ]); + $email = uniqid() . 'user@localhost.test'; + + $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $id, + ]), [ + 'userId' => ID::unique(), + 'email' => $email, + 'password' => $password, + 'name' => $name, + ]); + $this->assertEquals($response['headers']['status-code'], 501); /** @@ -856,6 +900,8 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertNotEmpty($response['body']['$id']); + $email = uniqid() . 'user@localhost.test'; + $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php b/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php index cd2fb6f13..66ea935bc 100644 --- a/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php +++ b/tests/e2e/Services/Realtime/RealtimeConsoleClientTest.php @@ -497,7 +497,7 @@ class RealtimeConsoleClientTest extends Scope $this->assertArrayHasKey('timestamp', $response['data']); $this->assertCount(1, $response['data']['channels']); $this->assertContains('console', $response['data']['channels']); - $this->assertContains("functions.{$functionId}.deployments.{$deploymentId}.create", $response['data']['events']); + // $this->assertContains("functions.{$functionId}.deployments.{$deploymentId}.create", $response['data']['events']); TODO @christyjacob4 : enable test once we allow functions.* events $this->assertNotEmpty($response['data']['payload']); $client->close(); diff --git a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php index c20cb58f4..26a97f97b 100644 --- a/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php +++ b/tests/e2e/Services/Webhooks/WebhooksCustomServerTest.php @@ -415,10 +415,10 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('functions.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -474,10 +474,10 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('functions.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -529,12 +529,12 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -572,16 +572,16 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('functions.*.deployments.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.*.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.deployments.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*.deployments.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.*.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.deployments.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -620,16 +620,16 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('functions.*.executions.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.*.executions.{$executionId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.executions.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.executions.{$executionId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*.executions.*.create', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.*.executions.{$executionId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.executions.*.create", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.executions.{$executionId}.create", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -643,16 +643,16 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('functions.*.executions.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.*.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.executions.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*.executions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*.executions.*.update', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.*.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.*.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.executions.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.executions.*.update", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.executions.{$executionId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.executions.{$executionId}.update", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -688,16 +688,16 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('functions.*.deployments.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.*.deployments.{$deploymentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.deployments.*.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*.deployments.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*.deployments.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.*.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.*.deployments.{$deploymentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.deployments.*", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.deployments.*.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.deployments.{$deploymentId}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); @@ -733,10 +733,10 @@ class WebhooksCustomServerTest extends Scope $this->assertEquals($webhook['method'], 'POST'); $this->assertEquals($webhook['headers']['Content-Type'], 'application/json'); $this->assertEquals($webhook['headers']['User-Agent'], 'Appwrite-Server vdev. Please report abuse at security@appwrite.io'); - $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString('functions.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); - $this->assertStringContainsString("functions.{$id}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString('functions.*.delete', $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}", $webhook['headers']['X-Appwrite-Webhook-Events']); + // $this->assertStringContainsString("functions.{$id}.delete", $webhook['headers']['X-Appwrite-Webhook-Events']); TODO @christyjacob4 : enable test once we allow functions.* events $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Signature'], $signatureExpected); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Id'] ?? '', $this->getProject()['webhookId']); $this->assertEquals($webhook['headers']['X-Appwrite-Webhook-Project-Id'] ?? '', $this->getProject()['$id']); diff --git a/tests/unit/Event/Validator/EventValidatorTest.php b/tests/unit/Event/Validator/EventValidatorTest.php index e9f652ade..cf62ecca6 100644 --- a/tests/unit/Event/Validator/EventValidatorTest.php +++ b/tests/unit/Event/Validator/EventValidatorTest.php @@ -50,7 +50,7 @@ class EventValidatorTest extends TestCase $this->assertTrue($this->object->isValid('databases.books')); $this->assertTrue($this->object->isValid('databases.books.collections.chapters')); $this->assertTrue($this->object->isValid('databases.books.collections.*')); - $this->assertTrue($this->object->isValid('functions.*')); + // $this->assertTrue($this->object->isValid('functions.*')); TODO @christyjacob4 : enable test once we allow functions.* events $this->assertTrue($this->object->isValid('buckets.*')); $this->assertTrue($this->object->isValid('teams.*')); $this->assertTrue($this->object->isValid('users.*')); diff --git a/tests/unit/General/CollectionsTest.php b/tests/unit/General/CollectionsTest.php index 1d648f93c..73a9ccd0c 100644 --- a/tests/unit/General/CollectionsTest.php +++ b/tests/unit/General/CollectionsTest.php @@ -15,16 +15,18 @@ class CollectionsTest extends TestCase public function testDuplicateRules(): void { - foreach ($this->collections as $key => $collection) { - if (array_key_exists('attributes', $collection)) { - foreach ($collection['attributes'] as $check) { - $occurrences = 0; - foreach ($collection['attributes'] as $attribute) { - if ($attribute['$id'] == $check['$id']) { - $occurrences++; + foreach ($this->collections as $key => $sections) { + foreach ($sections as $key => $collection) { + if (array_key_exists('attributes', $collection)) { + foreach ($collection['attributes'] as $check) { + $occurrences = 0; + foreach ($collection['attributes'] as $attribute) { + if ($attribute['$id'] == $check['$id']) { + $occurrences++; + } } + $this->assertEquals(1, $occurrences); } - $this->assertEquals(1, $occurrences); } } }