From 4f67db160bf4d991e820741e8d3183b596fc80f1 Mon Sep 17 00:00:00 2001 From: achraf112ben Date: Sun, 28 Jan 2024 06:14:21 +0100 Subject: [PATCH 01/43] Adding Darija (Moroccan Arabic) translation file --- app/config/locale/translations/ar-ma.json | 238 ++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 app/config/locale/translations/ar-ma.json diff --git a/app/config/locale/translations/ar-ma.json b/app/config/locale/translations/ar-ma.json new file mode 100644 index 0000000000..18fcaa1ee0 --- /dev/null +++ b/app/config/locale/translations/ar-ma.json @@ -0,0 +1,238 @@ +{ + "settings.inspire": "\"الفن ديال الحكمة هو الفن ديال أنك تعرف أش تنخّل.\"", + "settings.locale": "ar-ma", + "settings.direction": "rtl", + "emails.sender": "فرقة %s", + "emails.verification.subject": "التيْقان ديال الحساب", + "emails.verification.hello": "السلام {{user}}", + "emails.verification.body": "تبّع هاد الوصلة باش تيقّن لادريسة تاع ليميل ديالك.", + "emails.verification.footer": "إلا ماشي نتا اللي طلبتي تيقّن هاد لادريسة تاع ليميل، ممكن تنخّل هاد البرية.", + "emails.verification.thanks": "شكرا", + "emails.verification.signature": "فرقة {{project}}", + "emails.magicSession.subject": "تكونيكطا", + "emails.magicSession.hello": "السلام,", + "emails.magicSession.body": "تبّع هاد الوصلة باش تتكونيكطا.", + "emails.magicSession.footer": "إلا ماشي نتا اللي طلبتي تتكونيكطا بهاد ليميل، ممكن تنخّل هاد البرية.", + "emails.magicSession.thanks": "شكرا", + "emails.magicSession.signature": "فرقة {{project}}", + "emails.recovery.subject": "تبدال كلمة السر", + "emails.recovery.hello": "السلام {{user}}", + "emails.recovery.body": "تبّع هاد الوصلة باش تبدّل كلمة السر تاع {{project}}.", + "emails.recovery.footer": "إلا ماشي نتا اللي طلبتي تبدّل كلمة السر، ممكن تنخّل هاد البرية.", + "emails.recovery.thanks": "شكرا", + "emails.recovery.signature": "فرقة {{project}}", + "emails.invitation.subject": "عراضة ل فرقة %s ف %s", + "emails.invitation.hello": "السلام", + "emails.invitation.body": "هاد البرية تصيفطات ليك حيت {{owner}} بغى يعرض عليك تولّي عضو ف فرقة {{team}} عند {{project}}.", + "emails.invitation.footer": "إلا كنتي ما مسوّقش, ممكن تنخّل هاد البرية.", + "emails.invitation.thanks": "شكرا", + "emails.invitation.signature": "فرقة {{project}}", + "emails.certificate.subject": "Certificate failure for %s", + "emails.certificate.hello": "Hello", + "emails.certificate.body": "السرتافيكة ديال ضومينك '{{domain}}' ما قدّاتش تجينيرا. هادي هي المحاولة نمرة {{attempt}}, السبب ديال هاد العطبة هو: {{error}}", + "emails.certificate.footer": "السرتافيكة الفايتة ديالك غاتبقى مزيانة لمدة 30 يوم من عند أول عطبة. كانشجعوك بزاف أنك تبقشش فهاد الموضوع, وا إلّا الضومين ديالك ما غايبقاش خدّام فيه الـ SSL.", + "emails.certificate.thanks": "شكرا", + "emails.certificate.signature": "فرقة {{project}}", + "locale.country.unknown": "ما معروفش", + "countries.af": "أفغانستان", + "countries.ao": "أنڭولا", + "countries.al": "ألبانيا", + "countries.ad": "أندورا", + "countries.ae": "الإمارات العربية المتّاحدة", + "countries.ar": "الأرجنتين", + "countries.am": "أرمينيا", + "countries.ag": "أنتيڭوا وبربودا", + "countries.au": "ؤسطراليا", + "countries.at": "النامسا", + "countries.az": "أديربيجان", + "countries.bi": "بوروندي", + "countries.be": "بلجيكا", + "countries.bj": "بينين", + "countries.bf": "بوركينا فاصو", + "countries.bd": "بنڭلاديش", + "countries.bg": "بلڭاريا", + "countries.bh": "البحرين", + "countries.bs": "دزيرات البهاما", + "countries.ba": "البوسنة ؤ الهرسك", + "countries.by": "بيلاروسيا", + "countries.bz": "بيليز", + "countries.bo": "بوليڤيا", + "countries.br": "البرازيل", + "countries.bb": "باربادوس", + "countries.bn": "بروناي", + "countries.bt": "بوتان", + "countries.bw": "بوتسوانا", + "countries.cf": "جمهورية إفريقيا الوسطانية", + "countries.ca": "كانادا", + "countries.ch": "سويسرا", + "countries.cl": "تشيلي", + "countries.cn": "الشينوا", + "countries.ci": "ساحل العاج", + "countries.cm": "الكاميرون", + "countries.cd": "جمهورية الكونڭو الديمقراطية", + "countries.cg": "جمهورية الكونڭو", + "countries.co": "كولومبيا", + "countries.km": "دزيرات القومور", + "countries.cv": "الراس الخضر", + "countries.cr": "كوسطاريكا", + "countries.cu": "كوبا", + "countries.cy": "قوبروص", + "countries.cz": "التشيك", + "countries.de": "ألمانيا", + "countries.dj": "دجيبوتي", + "countries.dm": "ضومينيكا", + "countries.dk": "الدنمارك", + "countries.do": "جمهورية الضومينيكان", + "countries.dz": "الدزاير", + "countries.ec": "إكوادور", + "countries.eg": "مصر", + "countries.er": "إريتريا", + "countries.es": "سبانيا", + "countries.ee": "إسطونيا", + "countries.et": "إتيوپيا", + "countries.fi": "فينلاندا", + "countries.fj": "فيدجي", + "countries.fr": "فرانسا", + "countries.fm": "ميكرونيزيا", + "countries.ga": "الڭابون", + "countries.gb": "المملكة المتّاحدة", + "countries.ge": "تجورجيا", + "countries.gh": "غانا", + "countries.gn": "غينيا", + "countries.gm": "ڭامبيا", + "countries.gw": "غينيا بيساو", + "countries.gq": "غينيا الستوائية", + "countries.gr": "اليونان", + "countries.gd": "ڭرينادا", + "countries.gt": "ڭواتيمالا", + "countries.gy": "ڭيانا", + "countries.hn": "هوندوراس", + "countries.hr": "كرواتيا", + "countries.ht": "هايتي", + "countries.hu": "الماجر", + "countries.id": "إندونيسيا", + "countries.in": "الهند", + "countries.ie": "إرلاندا", + "countries.ir": "إران", + "countries.iq": "العراق", + "countries.is": "إسلاندا", + "countries.il": "إسرائيل", + "countries.it": "الطاليان", + "countries.jm": "جامايكا", + "countries.jo": "الأردن", + "countries.jp": "الجاپون", + "countries.kz": "كازاخستان", + "countries.ke": "كينيا", + "countries.kg": "قيرغيزستان", + "countries.kh": "كمبوديا", + "countries.ki": "كيريباتي", + "countries.kn": "سانت كيتس ؤ نيفيس", + "countries.kr": "كوريا الجنوبية", + "countries.kw": "الكويت", + "countries.la": "لاوس", + "countries.lb": "لبنان", + "countries.lr": "ليبيريا", + "countries.ly": "ليبيا", + "countries.lc": "سانت لوسيا", + "countries.li": "ليختنشتاين", + "countries.lk": "سري لانكا", + "countries.ls": "ليسوتو", + "countries.lt": "ليتوانيا", + "countries.lu": "لوكسمبورڭ", + "countries.lv": "لاتفيا", + "countries.ma": "المغريب", + "countries.mc": "موناكو", + "countries.md": "مولضوڤا", + "countries.mg": "ماداغشقار", + "countries.mv": "دزيرات المالديڤ", + "countries.mx": "الميكسيك", + "countries.mh": "دزيرات مارشال", + "countries.mk": "مقدونيا", + "countries.ml": "مالي", + "countries.mt": "مالطا", + "countries.mm": "ميانمار", + "countries.me": "مونطينيڭرو", + "countries.mn": "منغوليا", + "countries.mz": "الموزمبيق", + "countries.mr": "موريتانيا", + "countries.mu": "موريشيوس", + "countries.mw": "مالاوي", + "countries.my": "ماليزيا", + "countries.na": "ناميبيا", + "countries.ne": "النيجر", + "countries.ng": "نيجيريا", + "countries.ni": "نيكاراڭوا", + "countries.nl": "هولاندا", + "countries.no": "النرويج", + "countries.np": "نيپال", + "countries.nr": "ناورو", + "countries.nz": "نيوزيلاندا", + "countries.om": "عمّان", + "countries.pk": "پاكيستان", + "countries.pa": "پاناما", + "countries.pe": "الپيرو", + "countries.ph": "الفيليپين", + "countries.pw": "پالاو", + "countries.pg": "پاپوا غينيا الجديدة", + "countries.pl": "پولاندا", + "countries.kp": "كوريا الشمالية", + "countries.pt": "البرطقيز", + "countries.py": "الپاراڭواي", + "countries.qa": "قطر", + "countries.ro": "رومانيا", + "countries.ru": "روسيا", + "countries.rw": "روّاندا", + "countries.sa": "المملكة العربية السعودية", + "countries.sd": "السودان", + "countries.sn": "السينيڭال", + "countries.sg": "سنغافورة", + "countries.sb": "دزيرات سليمان", + "countries.sl": "صييراليون", + "countries.sv": "السالڤاضور", + "countries.sm": "سان مارينو", + "countries.so": "الصومال", + "countries.rs": "صيربيا", + "countries.ss": "جنوب السودان", + "countries.st": "صاو طومي ؤ پرينسيپي", + "countries.sr": "سورينام", + "countries.sk": "صلوڤاكيا", + "countries.si": "صلوڤينيا", + "countries.se": "السويد", + "countries.sz": "سوازيلاند", + "countries.sc": "السيشيل", + "countries.sy": "سوريا", + "countries.td": "تشاد", + "countries.tg": "الطوڭو", + "countries.th": "الطايلوند", + "countries.tj": "طادجيكيستان", + "countries.tm": "تركمانيستان", + "countries.tl": "تيمور الشرقية", + "countries.to": "تونڭا", + "countries.tt": "ترينيداد ؤ طوباڭو", + "countries.tn": "تونس", + "countries.tr": "توركيا", + "countries.tv": "توڤالو", + "countries.tz": "طنزانيا", + "countries.ug": "ؤڭاندا", + "countries.ua": "ؤكرانيا", + "countries.uy": "ؤروڭواي", + "countries.us": "ميريكان", + "countries.uz": "ؤزباكيستان", + "countries.va": "مدينة الڤاتيكان", + "countries.vc": "سانت ڤانسون ؤ دزيرات ڭرينادين", + "countries.ve": "ڤينيزويلا", + "countries.vn": "ڤيطنام", + "countries.vu": "ڤانواتو", + "countries.ws": "ساموا", + "countries.ye": "اليمن", + "countries.za": "جنوب إفريقيا", + "countries.zm": "زامبيا", + "countries.zw": "زيمبابوي", + "continents.af": "أفريقيا", + "continents.an": "القارة القطبية الجنوبية", + "continents.as": "أسيا", + "continents.eu": "ؤروپا", + "continents.na": "ميريكان الشمالية", + "continents.oc": "ؤقيانوسيا", + "continents.sa": "ميريكان الجنوبية" +} From 3a35412bf8905abaf5a8912305f4820c1e05ec63 Mon Sep 17 00:00:00 2001 From: achraf112ben Date: Mon, 29 Jan 2024 04:21:14 +0100 Subject: [PATCH 02/43] fix some missed values --- app/config/locale/translations/ar-ma.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/config/locale/translations/ar-ma.json b/app/config/locale/translations/ar-ma.json index 18fcaa1ee0..453de25c80 100644 --- a/app/config/locale/translations/ar-ma.json +++ b/app/config/locale/translations/ar-ma.json @@ -27,10 +27,10 @@ "emails.invitation.footer": "إلا كنتي ما مسوّقش, ممكن تنخّل هاد البرية.", "emails.invitation.thanks": "شكرا", "emails.invitation.signature": "فرقة {{project}}", - "emails.certificate.subject": "Certificate failure for %s", - "emails.certificate.hello": "Hello", - "emails.certificate.body": "السرتافيكة ديال ضومينك '{{domain}}' ما قدّاتش تجينيرا. هادي هي المحاولة نمرة {{attempt}}, السبب ديال هاد العطبة هو: {{error}}", - "emails.certificate.footer": "السرتافيكة الفايتة ديالك غاتبقى مزيانة لمدة 30 يوم من عند أول عطبة. كانشجعوك بزاف أنك تبقشش فهاد الموضوع, وا إلّا الضومين ديالك ما غايبقاش خدّام فيه الـ SSL.", + "emails.certificate.subject": "السرتافيكة فشلات ل %s", + "emails.certificate.hello": "السلام", + "emails.certificate.body": "السرتافيكة ديال الضومين ديالك '{{domain}}' ما قدّاتش تجينيرا. هادي هي المحاولة نمرة {{attempt}}, السبب ديال هاد الفشل هو: {{error}}", + "emails.certificate.footer": "السرتافيكة الفايتة ديالك غاتبقى مزيانة لمدة 30 يوم من عند أول فشل. كانشجعوك بزاف أنك تبقشش فهاد الموضوع, وا إلّا الضومين ديالك ما غايبقاش خدّام فيه الـ SSL.", "emails.certificate.thanks": "شكرا", "emails.certificate.signature": "فرقة {{project}}", "locale.country.unknown": "ما معروفش", From a9cee074be22d7123a722568bc9d78175f5843ae Mon Sep 17 00:00:00 2001 From: ItzNotABug Date: Wed, 5 Jun 2024 13:45:41 +0530 Subject: [PATCH 03/43] update: add api doesn't support redirections. --- docs/references/avatars/get-favicon.md | 2 ++ docs/references/avatars/get-image.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/references/avatars/get-favicon.md b/docs/references/avatars/get-favicon.md index b571e0af93..c3a63dd83f 100644 --- a/docs/references/avatars/get-favicon.md +++ b/docs/references/avatars/get-favicon.md @@ -1 +1,3 @@ Use this endpoint to fetch the favorite icon (AKA favicon) of any remote website URL. + +This endpoint does not follow HTTP redirects. \ No newline at end of file diff --git a/docs/references/avatars/get-image.md b/docs/references/avatars/get-image.md index efd654b362..ca9cc5e418 100644 --- a/docs/references/avatars/get-image.md +++ b/docs/references/avatars/get-image.md @@ -1,3 +1,5 @@ Use this endpoint to fetch a remote image URL and crop it to any image size you want. This endpoint is very useful if you need to crop and display remote images in your app or in case you want to make sure a 3rd party image is properly served using a TLS protocol. When one dimension is specified and the other is 0, the image is scaled with preserved aspect ratio. If both dimensions are 0, the API provides an image at source quality. If dimensions are not specified, the default size of image returned is 400x400px. + +This endpoint does not follow HTTP redirects. \ No newline at end of file From e6686bd3dd0983c2087fa971579c7c6deb691251 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Mon, 15 Jul 2024 12:40:11 +0530 Subject: [PATCH 04/43] Add APPWRITE_REGION in env var --- app/controllers/api/functions.php | 3 ++- app/controllers/general.php | 3 ++- src/Appwrite/Platform/Workers/Builds.php | 3 ++- src/Appwrite/Platform/Workers/Functions.php | 3 ++- tests/e2e/Services/Functions/FunctionsCustomClientTest.php | 2 ++ tests/resources/functions/php-fn/index.php | 1 + tests/resources/functions/php-large/index.php | 1 + tests/resources/functions/php/index.php | 1 + 8 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index d2a1790d94..bc8e91b966 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -1825,7 +1825,8 @@ App::post('/v1/functions/:functionId/executions') 'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(), 'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '', 'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '', - 'APPWRITE_VERSION' => APP_VERSION_STABLE + 'APPWRITE_VERSION' => APP_VERSION_STABLE, + 'APPWRITE_REGION' => $project->getAttribute('region'), ]); /** Execute function */ diff --git a/app/controllers/general.php b/app/controllers/general.php index 10c9eb8e18..fc572a6fd7 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -250,7 +250,8 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo 'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(), 'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '', 'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '', - 'APPWRITE_VERSION' => APP_VERSION_STABLE + 'APPWRITE_VERSION' => APP_VERSION_STABLE, + 'APPWRITE_REGION' => $project->getAttribute('region'), ]); /** Execute function */ diff --git a/src/Appwrite/Platform/Workers/Builds.php b/src/Appwrite/Platform/Workers/Builds.php index 922498c3fa..b712edd402 100644 --- a/src/Appwrite/Platform/Workers/Builds.php +++ b/src/Appwrite/Platform/Workers/Builds.php @@ -400,7 +400,8 @@ class Builds extends Action 'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(), 'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '', 'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '', - 'APPWRITE_VERSION' => APP_VERSION_STABLE + 'APPWRITE_VERSION' => APP_VERSION_STABLE, + 'APPWRITE_REGION' => $project->getAttribute('region'), ]); $command = $deployment->getAttribute('commands', ''); diff --git a/src/Appwrite/Platform/Workers/Functions.php b/src/Appwrite/Platform/Workers/Functions.php index 5dfdc0a63a..19c690577c 100644 --- a/src/Appwrite/Platform/Workers/Functions.php +++ b/src/Appwrite/Platform/Workers/Functions.php @@ -464,7 +464,8 @@ class Functions extends Action 'APPWRITE_FUNCTION_PROJECT_ID' => $project->getId(), 'APPWRITE_FUNCTION_RUNTIME_NAME' => $runtime['name'] ?? '', 'APPWRITE_FUNCTION_RUNTIME_VERSION' => $runtime['version'] ?? '', - 'APPWRITE_VERSION' => APP_VERSION_STABLE + 'APPWRITE_VERSION' => APP_VERSION_STABLE, + 'APPWRITE_REGION' => $project->getAttribute('region'), ]); /** Execute function */ diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index bf969d388a..18bdba5af4 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -449,6 +449,7 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals('PHP', $output['APPWRITE_FUNCTION_RUNTIME_NAME']); $this->assertEquals('8.0', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']); $this->assertEquals(APP_VERSION_STABLE, $output['APPWRITE_VERSION']); + $this->assertEquals('default', $output['APPWRITE_REGION']); $this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT']); $this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']); $this->assertEquals($this->getUser()['$id'], $output['APPWRITE_FUNCTION_USER_ID']); @@ -834,6 +835,7 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals('PHP', $output['APPWRITE_FUNCTION_RUNTIME_NAME']); $this->assertEquals('8.0', $output['APPWRITE_FUNCTION_RUNTIME_VERSION']); $this->assertEquals(APP_VERSION_STABLE, $output['APPWRITE_VERSION']); + $this->assertEquals('default', $output['APPWRITE_REGION']); $this->assertEquals('', $output['APPWRITE_FUNCTION_EVENT']); $this->assertEquals('foobar', $output['APPWRITE_FUNCTION_DATA']); $this->assertEquals($this->getUser()['$id'], $output['APPWRITE_FUNCTION_USER_ID']); diff --git a/tests/resources/functions/php-fn/index.php b/tests/resources/functions/php-fn/index.php index b353ad2ca4..0b6e3d206c 100644 --- a/tests/resources/functions/php-fn/index.php +++ b/tests/resources/functions/php-fn/index.php @@ -11,6 +11,7 @@ return function ($context) { 'APPWRITE_FUNCTION_RUNTIME_NAME' => \getenv('APPWRITE_FUNCTION_RUNTIME_NAME') ?: '', 'APPWRITE_FUNCTION_RUNTIME_VERSION' => \getenv('APPWRITE_FUNCTION_RUNTIME_VERSION') ?: '', 'APPWRITE_VERSION' => \getenv('APPWRITE_VERSION') ?: '', + 'APPWRITE_REGION' => \getenv('APPWRITE_REGION') ?: '', 'APPWRITE_FUNCTION_EVENT' => $context->req->headers['x-appwrite-event'] ?? '', 'APPWRITE_FUNCTION_EVENT_DATA' => $context->req->bodyRaw ?? '', 'APPWRITE_FUNCTION_DATA' => $context->req->bodyRaw ?? '', diff --git a/tests/resources/functions/php-large/index.php b/tests/resources/functions/php-large/index.php index 5a9666488e..ab3d050245 100644 --- a/tests/resources/functions/php-large/index.php +++ b/tests/resources/functions/php-large/index.php @@ -8,6 +8,7 @@ return function ($context) { 'APPWRITE_FUNCTION_TRIGGER' => $context->req->headers['x-appwrite-trigger'] ?? '', 'APPWRITE_FUNCTION_RUNTIME_NAME' => \getenv('APPWRITE_FUNCTION_RUNTIME_NAME') ?: '', 'APPWRITE_FUNCTION_RUNTIME_VERSION' => \getenv('APPWRITE_FUNCTION_RUNTIME_VERSION') ?: '', + 'APPWRITE_REGION' => \getenv('APPWRITE_REGION') ?: '', 'UNICODE_TEST' => "êä" ]); }; diff --git a/tests/resources/functions/php/index.php b/tests/resources/functions/php/index.php index ac7b85a43a..d5328c40e1 100644 --- a/tests/resources/functions/php/index.php +++ b/tests/resources/functions/php/index.php @@ -8,6 +8,7 @@ return function ($context) { 'APPWRITE_FUNCTION_TRIGGER' => $context->req->headers['x-appwrite-trigger'] ?? '', 'APPWRITE_FUNCTION_RUNTIME_NAME' => \getenv('APPWRITE_FUNCTION_RUNTIME_NAME') ?: '', 'APPWRITE_FUNCTION_RUNTIME_VERSION' => \getenv('APPWRITE_FUNCTION_RUNTIME_VERSION') ?: '', + 'APPWRITE_REGION' => \getenv('APPWRITE_REGION') ?: '', 'UNICODE_TEST' => "êä", 'GLOBAL_VARIABLE' => \getenv('GLOBAL_VARIABLE') ?: '' ]); From eacd965ae3a51ad510793b6ee884595d2852bd39 Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 15 Jul 2024 14:13:34 -0400 Subject: [PATCH 05/43] feat: Adding profiler for debugging --- CONTRIBUTING.md | 12 ++++++++++++ Dockerfile | 3 ++- dev/xdebug.ini | 6 ++++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9e3e6fcd81..8d8ddadda6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -497,6 +497,18 @@ If you are in PHP Storm you don't need any plugin. Below are the settings requir 2. If needed edit the **dev/xdebug.ini** file to your needs. 3. Launch your Appwrite instance while your debugger is listening for connections. +## Profiling +Appwrite uses XDebug [Profiler](https://xdebug.org/docs/profiler) for generating **CacheGrind** files. The generated file would be located in each of the `appwrite` containers inside the `/tmp/xdebug` folder. + +To disable the profiler while debugging remove the `,profiler` mode from the `xdebug.ini` file +```diff +zend_extension=xdebug + +[xdebug] +-xdebug.mode=develop,debug,profile ++xdebug.mode=develop,debug +``` + ### VS Code Launch Configuration ```json diff --git a/Dockerfile b/Dockerfile index 1d82930c1d..7cc0403b84 100755 --- a/Dockerfile +++ b/Dockerfile @@ -108,9 +108,10 @@ RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/ # Enable Extensions RUN if [ "$DEBUG" == "true" ]; then cp /usr/src/code/dev/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini; fi +RUN if [ "$DEBUG" == "true" ]; then mkdir -p /tmp/xdebug; fi RUN if [ "$DEBUG" = "false" ]; then rm -rf /usr/src/code/dev; fi RUN if [ "$DEBUG" = "false" ]; then rm -f /usr/local/lib/php/extensions/no-debug-non-zts-20220829/xdebug.so; fi EXPOSE 80 -CMD [ "php", "app/http.php" ] \ No newline at end of file +CMD [ "php", "app/http.php" ] diff --git a/dev/xdebug.ini b/dev/xdebug.ini index e29c8bd46e..f9c535019f 100644 --- a/dev/xdebug.ini +++ b/dev/xdebug.ini @@ -1,6 +1,8 @@ zend_extension=xdebug [xdebug] -xdebug.mode=develop,debug +xdebug.mode=develop,debug,profile xdebug.client_host=host.docker.internal -xdebug.start_with_request=yes \ No newline at end of file +xdebug.start_with_request=yes +xdebug.output_dir=/tmp/xdebug +xdebug.use_compression=false From 5d6b74e447a71b2d23ee594baac1a60194616685 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 17 Jul 2024 14:52:17 +0900 Subject: [PATCH 06/43] Add migrations and filters for 1.6.x --- app/controllers/general.php | 9 +- src/Appwrite/Migration/Migration.php | 1 + src/Appwrite/Migration/Version/V21.php | 143 ++++++++++++++++++ src/Appwrite/Utopia/Request/Filters/V18.php | 21 +++ src/Appwrite/Utopia/Response/Filters/V18.php | 36 +++++ tests/unit/Utopia/Request/Filters/V18Test.php | 51 +++++++ .../unit/Utopia/Response/Filters/V18Test.php | 84 ++++++++++ 7 files changed, 344 insertions(+), 1 deletion(-) create mode 100644 src/Appwrite/Migration/Version/V21.php create mode 100644 src/Appwrite/Utopia/Request/Filters/V18.php create mode 100644 src/Appwrite/Utopia/Response/Filters/V18.php create mode 100644 tests/unit/Utopia/Request/Filters/V18Test.php create mode 100644 tests/unit/Utopia/Response/Filters/V18Test.php diff --git a/app/controllers/general.php b/app/controllers/general.php index 10c9eb8e18..72143cbeca 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -11,9 +11,10 @@ use Appwrite\Network\Validator\Origin; use Appwrite\Utopia\Request; use Appwrite\Utopia\Request\Filters\V16 as RequestV16; use Appwrite\Utopia\Request\Filters\V17 as RequestV17; +use Appwrite\Utopia\Request\Filters\V18 as RequestV18; use Appwrite\Utopia\Response; use Appwrite\Utopia\Response\Filters\V16 as ResponseV16; -use Appwrite\Utopia\Response\Filters\V17 as ResponseV17; +use Appwrite\Utopia\Response\Filters\V18 as ResponseV18; use Appwrite\Utopia\View; use Executor\Executor; use MaxMind\Db\Reader; @@ -434,6 +435,9 @@ App::init() if (version_compare($requestFormat, '1.5.0', '<')) { $request->addFilter(new RequestV17()); } + if (version_compare($requestFormat, '1.6.0', '<')) { + $request->addFilter(new RequestV18()); + } } $domain = $request->getHostname(); @@ -550,6 +554,9 @@ App::init() if (version_compare($responseFormat, '1.5.0', '<')) { $response->addFilter(new ResponseV17()); } + if (version_compare($responseFormat, '1.6.0', '<')) { + $response->addFilter(new ResponseV18()); + } if (version_compare($responseFormat, APP_VERSION_STABLE, '>')) { $response->addHeader('X-Appwrite-Warning', "The current SDK is built for Appwrite " . $responseFormat . ". However, the current Appwrite server version is ". APP_VERSION_STABLE . ". Please downgrade your SDK to match the Appwrite version: https://appwrite.io/docs/sdks"); } diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index e3a2021c1a..716f6c6381 100644 --- a/src/Appwrite/Migration/Migration.php +++ b/src/Appwrite/Migration/Migration.php @@ -86,6 +86,7 @@ abstract class Migration '1.5.5' => 'V20', '1.5.6' => 'V20', '1.5.7' => 'V20', + '1.6.0' => 'V12' ]; /** diff --git a/src/Appwrite/Migration/Version/V21.php b/src/Appwrite/Migration/Version/V21.php new file mode 100644 index 0000000000..7dc9e4374b --- /dev/null +++ b/src/Appwrite/Migration/Version/V21.php @@ -0,0 +1,143 @@ + null, + fn () => [] + ); + } + + Console::log('Migrating Project: ' . $this->project->getAttribute('name') . ' (' . $this->project->getId() . ')'); + $this->projectDB->setNamespace("_{$this->project->getInternalId()}"); + + Console::info('Migrating Collections'); + $this->migrateCollections(); + + Console::info('Migrating Documents'); + $this->forEachDocument([$this, 'fixDocument']); + } + + /** + * Migrate Collections. + * + * @return void + * @throws Exception|Throwable + */ + private function migrateCollections(): void + { + $internalProjectId = $this->project->getInternalId(); + $collectionType = match ($internalProjectId) { + 'console' => 'console', + default => 'projects', + }; + + $collections = $this->collections[$collectionType]; + foreach ($collections as $collection) { + $id = $collection['$id']; + + Console::log("Migrating Collection \"{$id}\""); + + $this->projectDB->setNamespace("_$internalProjectId"); + + switch ($id) { + case 'projects': + // Create accessedAt attribute + try { + $this->createAttributeFromCollection($this->projectDB, $id, 'accessedAt'); + } catch (Throwable $th) { + Console::warning("'accessedAt' from {$id}: {$th->getMessage()}"); + } + break; + case 'schedules': + // Create data attribute + try { + $this->createAttributeFromCollection($this->projectDB, $id, 'data'); + } catch (Throwable $th) { + Console::warning("'data' from {$id}: {$th->getMessage()}"); + } + + break; + + case 'functions': + // Create scopes attribute + try { + $this->createAttributeFromCollection($this->projectDB, $id, 'scopes'); + } catch (Throwable $th) { + Console::warning("'scopes' from {$id}: {$th->getMessage()}"); + } + + break; + case 'executions': + // Create requestMethod index + try { + $this->createIndexFromCollection($this->projectDB, $id, '_key_requestMethod'); + } catch (\Throwable $th) { + Console::warning("'_key_requestMethod' from {$id}: {$th->getMessage()}"); + } + + // Create requestPath index + try { + $this->createIndexFromCollection($this->projectDB, $id, '_key_requestPath'); + } catch (\Throwable $th) { + Console::warning("'_key_requestPath' from {$id}: {$th->getMessage()}"); + } + + // Create deployment index + try { + $this->createIndexFromCollection($this->projectDB, $id, '_key_deployment'); + } catch (\Throwable $th) { + Console::warning("'_key_deployment' from {$id}: {$th->getMessage()}"); + } + } + + usleep(50000); + } + } + + /** + * Fix run on each document + * + * @param Document $document + * @return Document + */ + protected function fixDocument(Document $document): Document + { + switch ($document->getCollection()) { + case 'projects': + /** + * Bump version number. + */ + $document->setAttribute('version', '1.6.0'); + break; + case 'functions': + /** + * Add scopes attribute. + */ + if (!$document->getAttribute('scopes', false)) { + $document->setAttribute('scopes', []); + } + break; + } + return $document; + } +} diff --git a/src/Appwrite/Utopia/Request/Filters/V18.php b/src/Appwrite/Utopia/Request/Filters/V18.php new file mode 100644 index 0000000000..fd8b7ffd3a --- /dev/null +++ b/src/Appwrite/Utopia/Request/Filters/V18.php @@ -0,0 +1,21 @@ + $this->parseFunction($content), + Response::MODEL_PROJECT => $this->parseProject($content), + default => $parsedResponse, + }; + + return $parsedResponse; + } + + protected function parseFunction(array $content) + { + unset($content['scopes']); + return $content; + } + + protected function parseProject(array $content) + { + unset($content['authMockNumbers']); + unset($content['authSessionAlerts']); + return $content; + } +} \ No newline at end of file diff --git a/tests/unit/Utopia/Request/Filters/V18Test.php b/tests/unit/Utopia/Request/Filters/V18Test.php new file mode 100644 index 0000000000..4e1f81573a --- /dev/null +++ b/tests/unit/Utopia/Request/Filters/V18Test.php @@ -0,0 +1,51 @@ +filter = new V18(); + } + + public function tearDown(): void + { + } + + public function deleteMfaAuthenticatorProvider() + { + return [ + 'remove otp' => [ + [ + 'type' => 'totp', + 'otp' => 1230 + ], + [ + 'type' => 'totp' + ] + ] + ]; + } + + /** + * @dataProvider deleteMfaAuthenticatorProvider + */ + public function testdeleteMfaAuthenticator(array $content, array $expected): void + { + $model = 'account.deleteMfaAuthenticator'; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } +} diff --git a/tests/unit/Utopia/Response/Filters/V18Test.php b/tests/unit/Utopia/Response/Filters/V18Test.php new file mode 100644 index 0000000000..0b12ca66cd --- /dev/null +++ b/tests/unit/Utopia/Response/Filters/V18Test.php @@ -0,0 +1,84 @@ +filter = new V18(); + } + + public function tearDown(): void + { + } + + public function functionProvider(): array + { + return [ + 'remove scopes' => [ + [ + 'scopes' => [ + 'example_scope', + 'example_scope2', + ], + ], + [ + ] + ] + ]; + } + + /** + * @dataProvider functionProvider + */ + public function testFunction(array $content, array $expected): void + { + $model = Response::MODEL_FUNCTION; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } + + public function projectProvider(): array + { + return [ + 'remove authMockNumbers and authSessionAlerts' => [ + [ + 'authMockNumbers' => [ + 'example_mock_number', + 'example_mock_number2', + ], + 'authSessionAlerts' => [ + 'example_alert', + 'example_alert2', + ], + ], + [ + ] + ] + ]; + } + + /** + * @dataProvider projectProvider + */ + public function testProject(array $content, array $expected): void + { + $model = Response::MODEL_PROJECT; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } +} \ No newline at end of file From ec9bb8a898b7e7a08d1106c33922d6653aa574dd Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 17 Jul 2024 14:53:23 +0900 Subject: [PATCH 07/43] Re-add V17 Response filter --- app/controllers/general.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/general.php b/app/controllers/general.php index 72143cbeca..70acd9d482 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -14,6 +14,7 @@ use Appwrite\Utopia\Request\Filters\V17 as RequestV17; use Appwrite\Utopia\Request\Filters\V18 as RequestV18; use Appwrite\Utopia\Response; use Appwrite\Utopia\Response\Filters\V16 as ResponseV16; +use Appwrite\Utopia\Response\Filters\V17 as ResponseV17; use Appwrite\Utopia\Response\Filters\V18 as ResponseV18; use Appwrite\Utopia\View; use Executor\Executor; From 35375c42ad38a7266f4a45f1f3383c2ee0236197 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 17 Jul 2024 14:54:37 +0900 Subject: [PATCH 08/43] Fix typos --- src/Appwrite/Migration/Migration.php | 2 +- src/Appwrite/Migration/Version/V21.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Migration/Migration.php b/src/Appwrite/Migration/Migration.php index 716f6c6381..9075b85702 100644 --- a/src/Appwrite/Migration/Migration.php +++ b/src/Appwrite/Migration/Migration.php @@ -86,7 +86,7 @@ abstract class Migration '1.5.5' => 'V20', '1.5.6' => 'V20', '1.5.7' => 'V20', - '1.6.0' => 'V12' + '1.6.0' => 'V21' ]; /** diff --git a/src/Appwrite/Migration/Version/V21.php b/src/Appwrite/Migration/Version/V21.php index 7dc9e4374b..10193ec60c 100644 --- a/src/Appwrite/Migration/Version/V21.php +++ b/src/Appwrite/Migration/Version/V21.php @@ -9,7 +9,7 @@ use Utopia\CLI\Console; use Utopia\Database\Database; use Utopia\Database\Document; -class V20 extends Migration +class V21 extends Migration { /** * @throws Throwable From 917de006bff0fc694140ca04fd293470a936099b Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 17 Jul 2024 15:01:44 +0900 Subject: [PATCH 09/43] Increate stable version number --- app/init.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/init.php b/app/init.php index a0e71f041b..ef7742956f 100644 --- a/app/init.php +++ b/app/init.php @@ -118,7 +118,7 @@ const APP_USER_ACCESS = 24 * 60 * 60; // 24 hours const APP_PROJECT_ACCESS = 24 * 60 * 60; // 24 hours const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours const APP_CACHE_BUSTER = 4314; -const APP_VERSION_STABLE = '1.5.7'; +const APP_VERSION_STABLE = '1.6.0'; const APP_DATABASE_ATTRIBUTE_EMAIL = 'email'; const APP_DATABASE_ATTRIBUTE_ENUM = 'enum'; const APP_DATABASE_ATTRIBUTE_IP = 'ip'; From d78c880bf02aadb8ede51bb3ca50bf59f3b7ff69 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 17 Jul 2024 15:20:34 +0900 Subject: [PATCH 10/43] Update V21.php --- src/Appwrite/Migration/Version/V21.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Appwrite/Migration/Version/V21.php b/src/Appwrite/Migration/Version/V21.php index 10193ec60c..4bd27b71bc 100644 --- a/src/Appwrite/Migration/Version/V21.php +++ b/src/Appwrite/Migration/Version/V21.php @@ -7,6 +7,7 @@ use Exception; use Throwable; use Utopia\CLI\Console; use Utopia\Database\Database; +use Utopia\Database\DateTime; use Utopia\Database\Document; class V21 extends Migration @@ -133,7 +134,7 @@ class V21 extends Migration /** * Add scopes attribute. */ - if (!$document->getAttribute('scopes', false)) { + if (empty($document->getAttribute('scopes', []))) { $document->setAttribute('scopes', []); } break; From 7dec5f68bef598b1a178de97d051b8e43bb447f7 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 17 Jul 2024 15:20:42 +0900 Subject: [PATCH 11/43] Update V21.php --- src/Appwrite/Migration/Version/V21.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Appwrite/Migration/Version/V21.php b/src/Appwrite/Migration/Version/V21.php index 4bd27b71bc..53d8101af2 100644 --- a/src/Appwrite/Migration/Version/V21.php +++ b/src/Appwrite/Migration/Version/V21.php @@ -129,6 +129,7 @@ class V21 extends Migration * Bump version number. */ $document->setAttribute('version', '1.6.0'); + $document->setAttribute('accessedAt', DateTime::now()); break; case 'functions': /** From 83c43503957171fa253d5d0d73bd540d18a31b09 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 17 Jul 2024 17:58:25 +0900 Subject: [PATCH 12/43] Make scopes optional on functions --- app/config/collections.php | 4 ++-- src/Appwrite/Migration/Version/V21.php | 8 -------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index d0c3df165e..b4dc1049fb 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -4751,8 +4751,8 @@ $consoleCollections = array_merge([ 'format' => '', 'size' => Database::LENGTH_KEY, 'signed' => true, - 'required' => true, - 'default' => null, + 'required' => false, + 'default' => [], 'array' => true, 'filters' => [], ], diff --git a/src/Appwrite/Migration/Version/V21.php b/src/Appwrite/Migration/Version/V21.php index 53d8101af2..c5f09822cb 100644 --- a/src/Appwrite/Migration/Version/V21.php +++ b/src/Appwrite/Migration/Version/V21.php @@ -131,14 +131,6 @@ class V21 extends Migration $document->setAttribute('version', '1.6.0'); $document->setAttribute('accessedAt', DateTime::now()); break; - case 'functions': - /** - * Add scopes attribute. - */ - if (empty($document->getAttribute('scopes', []))) { - $document->setAttribute('scopes', []); - } - break; } return $document; } From efa1924f26649a13471d5f14ad8e8acc113169f9 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Wed, 17 Jul 2024 18:00:41 +0900 Subject: [PATCH 13/43] Run Linter --- src/Appwrite/Utopia/Request/Filters/V18.php | 2 +- src/Appwrite/Utopia/Response/Filters/V18.php | 2 +- tests/unit/Utopia/Response/Filters/V18Test.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Utopia/Request/Filters/V18.php b/src/Appwrite/Utopia/Request/Filters/V18.php index fd8b7ffd3a..4f889ce66b 100644 --- a/src/Appwrite/Utopia/Request/Filters/V18.php +++ b/src/Appwrite/Utopia/Request/Filters/V18.php @@ -18,4 +18,4 @@ class V18 extends Filter return $content; } -} \ No newline at end of file +} diff --git a/src/Appwrite/Utopia/Response/Filters/V18.php b/src/Appwrite/Utopia/Response/Filters/V18.php index ea82f8335e..b32856bdb4 100644 --- a/src/Appwrite/Utopia/Response/Filters/V18.php +++ b/src/Appwrite/Utopia/Response/Filters/V18.php @@ -33,4 +33,4 @@ class V18 extends Filter unset($content['authSessionAlerts']); return $content; } -} \ No newline at end of file +} diff --git a/tests/unit/Utopia/Response/Filters/V18Test.php b/tests/unit/Utopia/Response/Filters/V18Test.php index 0b12ca66cd..36719a7620 100644 --- a/tests/unit/Utopia/Response/Filters/V18Test.php +++ b/tests/unit/Utopia/Response/Filters/V18Test.php @@ -81,4 +81,4 @@ class V18Test extends TestCase $this->assertEquals($expected, $result); } -} \ No newline at end of file +} From c0f7edc840b3fcdd942a02685b2f4ebc858f4498 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Thu, 18 Jul 2024 23:47:28 +0530 Subject: [PATCH 14/43] Add function templates to config --- app/config/templates.php | 2299 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 2299 insertions(+) create mode 100644 app/config/templates.php diff --git a/app/config/templates.php b/app/config/templates.php new file mode 100644 index 0000000000..5c6e4fbe0f --- /dev/null +++ b/app/config/templates.php @@ -0,0 +1,2299 @@ + [ + 'name' => 'node', + 'versions' => ['21.0', '20.0', '19.0', '18.0', '16.0', '14.5'] + ], + 'PHP' => [ + 'name' => 'php', + 'versions' => ['8.3', '8.2', '8.1', '8.0'] + ], + 'RUBY' => [ + 'name' => 'ruby', + 'versions' => ['3.3', '3.2', '3.1', '3.0'] + ], + 'PYTHON' => [ + 'name' => 'python', + 'versions' => ['3.12', '3.11', '3.10', '3.9', '3.8'] + ], + 'DART' => [ + 'name' => 'dart', + 'versions' => ['3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16', '2.16'] + ], + 'BUN' => [ + 'name' => 'bun', + 'versions' => ['1.0'] + ] +]; + +function getRuntimes($runtime, $commands, $entrypoint, $providerRootDirectory, $versionsDenyList = []) { + return array_map(function($version) use ($runtime, $commands, $entrypoint, $providerRootDirectory) { + return [ + 'name' => $runtime['name'] . '-' . $version, + 'commands' => $commands, + 'entrypoint' => $entrypoint, + 'providerRootDirectory' => $providerRootDirectory + ]; + }, array_filter($runtime['versions'], function($version) use ($versionsDenyList) { + return !in_array($version, $versionsDenyList); + })); +} + +return [ + [ + 'icon' => 'icon-lightning-bolt', + 'id' => 'starter', + 'name' => 'Starter function', + 'tagline' => + 'A simple function to get started. Edit this function to explore endless possibilities with Appwrite Functions.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['Starter'], + 'runtimes' => [ + ...getRuntimes(TEMPLATE_RUNTIMES['NODE'], 'npm install', 'src/main.js', 'node/starter'), + ...getRuntimes( + TEMPLATE_RUNTIMES['PHP'], + 'composer install', + 'src/index.php', + 'php/starter' + ), + ...getRuntimes(TEMPLATE_RUNTIMES['RUBY'], 'bundle install', 'lib/main.rb', 'ruby/starter'), + ...getRuntimes( + TEMPLATE_RUNTIMES['PYTHON'], + 'pip install -r requirements.txt', + 'src/main.py', + 'python/starter' + ), + ...getRuntimes(TEMPLATE_RUNTIMES['DART'], 'dart pub get', 'lib/main.dart', 'dart/starter'), + ...getRuntimes(TEMPLATE_RUNTIMES['BUN'], 'bun install', 'src/main.ts', 'bun/starter') + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_API_KEY', + 'description' => 'The API Key to authenticate against Appwrite\'s Server APIs. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => false, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-upstash', + 'id' => 'query-upstash-vector', + 'name' => 'Query Upstash Vector', + 'tagline' => 'Vector database that stores text embeddings and context retrieval for LLMs', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['Databases'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/query-upstash-vector' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'UPSTASH_URL', + 'description' => 'The endpoint to connect to your Upstash Vector database. Learn more.', + 'value' => '', + 'placeholder' => 'https://resolved-mallard-84564-eu1-vector.upstash.io', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'UPSTASH_TOKEN', + 'description' => 'Authentication token to access your Upstash Vector database. Learn more.', + 'value' => '', + 'placeholder' => + 'oe4wNTbwHVLcDNa6oceZfhBEABsCNYh43ii6Xdq4bKBH7mq7qJkUmc4cs3ABbYyuVKWZTxVQjiNjYgydn2dkhABNes4NAuDpj7qxUAmZYqGJT78', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-redis', + 'id' => 'query-redis-labs', + 'name' => 'Query Redis Labs', + 'tagline' => 'Key-value database with advanced caching capabilities.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['Databases'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/query-redis-labs' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'REDIS_HOST', + 'description' => 'The endpoint to connect to your Redis database. Learn more.', + 'value' => '', + 'placeholder' => 'redis-13258.c35.eu-central-1-1.ec2.redns.redis-cloud.com', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'REDIS_PASSWORD', + 'description' => 'Authentication password to access your Redis database. Learn more.', + 'value' => '', + 'placeholder' => 'efNNehiACfcZiwsTAjcK6xiwPyu6Dpdq', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-neo4j', + 'id' => 'query-neo4j-auradb', + 'name' => 'Query Neo4j AuraDB', + 'tagline' => 'Graph database with focus on relations between data.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['Databases'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/query-neo4j-auradb' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'NEO4J_URI', + 'description' => 'The endpoint to connect to your Neo4j database. Learn more.', + 'value' => '', + 'placeholder' => 'neo4j+s://4tg4mddo.databases.neo4j.io', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'NEO4J_USER', + 'description' => 'Authentication user to access your Neo4j database. Learn more.', + 'value' => '', + 'placeholder' => 'neo4j', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'NEO4J_PASSWORD', + 'description' => 'Authentication password to access your Neo4j database. Learn more.', + 'value' => '', + 'placeholder' => 'mCUc4PbVUQN-_NkTLJLisb6ccnwzQKKhrkF77YMctzx', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-mongodb', + 'id' => 'query-mongo-atlas', + 'name' => 'Query MongoDB Atlas', + 'tagline' => + 'Realtime NoSQL document database with geospecial, graph, search, and vector suport.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['Databases'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/query-mongo-atlas' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'MONGO_URI', + 'description' => 'The endpoint to connect to your Mongo database. Learn more.', + 'value' => '', + 'placeholder' => + 'mongodb+srv://appwrite:Yx42hafg7Q4fgkxe@cluster0.7mslfog.mongodb.net/?retryWrites=true&w=majority&appName=Appwrite', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-neon', + 'id' => 'query-neon-postgres', + 'name' => 'Query Neon Postgres', + 'tagline' => + 'Reliable SQL database with replication, point-in-time recovery, and pgvector support.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['Databases'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/query-neon-postgres' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'PGHOST', + 'description' => 'The endpoint to connect to your Postgres database. Learn more.', + 'value' => '', + 'placeholder' => 'ep-still-sea-a792sh84.eu-central-1.aws.neon.tech', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'PGDATABASE', + 'description' => 'Name of our Postgres database. Learn more.', + 'value' => '', + 'placeholder' => 'main', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'PGUSER', + 'description' => 'Name of our Postgres user for authentication. Learn more.', + 'value' => '', + 'placeholder' => 'main_owner', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'PGPASSWORD', + 'description' => 'Password of our Postgres user for authentication. Learn more.', + 'value' => '', + 'placeholder' => 'iQCfaUaaWB3B', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'ENDPOINT_ID', + 'description' => 'Endpoint ID provided for your Postgres database. Learn more.', + 'value' => '', + 'placeholder' => 'ep-still-sea-a792sh84', + 'required' => true, + 'type' => 'text' + ] + ] + ], + [ + 'icon' => 'icon-open-ai', + 'id' => 'prompt-chatgpt', + 'name' => 'Prompt ChatGPT', + 'tagline' => 'Ask questions and let OpenAI GPT-3.5-turbo answer.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/prompt-chatgpt' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PYTHON'], + 'pip install -r requirements.txt', + 'src/main.py', + 'python/prompt_chatgpt' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PHP'], + 'composer install', + 'src/index.php', + 'php/prompt-chatgpt' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['DART'], + 'dart pub get', + 'lib/main.dart', + 'dart/prompt_chatgpt' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'OPENAI_API_KEY', + 'description' => 'A unique key used to authenticate with the OpenAI API. This is a paid service and you will be charged for each request made to the API. Learn more.', + 'value' => '', + 'placeholder' => 'sk-wzG...vcy', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'OPENAI_MAX_TOKENS', + 'description' => 'The maximum number of tokens that the OpenAI response should contain. Be aware that OpenAI models read and write a maximum number of tokens per API call, which varies depending on the model. For GPT-3.5-turbo, the limit is 4096 tokens. Learn more.', + 'value' => '512', + 'placeholder' => '512', + 'required' => false, + 'type' => 'number' + ] + ] + ], + [ + 'icon' => 'icon-discord', + 'id' => 'discord-command-bot', + 'name' => 'Discord Command Bot', + 'tagline' => 'Simple command using Discord Interactions.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['Messaging'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install && npm run setup', + 'src/main.js', + 'node/discord-command-bot' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PYTHON'], + 'pip install -r requirements.txt && python src/setup.py', + 'src/main.py', + 'python/discord_command_bot' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'DISCORD_PUBLIC_KEY', + 'description' => 'Public Key of your application in Discord Developer Portal. Learn more.', + 'value' => '', + 'placeholder' => 'db9...980', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'DISCORD_APPLICATION_ID', + 'description' => 'ID of your application in Discord Developer Portal. Learn more.', + 'value' => '', + 'placeholder' => '427...169', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'DISCORD_TOKEN', + 'description' => 'Bot token of your application in Discord Developer Portal. Learn more.', + 'value' => '', + 'placeholder' => 'NDI...LUfg', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-perspective-api', + 'id' => 'analyze-with-perspectiveapi', + 'name' => 'Analyze with PerspectiveAPI', + 'tagline' => 'Automate moderation by getting toxicity of messages.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/analyze-with-perspectiveapi' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'PERSPECTIVE_API_KEY', + 'description' => 'Google Perspective API key. It authenticates your function, allowing it to interact with the API. Learn more.', + 'value' => '', + 'placeholder' => 'AIzaS...fk-fuM', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-pangea', + 'id' => 'censor-with-redact', + 'name' => 'Censor with Redact', + 'tagline' => + 'Censor sensitive information from a provided text string using Redact API by Pangea.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/censor-with-redact' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PYTHON'], + 'pip install -r requirements.txt', + 'src/main.py', + 'python/censor_with_redact' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['DART'], + 'dart pub get', + 'lib/main.dart', + 'dart/censor_with_redact' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'PANGEA_REDACT_TOKEN', + 'description' => 'Access token for the Pangea Redact API. Learn more.', + 'value' => '', + 'placeholder' => 'pts_7p4...5wl4', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-document', + 'id' => 'generate-pdf', + 'name' => 'Generate PDF', + 'tagline' => 'Document containing sample invoice in PDF format.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['Utilities'], + 'runtimes' => [ + ...getRuntimes(TEMPLATE_RUNTIMES['NODE'], 'npm install', 'src/main.js', 'node/generate-pdf') + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [] + ], + [ + 'icon' => 'icon-github', + 'id' => 'github-issue-bot', + 'name' => 'GitHub issue bot', + 'tagline' => + 'Automate the process of responding to newly opened issues in a GitHub repository.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['Dev Tools'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/github-issue-bot' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'GITHUB_TOKEN', + 'description' => 'A personal access token from GitHub with the necessary permissions to post comments on issues. Learn more.', + 'value' => '', + 'placeholder' => 'ghp_1...', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'GITHUB_WEBHOOK_SECRET', + 'description' => 'The secret used to verify that the webhook request comes from GitHub. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-bookmark', + 'id' => 'url-shortener', + 'name' => 'URL shortener', + 'tagline' => 'Generate URL with short ID and redirect to the original URL when visited.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['Utilities'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/url-shortener' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_API_KEY', + 'description' => 'The API Key to authenticate against Appwrite\'s Server APIs. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_ENDPOINT', + 'description' => 'The API endpoint of the Appwrite. Learn more.', + 'value' => 'https://cloud.appwrite.io/v1', + 'placeholder' => 'https://cloud.appwrite.io/v1', + 'required' => false, + 'type' => 'url' + ], + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database to store the short URLs. Learn more.', + 'value' => 'urlShortener', + 'placeholder' => 'urlShortener', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection to store the short URLs. Learn more.', + 'value' => 'urls', + 'placeholder' => 'urls', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'SHORT_BASE_URL', + 'description' => 'The domain to use for the short URLs. You can use your functions subdomain or a custom domain.', + 'value' => '', + 'placeholder' => 'https://shortdomain.io', + 'required' => true, + 'type' => 'url' + ] + ] + ], + [ + 'icon' => 'icon-algolia', + 'id' => 'sync-with-algolia', + 'name' => 'Sync with Algolia', + 'tagline' => 'Intuitive search bar for any data in Appwrite Databases.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['Databases'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/sync-with-algolia' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PYTHON'], + 'pip install -r requirements.txt', + 'src/main.py', + 'python/sync_with_algolia' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PHP'], + 'composer install', + 'src/index.php', + 'php/sync-with-algolia' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_API_KEY', + 'description' => 'The API Key to authenticate against Appwrite\'s Server APIs. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the Appwrite database that contains the collection to sync. Learn more.', + 'placeholder' => '64a55...7b912', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection in the Appwrite database to sync. Learn more.', + 'placeholder' => '7c3e8...2a9f1', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'ALGOLIA_APP_ID', + 'description' => 'The ID of the application in Algolia. Learn more.', + 'placeholder' => 'OFCNCOG2CU', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'ALGOLIA_ADMIN_API_KEY', + 'description' => 'The admin API Key for your Algolia service. Learn more.', + 'placeholder' => 'fd0aa...136a8', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'ALGOLIA_INDEX_ID', + 'description' => 'The ID of the index in Algolia where the documents are to be synced. Learn more.', + 'placeholder' => 'my_index', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'ALGOLIA_SEARCH_API_KEY', + 'description' => 'The search API Key for your Algolia service. This key is used for searching the synced index. Learn more.', + 'placeholder' => 'bf2f5...df733', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_ENDPOINT', + 'description' => 'The URL endpoint of the Appwrite server. Learn more.', + 'value' => 'https://cloud.appwrite.io/v1', + 'placeholder' => 'https://cloud.appwrite.io/v1', + 'required' => false, + 'type' => 'url' + ] + ] + ], + [ + 'icon' => 'icon-meilisearch', + 'id' => 'sync-with-meilisearch', + 'name' => 'Sync with Meilisearch', + 'tagline' => 'Intuitive search bar for any data in Appwrite Databases.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['Databases'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/sync-with-meilisearch' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PYTHON'], + 'pip install -r requirements.txt', + 'src/main.py', + 'python/sync-with-meilisearch' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PHP'], + 'composer install', + 'src/index.php', + 'php/sync-with-meilisearch' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['BUN'], + 'bun install', + 'src/main.ts', + 'bun/sync-with-meilisearch' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['RUBY'], + 'bundle install', + 'lib/main.rb', + 'ruby/sync-with-meilisearch' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_API_KEY', + 'description' => 'The API Key to authenticate against Appwrite\'s Server APIs. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the Appwrite database that contains the collection to sync. Learn more.', + 'placeholder' => '64a55...7b912', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection in the Appwrite database to sync. Learn more.', + 'placeholder' => '7c3e8...2a9f1', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'MEILISEARCH_ENDPOINT', + 'description' => 'The host URL of the Meilisearch server. Learn more.', + 'placeholder' => 'http://127.0.0.1:7700', + 'required' => true, + 'type' => 'url' + ], + [ + 'name' => 'MEILISEARCH_ADMIN_API_KEY', + 'description' => 'The admin API key for Meilisearch. Learn more.', + 'placeholder' => 'masterKey1234', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'MEILISEARCH_SEARCH_API_KEY', + 'description' => 'API Key for Meilisearch search operations. Learn more.', + 'placeholder' => 'searchKey1234', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'MEILISEARCH_INDEX_NAME', + 'description' => 'Name of the Meilisearch index to which the documents will be synchronized. Learn more.', + 'placeholder' => 'appwrite_index', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_ENDPOINT', + 'description' => 'The URL endpoint of the Appwrite server. Learn more.', + 'value' => 'https://cloud.appwrite.io/v1', + 'placeholder' => 'https://cloud.appwrite.io/v1', + 'required' => false, + 'type' => 'url' + ] + ] + ], + [ + 'icon' => 'icon-vonage', + 'id' => 'whatsapp-with-vonage', + 'name' => 'WhatsApp with Vonage', + 'tagline' => 'Simple bot to answer WhatsApp messages.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['Messaging'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/whatsapp-with-vonage' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PYTHON'], + 'pip install -r requirements.txt', + 'src/main.py', + 'python/whatsapp_with_vonage' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PHP'], + 'composer install', + 'src/index.php', + 'php/whatsapp-with-vonage' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['DART'], + 'dart pub get', + 'lib/main.dart', + 'dart/whatsapp-with-vonage' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['RUBY'], + 'bundle install', + 'lib/main.rb', + 'ruby/whatsapp-with-vonage' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['BUN'], + 'bun install', + 'src/main.ts', + 'bun/whatsapp-with-vonage' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'VONAGE_API_KEY', + 'description' => 'API Key to use the Vonage API. Learn more.', + 'value' => '', + 'placeholder' => '62...97', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'VONAGE_API_SECRET', + 'description' => 'Secret to use the Vonage API. Learn more.', + 'placeholder' => 'Zjc...5PH', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'VONAGE_API_SIGNATURE_SECRET', + 'description' => 'Secret to verify the JWT token sent by Vonage. Learn more.', + 'placeholder' => 'NXOi3...IBHDa', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'VONAGE_WHATSAPP_NUMBER', + 'description' => 'Vonage WhatsApp number to send messages from. Learn more.', + 'placeholder' => '+14000000102', + 'required' => true, + 'type' => 'phone' + ] + ] + ], + [ + 'icon' => 'icon-bell', + 'id' => 'push-notification-with-fcm', + 'name' => 'Push notification with FCM', + 'tagline' => 'Send push notifications to your users using Firebase Cloud Messaging (FCM).', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['Messaging'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/push-notification-with-fcm' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'FCM_PROJECT_ID', + 'description' => 'A unique identifier for your FCM project. Learn more.', + 'value' => '', + 'placeholder' => 'mywebapp-f6e57', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'FCM_CLIENT_EMAIL', + 'description' => 'Your FCM service account email. Learn more.', + 'placeholder' => 'fcm-adminsdk-2f0de@test-f7q57.iam.gserviceaccount.com', + 'required' => true, + 'type' => 'email' + ], + [ + 'name' => 'FCM_PRIVATE_KEY', + 'description' => 'A unique private key used to authenticate with FCM. Learn more.', + 'placeholder' => '0b683...75675', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'FCM_DATABASE_URL', + 'description' => 'URL of your FCM database. Learn more.', + 'placeholder' => 'https://my-app-f298e.firebaseio.com', + 'required' => true, + 'type' => 'url' + ] + ] + ], + [ + 'icon' => 'icon-mail', + 'id' => 'email-contact-form', + 'name' => 'Email contact form', + 'tagline' => 'Sends an email with the contents of a HTML form.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['Utilities'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/email-contact-form' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PYTHON'], + 'pip install -r requirements.txt', + 'src/main.py', + 'python/email_contact_form' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['PHP'], + 'composer install', + 'src/index.php', + 'php/email-contact-form' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'SMTP_HOST', + 'description' => 'The address of your SMTP server. Many STMP providers will provide this information in their documentation. Some popular providers include: Mailgun, SendGrid, and Gmail.', + 'value' => '', + 'placeholder' => 'smtp.mailgun.org', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'SMTP_PORT', + 'description' => 'The port of your STMP server. Commnly used ports include 25, 465, and 587.', + 'placeholder' => '25', + 'required' => true, + 'type' => 'number' + ], + [ + 'name' => 'SMTP_USERNAME', + 'description' => 'The username for your SMTP server. This is commonly your email address.', + 'placeholder' => 'no-reply@mywebapp.org', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'SMTP_PASSWORD', + 'description' => 'The password for your SMTP server.', + 'placeholder' => '5up3r5tr0ngP4ssw0rd', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'SUBMIT_EMAIL', + 'description' => 'The email address to send form submissions to.', + 'placeholder' => 'me@mywebapp.org', + 'required' => true, + 'type' => 'email' + ], + [ + 'name' => 'ALLOWED_ORIGINS', + 'description' => 'An optional comma-separated list of allowed origins for CORS (defaults to *). This is an important security measure to prevent malicious users from abusing your function.', + 'value' => '', + 'placeholder' => 'https://mywebapp.org,https://mywebapp.com', + 'required' => false, + 'type' => 'text' + ] + ] + ], + [ + 'icon' => 'icon-stripe', + 'id' => 'subscriptions-with-stripe', + 'name' => 'Subscriptions with Stripe', + 'tagline' => 'Receive recurring card payments and grant subscribers extra permissions.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['Utilities'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/subscriptions-with-stripe' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_API_KEY', + 'description' => 'The API Key to authenticate against Appwrite\'s Server APIs. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_ENDPOINT', + 'description' => 'The URL endpoint of the Appwrite server. Learn more.', + 'value' => 'https://cloud.appwrite.io/v1', + 'placeholder' => 'https://cloud.appwrite.io/v1', + 'required' => false, + 'type' => 'url' + ], + [ + 'name' => 'STRIPE_SECRET_KEY', + 'description' => 'Secret for sending requests to the Stripe API. Learn more.', + 'placeholder' => 'sk_test_51J...', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'STRIPE_WEBHOOK_SECRET', + 'description' => 'Secret used to validate the Stripe Webhook signature. Learn more.', + 'placeholder' => 'whsec_...', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-stripe', + 'id' => 'payments-with-stripe', + 'name' => 'Payments with Stripe', + 'tagline' => 'Receive card payments and store paid orders.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['Utilities'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/payments-with-stripe' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_API_KEY', + 'description' => 'The API Key to authenticate against Appwrite\'s Server APIs. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_ENDPOINT', + 'description' => 'The URL endpoint of the Appwrite server. Learn more.', + 'value' => 'https://cloud.appwrite.io/v1', + 'placeholder' => 'https://cloud.appwrite.io/v1', + 'required' => false, + 'type' => 'url' + ], + [ + 'name' => 'STRIPE_SECRET_KEY', + 'description' => 'Secret for sending requests to the Stripe API. Learn more.', + 'placeholder' => 'sk_test_51J...', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'STRIPE_WEBHOOK_SECRET', + 'description' => 'Secret used to validate the Stripe Webhook signature. Learn more.', + 'placeholder' => 'whsec_...', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database to store paid orders. Learn more.', + 'value' => 'orders', + 'placeholder' => 'orders', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection to store paid orders. Learn more.', + 'value' => 'orders', + 'placeholder' => 'orders', + 'required' => false, + 'type' => 'text' + ] + ] + ], + [ + 'icon' => 'icon-chat', + 'id' => 'text-generation-with-huggingface', + 'name' => 'Text generation', + 'tagline' => 'Generate text using the Hugging Face inference API.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 30, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/text-generation-with-huggingface' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'HUGGINGFACE_ACCESS_TOKEN', + 'description' => 'Secret for sending requests to the Hugging Face API. Learn more.', + 'placeholder' => 'hf_MUvn...', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-translate', + 'id' => 'language-translation-with-huggingface', + 'name' => 'Language translation', + 'tagline' => 'Translate text using the Hugging Face inference API.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 30, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/language-translation-with-huggingface' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'HUGGINGFACE_ACCESS_TOKEN', + 'description' => 'Secret for sending requests to the Hugging Face API. Learn more.', + 'placeholder' => 'hf_MUvn...', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-eye', + 'id' => 'image-classification-with-huggingface', + 'name' => 'Image classification', + 'tagline' => 'Classify images using the Hugging Face inference API.', + 'permissions' => ['any'], + 'events' => ['buckets.*.files.*.create'], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install && npm run setup', + 'src/main.js', + 'node/image-classification-with-huggingface' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_API_KEY', + 'description' => 'The API Key to authenticate against Appwrite\'s Server APIs. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_ENDPOINT', + 'description' => 'The URL endpoint of the Appwrite server. Learn more.', + 'value' => 'https://cloud.appwrite.io/v1', + 'placeholder' => 'https://cloud.appwrite.io/v1', + 'required' => false, + 'type' => 'url' + ], + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database where the responses are stored. Learn more.', + 'value' => 'ai', + 'placeholder' => 'ai', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection where the responses are stored. Learn more.', + 'value' => 'image_classification', + 'placeholder' => 'image_classification', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_BUCKET_ID', + 'description' => 'The ID of the bucket where the images are stored. Learn more.', + 'value' => 'image_classification', + 'placeholder' => 'image_classification', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'HUGGINGFACE_ACCESS_TOKEN', + 'description' => 'Secret for sending requests to the Hugging Face API. Learn more.', + 'placeholder' => 'hf_MUvn...', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-eye', + 'id' => 'object-detection-with-huggingface', + 'name' => 'Object detection', + 'tagline' => 'Detect objects in images using the Hugging Face inference API.', + 'permissions' => ['any'], + 'events' => ['buckets.*.files.*.create'], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install && npm run setup', + 'src/main.js', + 'node/object-detection-with-huggingface' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_API_KEY', + 'description' => 'The API Key to authenticate against Appwrite\'s server APIs. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_ENDPOINT', + 'description' => 'The URL endpoint of the Appwrite server. Learn more.', + 'value' => 'https://cloud.appwrite.io/v1', + 'placeholder' => 'https://cloud.appwrite.io/v1', + 'required' => false, + 'type' => 'url' + ], + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database where the responses are stored. Learn more.', + 'value' => 'ai', + 'placeholder' => 'ai', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection where the responses are stored. Learn more.', + 'value' => 'object_detection', + 'placeholder' => 'object_detection', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_BUCKET_ID', + 'description' => 'The ID of the bucket where the images are stored. Learn more.', + 'value' => 'object_detection', + 'placeholder' => 'object_detection', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'HUGGINGFACE_ACCESS_TOKEN', + 'description' => 'Secret for sending requests to the Hugging Face API. Learn more.', + 'placeholder' => 'hf_MUvn...', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-text', + 'id' => 'speech-recognition-with-huggingface', + 'name' => 'Speech recognition', + 'tagline' => 'Transcribe audio to text using the Hugging Face inference API.', + 'permissions' => ['any'], + 'events' => ['buckets.*.files.*.create'], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install && npm run setup', + 'src/main.js', + 'node/speech-recognition-with-huggingface' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_API_KEY', + 'description' => 'The API Key to authenticate against Appwrite\'s server APIs. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_ENDPOINT', + 'description' => 'The URL endpoint of the Appwrite server. Learn more.', + 'value' => 'https://cloud.appwrite.io/v1', + 'placeholder' => 'https://cloud.appwrite.io/v1', + 'required' => false, + 'type' => 'url' + ], + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database where the responses are stored. Learn more.', + 'value' => 'ai', + 'placeholder' => 'ai', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection where the responses are stored. Learn more.', + 'value' => 'speech_recognition', + 'placeholder' => 'speech_recognition', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_BUCKET_ID', + 'description' => 'The ID of the bucket where audio is stored. Learn more.', + 'value' => 'speech_recognition', + 'placeholder' => 'speech_recognition', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'HUGGINGFACE_ACCESS_TOKEN', + 'description' => 'Secret for sending requests to the Hugging Face API. Learn more.', + 'placeholder' => 'hf_MUvn...', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-chat', + 'id' => 'text-to-speech-with-huggingface', + 'name' => 'Text to speech', + 'tagline' => 'Convert text to speech using the Hugging Face inference API.', + 'permissions' => ['any'], + 'events' => ['databases.*.collections.*.documents.*.create'], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install && npm run setup', + 'src/main.js', + 'node/text-to-speech-with-huggingface' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_API_KEY', + 'description' => 'The API Key to authenticate against Appwrite\'s server APIs. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_ENDPOINT', + 'description' => 'The URL endpoint of the Appwrite server. Learn more.', + 'value' => 'https://cloud.appwrite.io/v1', + 'placeholder' => 'https://cloud.appwrite.io/v1', + 'required' => false, + 'type' => 'url' + ], + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database where the responses are stored. Learn more.', + 'value' => 'ai', + 'placeholder' => 'ai', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection where the responses are stored. Learn more.', + 'value' => 'speech_recognition', + 'placeholder' => 'speech_recognition', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_BUCKET_ID', + 'description' => 'The ID of the bucket where audio is stored. Learn more.', + 'value' => 'speech_recognition', + 'placeholder' => 'speech_recognition', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'HUGGINGFACE_ACCESS_TOKEN', + 'description' => 'Secret for sending requests to the Hugging Face API. Learn more.', + 'placeholder' => 'hf_MUvn...', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-chip', + 'id' => 'generate-with-replicate', + 'name' => 'Generate with Replicate', + 'tagline' => "Generate text, audio and images using Replicate's API.", + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 300, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/generate-with-replicate' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'REPLICATE_API_KEY', + 'description' => 'A unique key used to authenticate with the Replicate API. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-chip', + 'id' => 'generate-with-together-ai', + 'name' => 'Generate with Together AI', + 'tagline' => "Generate text and images using Together AI's API.", + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 300, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/generate-with-together-ai' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'TOGETHER_API_KEY', + 'description' => 'A unique key used to authenticate with the Together AI API. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_API_KEY', + 'description' => 'The API Key to authenticate against Appwrite\'s server APIs. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_ENDPOINT', + 'description' => 'The URL endpoint of the Appwrite server. Learn more.', + 'value' => 'https://cloud.appwrite.io/v1', + 'placeholder' => 'https://cloud.appwrite.io/v1', + 'required' => false, + 'type' => 'url' + ], + [ + 'name' => 'APPWRITE_BUCKET_ID', + 'description' => 'The ID of the bucket where audio is stored. Learn more.', + 'placeholder' => 'generated_speech', + 'required' => true, + 'type' => 'text' + ] + ] + ], + [ + 'icon' => 'icon-chip', + 'id' => 'chat-with-perplexity-ai', + 'name' => 'Chat with Perplexity AI', + 'tagline' => 'Create a chatbot using the Perplexity AI API.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/chat-with-perplexity-ai' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'PERPLEXITY_API_KEY', + 'description' => 'A unique key used to authenticate with the Perplexity API. Learn more.', + 'placeholder' => 'pplex-68...999', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'PERPLEXITY_MAX_TOKENS', + 'description' => 'The maximum number of tokens to generate. Learn more.', + 'placeholder' => '512', + 'required' => false, + 'type' => 'number' + ] + ] + ], + [ + 'icon' => 'icon-chip', + 'id' => 'generate-with-replicate', + 'name' => 'Generate with Replicate', + 'tagline' => "Generate text, audio and images using Replicate's API.", + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 300, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/generate-with-replicate' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'REPLICATE_API_KEY', + 'description' => 'A unique key used to authenticate with the Replicate API. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-document-search', + 'id' => 'sync-with-pinecone', + 'name' => 'Sync with Pinecone', + 'tagline' => "Sync your Appwrite database with Pinecone's vector database.", + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 30, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/sync-with-pinecone' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'OPENAI_API_KEY', + 'description' => 'A unique key used to authenticate with the OpenAI API. This is a paid service and you will be charged for each request made to the API. Learn more.', + 'value' => '', + 'placeholder' => 'sk-wzG...vcy', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'PINECONE_API_KEY', + 'description' => 'A unique key used to authenticate with the Pinecone API. Learn more.', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'PINECONE_INDEX_NAME', + 'description' => 'The name of the index in Pinecone. Learn more.', + 'placeholder' => 'my-index', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_API_KEY', + 'description' => 'The API Key to authenticate against Appwrite\'s server APIs. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_ENDPOINT', + 'description' => 'The URL endpoint of the Appwrite server. Learn more.', + 'value' => 'https://cloud.appwrite.io/v1', + 'placeholder' => 'https://cloud.appwrite.io/v1', + 'required' => false, + 'type' => 'url' + ], + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database where the documents are stored. Learn more.', + 'placeholder' => 'my-database', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection where the documents are stored. Learn more.', + 'placeholder' => 'my-collection', + 'required' => true, + 'type' => 'text' + ] + ] + ], + [ + 'icon' => 'icon-chip', + 'id' => 'rag-with-langchain', + 'name' => 'RAG with LangChain', + 'tagline' => 'Generate text using a LangChain RAG model', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 30, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/rag-with-langchain' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'OPENAI_API_KEY', + 'description' => 'A unique key used to authenticate with the OpenAI API. This is a paid service and you will be charged for each request made to the API. Learn more.', + 'value' => '', + 'placeholder' => 'sk-wzG...vcy', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'PINECONE_API_KEY', + 'description' => 'A unique key used to authenticate with the Pinecone API. Learn more.', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'PINECONE_INDEX_NAME', + 'description' => 'The name of the index in Pinecone. Learn more.', + 'placeholder' => 'my-index', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_API_KEY', + 'description' => 'The API Key to authenticate against Appwrite\'s server APIs. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_ENDPOINT', + 'description' => 'The URL endpoint of the Appwrite server. Learn more.', + 'value' => 'https://cloud.appwrite.io/v1', + 'placeholder' => 'https://cloud.appwrite.io/v1', + 'required' => false, + 'type' => 'url' + ], + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database where the documents are stored. Learn more.', + 'placeholder' => 'my-database', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection where the documents are stored. Learn more.', + 'placeholder' => 'my-collection', + 'required' => true, + 'type' => 'text' + ] + ] + ], + [ + 'icon' => 'icon-chat', + 'id' => 'speak-with-elevenlabs', + 'name' => 'Speak with ElevenLabs', + 'tagline' => 'Convert text to speech using the ElevenLabs API.', + 'permissions' => ['any'], + 'cron' => '', + 'events' => [], + 'timeout' => 15, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/speak-with-elevenlabs' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'ELEVENLABS_API_KEY', + 'description' => 'A unique key used to authenticate with the ElevenLabs API. Learn more.', + 'placeholder' => 'd03xxxxxxxx26', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_API_KEY', + 'description' => 'The API Key to authenticate against Appwrite\'s server APIs. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_ENDPOINT', + 'description' => 'The URL endpoint of the Appwrite server. Learn more.', + 'value' => 'https://cloud.appwrite.io/v1', + 'placeholder' => 'https://cloud.appwrite.io/v1', + 'required' => false, + 'type' => 'url' + ], + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database where the responses are stored. Learn more.', + 'placeholder' => 'my-database', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection where the responses are stored. Learn more.', + 'placeholder' => 'my-collection', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_BUCKET_ID', + 'description' => 'The ID of the bucket where audio is stored. Learn more.', + 'placeholder' => 'generated_speech', + 'required' => true, + 'type' => 'text' + ] + ] + ], + [ + 'icon' => 'icon-chip', + 'id' => 'speak-with-lmnt', + 'name' => 'Speak with LMNT', + 'tagline' => 'Convert text to speech using the LMNT API.', + 'permissions' => ['any'], + 'cron' => '', + 'events' => [], + 'timeout' => 15, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/speak-with-lmnt' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'LMNT_API_KEY', + 'description' => 'A unique key used to authenticate with the LMNT API. Learn more.', + 'placeholder' => 'd03xxxxxxxx26', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_API_KEY', + 'description' => 'The API Key to authenticate against Appwrite\'s server APIs. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_ENDPOINT', + 'description' => 'The URL endpoint of the Appwrite server. Learn more.', + 'value' => 'https://cloud.appwrite.io/v1', + 'placeholder' => 'https://cloud.appwrite.io/v1', + 'required' => false, + 'type' => 'url' + ], + [ + 'name' => 'APPWRITE_BUCKET_ID', + 'description' => 'The ID of the bucket where audio is stored. Learn more.', + 'placeholder' => 'generated_speech', + 'required' => true, + 'type' => 'text' + ] + ] + ], + [ + 'icon' => 'icon-chip', + 'id' => 'chat-with-anyscale', + 'name' => 'Chat with AnyScale', + 'tagline' => 'Create a chatbot using the AnyScale API.', + 'permissions' => ['any'], + 'cron' => '', + 'events' => [], + 'timeout' => 15, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/chat-with-anyscale' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'ANYSCALE_API_KEY', + 'description' => 'A unique key used to authenticate with the AnyScale API. Learn more.', + 'placeholder' => 'd03xxxxxxxx26', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'ANYSCALE_MAX_TOKENS', + 'description' => 'The maximum number of tokens that Anyscale responses should contain. Learn more.', + 'placeholder' => '', + 'required' => false, + 'type' => 'number' + ] + ] + ], + [ + 'icon' => 'icon-music-note', + 'id' => 'music-generation-with-huggingface', + 'name' => 'Music generation', + 'tagline' => 'Generate music from a text prompt using the Hugging Face inference API.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install && npm run setup', + 'src/main.js', + 'node/music-generation-with-huggingface' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_API_KEY', + 'description' => 'The API Key to authenticate against Appwrite\'s server APIs. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_ENDPOINT', + 'description' => 'The URL endpoint of the Appwrite server. Learn more.', + 'value' => 'https://cloud.appwrite.io/v1', + 'placeholder' => 'https://cloud.appwrite.io/v1', + 'required' => false, + 'type' => 'url' + ], + [ + 'name' => 'APPWRITE_BUCKET_ID', + 'description' => 'The ID of the bucket where generated music is stored. Learn more.', + 'value' => 'generated_music', + 'placeholder' => 'generated_music', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'HUGGINGFACE_ACCESS_TOKEN', + 'description' => 'Secret for sending requests to the Hugging Face API. Learn more.', + 'placeholder' => 'hf_MUvn...', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-chip', + 'id' => 'generate-with-fal-ai', + 'name' => 'Generate with fal.ai', + 'tagline' => "Generate images using fal.ai's API.", + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 300, + 'usecases' => ['AI'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/generate-with-fal-ai' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'FAL_API_KEY', + 'description' => 'A unique key used to authenticate with the fal.ai API. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ] + ] + ], + [ + 'icon' => 'icon-currency-dollar', + 'id' => 'subscriptions-with-lemon-squeezy', + 'name' => 'Subscriptions with Lemon Squeezy', + 'tagline' => 'Receive recurring card payments and grant subscribers extra permissions.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['Utilities'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/subscriptions-with-lemon-squeezy' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_API_KEY', + 'description' => 'The API Key to authenticate against Appwrite\'s Server APIs. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_ENDPOINT', + 'description' => 'The URL endpoint of the Appwrite server. Learn more.', + 'value' => 'https://cloud.appwrite.io/v1', + 'placeholder' => 'https://cloud.appwrite.io/v1', + 'required' => false, + 'type' => 'url' + ], + [ + 'name' => 'LEMON_SQUEEZY_API_KEY', + 'description' => 'API key for sending requests to the Lemon Squeezy API. Learn more.', + 'placeholder' => 'eyJ0eXAiOiJ...', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'LEMON_SQUEEZY_WEBHOOK_SECRET', + 'description' => 'Secret used to validate the Lemon Squuezy Webhook signature. Learn more.', + 'placeholder' => 'abcd...', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'LEMON_SQUEEZY_STORE_ID', + 'description' => 'Store ID required to create a checkout using the Lemon Squeezy API. Learn more.', + 'placeholder' => '123456', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'LEMON_SQUEEZY_VARIANT_ID', + 'description' => 'Variant ID of a product required to create a checkout using the Lemon Squeezy API. Learn more.', + 'placeholder' => 'abcd...', + 'required' => true, + 'type' => 'text' + ] + ] + ], + [ + 'icon' => 'icon-currency-dollar', + 'id' => 'payments-with-lemon-squeezy', + 'name' => 'Payments with Lemon Squeezy', + 'tagline' => 'Receive card payments and store paid orders.', + 'permissions' => ['any'], + 'events' => [], + 'cron' => '', + 'timeout' => 15, + 'usecases' => ['Utilities'], + 'runtimes' => [ + ...getRuntimes( + TEMPLATE_RUNTIMES['NODE'], + 'npm install', + 'src/main.js', + 'node/payments-with-lemon-squeezy' + ) + ], + 'instructions' => 'For documentation and instructions check out file.', + 'vcsProvider' => 'github', + 'providerRepositoryId' => 'templates', + 'providerOwner' => 'appwrite', + 'providerBranch' => 'main', + 'variables' => [ + [ + 'name' => 'APPWRITE_API_KEY', + 'description' => 'The API Key to authenticate against Appwrite\'s Server APIs. Learn more.', + 'value' => '', + 'placeholder' => 'd1efb...aec35', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'APPWRITE_ENDPOINT', + 'description' => 'The URL endpoint of the Appwrite server. Learn more.', + 'value' => 'https://cloud.appwrite.io/v1', + 'placeholder' => 'https://cloud.appwrite.io/v1', + 'required' => false, + 'type' => 'url' + ], + [ + 'name' => 'APPWRITE_DATABASE_ID', + 'description' => 'The ID of the database to store paid orders. Learn more.', + 'value' => 'orders', + 'placeholder' => 'orders', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'APPWRITE_COLLECTION_ID', + 'description' => 'The ID of the collection to store paid orders. Learn more.', + 'value' => 'orders', + 'placeholder' => 'orders', + 'required' => false, + 'type' => 'text' + ], + [ + 'name' => 'LEMON_SQUEEZY_API_KEY', + 'description' => 'API key for sending requests to the Lemon Squeezy API. Learn more.', + 'placeholder' => 'eyJ0eXAiOiJ...', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'LEMON_SQUEEZY_WEBHOOK_SECRET', + 'description' => 'Secret used to validate the Lemon Squuezy Webhook signature. Learn more.', + 'placeholder' => 'abcd...', + 'required' => true, + 'type' => 'password' + ], + [ + 'name' => 'LEMON_SQUEEZY_STORE_ID', + 'description' => 'Store ID required to create a checkout using the Lemon Squeezy API. Learn more.', + 'placeholder' => '123456', + 'required' => true, + 'type' => 'text' + ], + [ + 'name' => 'LEMON_SQUEEZY_VARIANT_ID', + 'description' => 'Variant ID of a product required to create a checkout using the Lemon Squeezy API. Learn more.', + 'placeholder' => 'abcd...', + 'required' => true, + 'type' => 'text' + ] + ] + ] +]; \ No newline at end of file From 6c1cfb83a6ae4283389f6b2ad903a1245aa0499a Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Thu, 18 Jul 2024 23:50:09 +0530 Subject: [PATCH 15/43] Formatted file --- app/config/templates.php | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/app/config/templates.php b/app/config/templates.php index 5c6e4fbe0f..fff9735a94 100644 --- a/app/config/templates.php +++ b/app/config/templates.php @@ -27,15 +27,16 @@ const TEMPLATE_RUNTIMES = [ ] ]; -function getRuntimes($runtime, $commands, $entrypoint, $providerRootDirectory, $versionsDenyList = []) { - return array_map(function($version) use ($runtime, $commands, $entrypoint, $providerRootDirectory) { +function getRuntimes($runtime, $commands, $entrypoint, $providerRootDirectory, $versionsDenyList = []) +{ + return array_map(function ($version) use ($runtime, $commands, $entrypoint, $providerRootDirectory) { return [ 'name' => $runtime['name'] . '-' . $version, 'commands' => $commands, 'entrypoint' => $entrypoint, 'providerRootDirectory' => $providerRootDirectory ]; - }, array_filter($runtime['versions'], function($version) use ($versionsDenyList) { + }, array_filter($runtime['versions'], function ($version) use ($versionsDenyList) { return !in_array($version, $versionsDenyList); })); } @@ -46,7 +47,7 @@ return [ 'id' => 'starter', 'name' => 'Starter function', 'tagline' => - 'A simple function to get started. Edit this function to explore endless possibilities with Appwrite Functions.', + 'A simple function to get started. Edit this function to explore endless possibilities with Appwrite Functions.', 'permissions' => ['any'], 'events' => [], 'cron' => '', @@ -123,7 +124,7 @@ return [ 'description' => 'Authentication token to access your Upstash Vector database. Learn more.', 'value' => '', 'placeholder' => - 'oe4wNTbwHVLcDNa6oceZfhBEABsCNYh43ii6Xdq4bKBH7mq7qJkUmc4cs3ABbYyuVKWZTxVQjiNjYgydn2dkhABNes4NAuDpj7qxUAmZYqGJT78', + 'oe4wNTbwHVLcDNa6oceZfhBEABsCNYh43ii6Xdq4bKBH7mq7qJkUmc4cs3ABbYyuVKWZTxVQjiNjYgydn2dkhABNes4NAuDpj7qxUAmZYqGJT78', 'required' => true, 'type' => 'password' ] @@ -226,7 +227,7 @@ return [ 'id' => 'query-mongo-atlas', 'name' => 'Query MongoDB Atlas', 'tagline' => - 'Realtime NoSQL document database with geospecial, graph, search, and vector suport.', + 'Realtime NoSQL document database with geospecial, graph, search, and vector suport.', 'permissions' => ['any'], 'events' => [], 'cron' => '', @@ -251,7 +252,7 @@ return [ 'description' => 'The endpoint to connect to your Mongo database. Learn more.', 'value' => '', 'placeholder' => - 'mongodb+srv://appwrite:Yx42hafg7Q4fgkxe@cluster0.7mslfog.mongodb.net/?retryWrites=true&w=majority&appName=Appwrite', + 'mongodb+srv://appwrite:Yx42hafg7Q4fgkxe@cluster0.7mslfog.mongodb.net/?retryWrites=true&w=majority&appName=Appwrite', 'required' => true, 'type' => 'password' ] @@ -262,7 +263,7 @@ return [ 'id' => 'query-neon-postgres', 'name' => 'Query Neon Postgres', 'tagline' => - 'Reliable SQL database with replication, point-in-time recovery, and pgvector support.', + 'Reliable SQL database with replication, point-in-time recovery, and pgvector support.', 'permissions' => ['any'], 'events' => [], 'cron' => '', @@ -479,7 +480,7 @@ return [ 'id' => 'censor-with-redact', 'name' => 'Censor with Redact', 'tagline' => - 'Censor sensitive information from a provided text string using Redact API by Pangea.', + 'Censor sensitive information from a provided text string using Redact API by Pangea.', 'permissions' => ['any'], 'events' => [], 'cron' => '', @@ -546,7 +547,7 @@ return [ 'id' => 'github-issue-bot', 'name' => 'GitHub issue bot', 'tagline' => - 'Automate the process of responding to newly opened issues in a GitHub repository.', + 'Automate the process of responding to newly opened issues in a GitHub repository.', 'permissions' => ['any'], 'events' => [], 'cron' => '', @@ -2296,4 +2297,4 @@ return [ ] ] ] -]; \ No newline at end of file +]; From 1b5d815532b9ba460e7a01e601852b296ef1ccf2 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Fri, 19 Jul 2024 17:39:18 +0530 Subject: [PATCH 16/43] Add get function templates endpoint --- .../{templates.php => functionTemplates.php} | 0 app/controllers/api/functions.php | 20 +++ app/init.php | 1 + .../functions/get-function-templates.md | 1 + src/Appwrite/Utopia/Response.php | 15 ++ .../Response/Model/FunctionTemplate.php | 136 ++++++++++++++++++ .../Utopia/Response/Model/RuntimeTemplate.php | 59 ++++++++ .../Response/Model/VariableTemplate.php | 65 +++++++++ 8 files changed, 297 insertions(+) rename app/config/{templates.php => functionTemplates.php} (100%) create mode 100644 docs/references/functions/get-function-templates.md create mode 100644 src/Appwrite/Utopia/Response/Model/FunctionTemplate.php create mode 100644 src/Appwrite/Utopia/Response/Model/RuntimeTemplate.php create mode 100644 src/Appwrite/Utopia/Response/Model/VariableTemplate.php diff --git a/app/config/templates.php b/app/config/functionTemplates.php similarity index 100% rename from app/config/templates.php rename to app/config/functionTemplates.php diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index d2a1790d94..1318f2ce3f 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -2349,3 +2349,23 @@ App::delete('/v1/functions/:functionId/variables/:variableId') $response->noContent(); }); + +App::get('/v1/functions/templates') + ->desc('Get function templates') + ->groups(['api', 'functions']) + ->label('scope', 'functions.read') + ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'functions') + ->label('sdk.method', 'getFunctionTemplates') + ->label('sdk.description', '/docs/references/functions/get-function-templates.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_FUNCTION_TEMPLATE_LIST) + ->inject('response') + ->action(function (Response $response) { + $templates = Config::getParam('functionTemplates', []); + $response->dynamic(new Document([ + 'templates' => $templates, + 'total' => \count($templates), + ]), Response::MODEL_FUNCTION_TEMPLATE_LIST); + }); diff --git a/app/init.php b/app/init.php index a0e71f041b..dfc04ca2ae 100644 --- a/app/init.php +++ b/app/init.php @@ -303,6 +303,7 @@ Config::load('storage-logos', __DIR__ . '/config/storage/logos.php'); Config::load('storage-mimes', __DIR__ . '/config/storage/mimes.php'); Config::load('storage-inputs', __DIR__ . '/config/storage/inputs.php'); Config::load('storage-outputs', __DIR__ . '/config/storage/outputs.php'); +Config::load('functionTemplates', __DIR__ . '/config/functionTemplates.php'); // List of function templates /** * New DB Filters diff --git a/docs/references/functions/get-function-templates.md b/docs/references/functions/get-function-templates.md new file mode 100644 index 0000000000..8edc21e996 --- /dev/null +++ b/docs/references/functions/get-function-templates.md @@ -0,0 +1 @@ +Get function templates from marketplace. \ No newline at end of file diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 128fce62c8..59a654849a 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -44,6 +44,7 @@ use Appwrite\Utopia\Response\Model\ErrorDev; use Appwrite\Utopia\Response\Model\Execution; use Appwrite\Utopia\Response\Model\File; use Appwrite\Utopia\Response\Model\Func; +use Appwrite\Utopia\Response\Model\FunctionTemplate; use Appwrite\Utopia\Response\Model\Headers; use Appwrite\Utopia\Response\Model\HealthAntivirus; use Appwrite\Utopia\Response\Model\HealthCertificate; @@ -82,6 +83,7 @@ use Appwrite\Utopia\Response\Model\Provider; use Appwrite\Utopia\Response\Model\ProviderRepository; use Appwrite\Utopia\Response\Model\Rule; use Appwrite\Utopia\Response\Model\Runtime; +use Appwrite\Utopia\Response\Model\RuntimeTemplate; use Appwrite\Utopia\Response\Model\Session; use Appwrite\Utopia\Response\Model\Subscriber; use Appwrite\Utopia\Response\Model\Target; @@ -101,6 +103,7 @@ use Appwrite\Utopia\Response\Model\UsageStorage; use Appwrite\Utopia\Response\Model\UsageUsers; use Appwrite\Utopia\Response\Model\User; use Appwrite\Utopia\Response\Model\Variable; +use Appwrite\Utopia\Response\Model\VariableTemplate; use Appwrite\Utopia\Response\Model\VcsContent; use Appwrite\Utopia\Response\Model\Webhook; use Exception; @@ -251,6 +254,12 @@ class Response extends SwooleResponse public const MODEL_BUILD_LIST = 'buildList'; // Not used anywhere yet public const MODEL_FUNC_PERMISSIONS = 'funcPermissions'; public const MODEL_HEADERS = 'headers'; + public const MODEL_FUNCTION_TEMPLATE = 'functionTemplate'; + public const MODEL_FUNCTION_TEMPLATE_LIST = 'functionTemplateList'; + public const MODEL_RUNTIME_TEMPLATE = 'runtimeTemplate'; + public const MODEL_VARIABLE_TEMPLATE = 'variableTemplate'; + public const MODEL_RUNTIME_TEMPLATE_LIST = 'runtimeTemplateList'; + public const MODEL_VARIABLE_TEMPLATE_LIST = 'variableTemplateList'; // Proxy public const MODEL_PROXY_RULE = 'proxyRule'; @@ -340,6 +349,9 @@ class Response extends SwooleResponse ->setModel(new BaseList('Teams List', self::MODEL_TEAM_LIST, 'teams', self::MODEL_TEAM)) ->setModel(new BaseList('Memberships List', self::MODEL_MEMBERSHIP_LIST, 'memberships', self::MODEL_MEMBERSHIP)) ->setModel(new BaseList('Functions List', self::MODEL_FUNCTION_LIST, 'functions', self::MODEL_FUNCTION)) + ->setModel(new BaseList('Function Templates List', self::MODEL_FUNCTION_TEMPLATE_LIST, 'templates', self::MODEL_FUNCTION_TEMPLATE)) + ->setModel(new BaseList('Runtime Templates List', self::MODEL_RUNTIME_TEMPLATE_LIST, 'runtimes', self::MODEL_RUNTIME_TEMPLATE)) + ->setModel(new BaseList('Variable Templates List', self::MODEL_VARIABLE_TEMPLATE_LIST, 'variables', self::MODEL_VARIABLE_TEMPLATE)) ->setModel(new BaseList('Installations List', self::MODEL_INSTALLATION_LIST, 'installations', self::MODEL_INSTALLATION)) ->setModel(new BaseList('Provider Repositories List', self::MODEL_PROVIDER_REPOSITORY_LIST, 'providerRepositories', self::MODEL_PROVIDER_REPOSITORY)) ->setModel(new BaseList('Branches List', self::MODEL_BRANCH_LIST, 'branches', self::MODEL_BRANCH)) @@ -409,6 +421,9 @@ class Response extends SwooleResponse ->setModel(new Team()) ->setModel(new Membership()) ->setModel(new Func()) + ->setModel(new FunctionTemplate()) + ->setModel(new RuntimeTemplate()) + ->setModel(new VariableTemplate()) ->setModel(new Installation()) ->setModel(new ProviderRepository()) ->setModel(new Detection()) diff --git a/src/Appwrite/Utopia/Response/Model/FunctionTemplate.php b/src/Appwrite/Utopia/Response/Model/FunctionTemplate.php new file mode 100644 index 0000000000..f67400b095 --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/FunctionTemplate.php @@ -0,0 +1,136 @@ +addRule('icon', [ + 'type' => self::TYPE_STRING, + 'description' => 'Function Template Icon.', + 'default' => '', + 'example' => 'icon-lightning-bolt', + ]) + ->addRule('$id', [ + 'type' => self::TYPE_STRING, + 'description' => 'Function Template ID.', + 'default' => '', + 'example' => 'starter', + ]) + ->addRule('name', [ + 'type' => self::TYPE_STRING, + 'description' => 'Function Template Name.', + 'default' => '', + 'example' => 'Starter function', + ]) + ->addRule('tagline', [ + 'type' => self::TYPE_STRING, + 'description' => 'Function Template Tagline.', + 'default' => '', + 'example' => 'A simple function to get started.', + ]) + ->addRule('permissions', [ + 'type' => self::TYPE_STRING, + 'description' => 'Execution permissions.', + 'default' => [], + 'example' => 'any', + 'array' => true, + ]) + ->addRule('events', [ + 'type' => self::TYPE_STRING, + 'description' => 'Function trigger events.', + 'default' => [], + 'example' => 'account.create', + 'array' => true, + ]) + ->addRule('cron', [ + 'type' => self::TYPE_STRING, + 'description' => 'Function execution schedult in CRON format.', + 'default' => '', + 'example' => '0 0 * * *', + ]) + ->addRule('timeout', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Function execution timeout in seconds.', + 'default' => 15, + 'example' => 300, + ]) + ->addRule('usecases', [ + 'type' => self::TYPE_STRING, + 'description' => 'Function usecases.', + 'default' => [], + 'example' => 'Starter', + 'array' => true, + ]) + ->addRule('runtimes', [ + 'type' => Response::MODEL_RUNTIME_TEMPLATE, + 'description' => 'List of runtimes that can be used with this template.', + 'default' => [], + 'example' => [], + 'array' => true + ]) + ->addRule('instructions', [ + 'type' => self::TYPE_STRING, + 'description' => 'Function Template Instructions.', + 'default' => '', + 'example' => 'For documentation and instructions check out .', + ]) + ->addRule('vcsProvider', [ + 'type' => self::TYPE_STRING, + 'description' => 'VCS (Version Control System) Provider.', + 'default' => '', + 'example' => 'github', + ]) + ->addRule('providerRepositoryId', [ + 'type' => self::TYPE_STRING, + 'description' => 'VCS (Version Control System) Repository ID', + 'default' => '', + 'example' => 'templates', + ]) + ->addRule('providerOwner', [ + 'type' => self::TYPE_STRING, + 'description' => 'VCS (Version Control System) Owner.', + 'default' => '', + 'example' => 'appwrite', + ]) + ->addRule('providerBranch', [ + 'type' => self::TYPE_STRING, + 'description' => 'VCS (Version Control System) branch name', + 'default' => '', + 'example' => 'main', + ]) + ->addRule('variables', [ + 'type' => Response::MODEL_VARIABLE_TEMPLATE, + 'description' => 'Function variables.', + 'default' => [], + 'example' => [], + 'array' => true + ]) + ; + } + + /** + * Get Name + * + * @return string + */ + public function getName(): string + { + return 'Function Template'; + } + + /** + * Get Type + * + * @return string + */ + public function getType(): string + { + return Response::MODEL_FUNCTION_TEMPLATE; + } +} diff --git a/src/Appwrite/Utopia/Response/Model/RuntimeTemplate.php b/src/Appwrite/Utopia/Response/Model/RuntimeTemplate.php new file mode 100644 index 0000000000..de1a94302a --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/RuntimeTemplate.php @@ -0,0 +1,59 @@ +addRule('name', [ + 'type' => self::TYPE_STRING, + 'description' => 'Runtime Name.', + 'default' => '', + 'example' => 'node-19.0', + ]) + ->addRule('commands', [ + 'type' => self::TYPE_STRING, + 'description' => 'The build command used to build the deployment.', + 'default' => '', + 'example' => 'npm install', + ]) + ->addRule('entrypoint', [ + 'type' => self::TYPE_STRING, + 'description' => 'The entrypoint file used to execute the deployment.', + 'default' => '', + 'example' => 'index.js', + ]) + ->addRule('providerRootDirectory', [ + 'type' => self::TYPE_STRING, + 'description' => 'Path to function in VCS (Version Control System) repository', + 'default' => '', + 'example' => 'node/starter', + ]) + ; + } + + /** + * Get Name + * + * @return string + */ + public function getName(): string + { + return 'Runtime Template'; + } + + /** + * Get Type + * + * @return string + */ + public function getType(): string + { + return Response::MODEL_RUNTIME_TEMPLATE; + } +} diff --git a/src/Appwrite/Utopia/Response/Model/VariableTemplate.php b/src/Appwrite/Utopia/Response/Model/VariableTemplate.php new file mode 100644 index 0000000000..fdb464681d --- /dev/null +++ b/src/Appwrite/Utopia/Response/Model/VariableTemplate.php @@ -0,0 +1,65 @@ +addRule('name', [ + 'type' => self::TYPE_STRING, + 'description' => 'Variable Name.', + 'default' => '', + 'example' => 'APPWRITE_DATABASE_ID', + ]) + ->addRule('description', [ + 'type' => self::TYPE_STRING, + 'description' => 'Variable Description.', + 'default' => '', + 'example' => 'The ID of the Appwrite database that contains the collection to sync.', + ]) + ->addRule('placeholder', [ + 'type' => self::TYPE_STRING, + 'description' => 'Variable Placeholder.', + 'default' => '', + 'example' => '64a55...7b912', + ]) + ->addRule('required', [ + 'type' => self::TYPE_BOOLEAN, + 'description' => 'Is the variable required?', + 'default' => false, + 'example' => false, + ]) + ->addRule('type', [ + 'type' => self::TYPE_STRING, + 'description' => 'Variable Type.', + 'default' => '', + 'example' => 'password', + ]) + ; + } + + /** + * Get Name + * + * @return string + */ + public function getName(): string + { + return 'Variable Template'; + } + + /** + * Get Type + * + * @return string + */ + public function getType(): string + { + return Response::MODEL_VARIABLE_TEMPLATE; + } +} From 6af886687e522e736616ce5969423cfbc2d3a924 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Fri, 19 Jul 2024 18:24:20 +0530 Subject: [PATCH 17/43] Add Go templates --- app/config/functionTemplates.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/config/functionTemplates.php b/app/config/functionTemplates.php index fff9735a94..d3ac7a2df7 100644 --- a/app/config/functionTemplates.php +++ b/app/config/functionTemplates.php @@ -24,6 +24,10 @@ const TEMPLATE_RUNTIMES = [ 'BUN' => [ 'name' => 'bun', 'versions' => ['1.0'] + ], + 'GO' => [ + 'name' => 'go', + 'versions' => ['1.22'] ] ]; @@ -69,7 +73,8 @@ return [ 'python/starter' ), ...getRuntimes(TEMPLATE_RUNTIMES['DART'], 'dart pub get', 'lib/main.dart', 'dart/starter'), - ...getRuntimes(TEMPLATE_RUNTIMES['BUN'], 'bun install', 'src/main.ts', 'bun/starter') + ...getRuntimes(TEMPLATE_RUNTIMES['BUN'], 'bun install', 'src/main.ts', 'bun/starter'), + ...getRuntimes(TEMPLATE_RUNTIMES['GO'], 'go get', 'main.go', 'go/starter') ], 'instructions' => 'For documentation and instructions check out file.', 'vcsProvider' => 'github', @@ -407,6 +412,12 @@ return [ 'pip install -r requirements.txt && python src/setup.py', 'src/main.py', 'python/discord_command_bot' + ), + ...getRuntimes( + TEMPLATE_RUNTIMES['GO'], + 'go get', + 'main.go', + 'go/discord-command-bot' ) ], 'instructions' => 'For documentation and instructions check out file.', From 14589a11c0c6493ef501d21e7f52d23d8b2eafbe Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Fri, 19 Jul 2024 18:24:59 +0530 Subject: [PATCH 18/43] Add Go templates --- app/config/functionTemplates.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/config/functionTemplates.php b/app/config/functionTemplates.php index d3ac7a2df7..e00db3e44e 100644 --- a/app/config/functionTemplates.php +++ b/app/config/functionTemplates.php @@ -74,7 +74,7 @@ return [ ), ...getRuntimes(TEMPLATE_RUNTIMES['DART'], 'dart pub get', 'lib/main.dart', 'dart/starter'), ...getRuntimes(TEMPLATE_RUNTIMES['BUN'], 'bun install', 'src/main.ts', 'bun/starter'), - ...getRuntimes(TEMPLATE_RUNTIMES['GO'], 'go get', 'main.go', 'go/starter') + ...getRuntimes(TEMPLATE_RUNTIMES['GO'], '', 'main.go', 'go/starter') ], 'instructions' => 'For documentation and instructions check out file.', 'vcsProvider' => 'github', @@ -415,7 +415,7 @@ return [ ), ...getRuntimes( TEMPLATE_RUNTIMES['GO'], - 'go get', + '', 'main.go', 'go/discord-command-bot' ) From 2065468708c7751d5a97f3e4fdea82495a5b86a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Sun, 21 Jul 2024 13:00:12 +0000 Subject: [PATCH 19/43] Make session not required in users JWT generation --- app/controllers/api/users.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 8b32349957..78988f525b 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -2109,7 +2109,7 @@ App::post('/v1/users/:userId/jwts') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_JWT) ->param('userId', '', new UID(), 'User ID.') - ->param('sessionId', 'recent', new UID(), 'Session ID. Use the string \'recent\' to use the most recent session. Defaults to the most recent session.', true) + ->param('sessionId', '', new UID(), 'Session ID. Use the string \'recent\' to use the most recent session. Defaults to the most recent session.', true) ->param('duration', 900, new Range(0, 3600), 'Time in seconds before JWT expires. Default duration is 900 seconds, and maximum is 3600 seconds.', true) ->inject('response') ->inject('dbForProject') @@ -2137,17 +2137,13 @@ App::post('/v1/users/:userId/jwts') } } - if ($session->isEmpty()) { - throw new Exception(Exception::USER_SESSION_NOT_FOUND); - } - $jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $duration, 0); $response ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic(new Document(['jwt' => $jwt->encode([ 'userId' => $user->getId(), - 'sessionId' => $session->getId() + 'sessionId' => $session->isEmpty() ? '' : $session->getId() ])]), Response::MODEL_JWT); }); From d4a5891c0dc9853cc460d4547f502a287900f26f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Sun, 21 Jul 2024 13:28:23 +0000 Subject: [PATCH 20/43] Fix user JWTs without sessionID --- app/init.php | 11 +++--- tests/e2e/Services/Users/UsersBase.php | 47 ++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 7 deletions(-) diff --git a/app/init.php b/app/init.php index ef7742956f..c6a0680a9d 100644 --- a/app/init.php +++ b/app/init.php @@ -1242,14 +1242,15 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons } $jwtUserId = $payload['userId'] ?? ''; - $jwtSessionId = $payload['sessionId'] ?? ''; - - if ($jwtUserId && $jwtSessionId) { + if (!empty($jwtUserId)) { $user = $dbForProject->getDocument('users', $jwtUserId); } - if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token - $user = new Document([]); + $jwtSessionId = $payload['sessionId'] ?? ''; + if(!empty($jwtSessionId)) { + if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token + $user = new Document([]); + } } } diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index c34227e4de..c06bc6f4a8 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -1589,6 +1589,27 @@ trait UsersBase ], false); $this->assertEquals($user['headers']['status-code'], 201); + // Create JWT 0, with no session available + $response = $this->client->call(Client::METHOD_POST, '/users/' . $userId . '/jwts', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['jwt']); + $jwt0 = $response['body']['jwt']; + + // Ensure JWT 0 works + $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-jwt' => $jwt0, + ])); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals($userId, $response['body']['$id']); + // Create two sessions $response = $this->client->call(Client::METHOD_POST, '/account/sessions/email', array_merge([ 'origin' => 'http://localhost', @@ -1641,12 +1662,13 @@ trait UsersBase $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals($userId, $response['body']['$id']); - // Create JWT 2 for latest session using default param + // Create JWT 2 for latest session using 'current' param $response = $this->client->call(Client::METHOD_POST, '/users/' . $userId . '/jwts', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'duration' => 5 + 'duration' => 5, + 'sessionId' => 'current' ]); $this->assertEquals(201, $response['headers']['status-code']); @@ -1696,6 +1718,27 @@ trait UsersBase $this->assertEquals(401, $response['headers']['status-code']); + // Ensure JWT 0 works still even with no sessions + + $response = $this->client->call(Client::METHOD_DELETE, '/users/' . $userId . '/sessions', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'sessionId' => $session2Id + ]); + + $this->assertEquals(204, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, '/account', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-jwt' => $jwt0, + ])); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals($userId, $response['body']['$id']); + // Cleanup after test $response = $this->client->call(Client::METHOD_DELETE, '/users/' . $userId, array_merge([ From 8e61b2149f1b5e538b210dbde947b644904627b4 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 22 Jul 2024 11:33:31 +0900 Subject: [PATCH 21/43] Auto update V3 Runtimes into V4 Runtimes on execution --- app/controllers/api/functions.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index d2a1790d94..a810d7be5b 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -1621,6 +1621,13 @@ App::post('/v1/functions/:functionId/executions') $version = $function->getAttribute('version', 'v2'); $runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []); + /** Auto-update v3 runtimes into v4, remove me after we do migrations */ + if ($version === 'v3') { + $function->setAttribute('version', 'v4'); + $function = $dbForProject->updateDocument('functions', $function->getId(), $function); + $version = 'v4'; + } + $runtime = (isset($runtimes[$function->getAttribute('runtime', '')])) ? $runtimes[$function->getAttribute('runtime', '')] : null; if (\is_null($runtime)) { From 304910e303b90db4258624cb147ad8f207f00c99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Mon, 22 Jul 2024 10:36:18 +0000 Subject: [PATCH 22/43] Add schedule date to response format --- app/config/collections.php | 33 +++++++++++++++++++ app/controllers/api/functions.php | 12 +++++-- src/Appwrite/Migration/Version/V16.php | 30 +++++++++++++++++ .../Utopia/Response/Model/Execution.php | 8 +++++ .../Functions/FunctionsCustomClientTest.php | 17 ++++++++++ 5 files changed, 97 insertions(+), 3 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index d0c3df165e..f6a357b3c5 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -3848,6 +3848,39 @@ $projectCollections = array_merge([ 'array' => false, 'filters' => [], ], + [ + '$id' => ID::custom('scheduledAt'), + 'type' => Database::VAR_DATETIME, + 'format' => '', + 'size' => 0, + 'signed' => false, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => ['datetime'], + ], + [ + '$id' => ID::custom('scheduleInternalId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], + [ + '$id' => ID::custom('scheduleId'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => Database::LENGTH_KEY, + 'signed' => true, + 'required' => false, + 'default' => null, + 'array' => false, + 'filters' => [], + ], ], 'indexes' => [ [ diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index d2a1790d94..00988840c9 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -1744,9 +1744,8 @@ App::post('/v1/functions/:functionId/executions') ->setContext('function', $function); if ($async) { - $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution)); - if(is_null($scheduledAt)) { + $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution)); $queueForFunctions ->setType('http') ->setExecution($execution) @@ -1770,7 +1769,7 @@ App::post('/v1/functions/:functionId/executions') 'jwt' => $jwt, ]; - $dbForConsole->createDocument('schedules', new Document([ + $schedule = $dbForConsole->createDocument('schedules', new Document([ 'region' => System::getEnv('_APP_REGION', 'default'), 'resourceType' => ScheduleExecutions::getSupportedResource(), 'resourceId' => $execution->getId(), @@ -1781,6 +1780,13 @@ App::post('/v1/functions/:functionId/executions') 'data' => $data, 'active' => true, ])); + + $execution = $execution + ->setAttribute('scheduleId', $schedule->getId()) + ->setAttribute('scheduleInternalId', $schedule->getInternalId()) + ->setAttribute('scheduledAt', $scheduledAt); + + $execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution)); } return $response diff --git a/src/Appwrite/Migration/Version/V16.php b/src/Appwrite/Migration/Version/V16.php index 49f244598e..636a301717 100644 --- a/src/Appwrite/Migration/Version/V16.php +++ b/src/Appwrite/Migration/Version/V16.php @@ -48,6 +48,36 @@ class V16 extends Migration $this->projectDB->setNamespace("_{$this->project->getInternalId()}"); switch ($id) { + case 'executions': + try { + /** + * Create 'scheduledAt' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'scheduledAt'); + } catch (\Throwable $th) { + Console::warning("'scheduledAt' from {$id}: {$th->getMessage()}"); + } + + try { + /** + * Create 'scheduleInternalId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleInternalId'); + } catch (\Throwable $th) { + Console::warning("'scheduleInternalId' from {$id}: {$th->getMessage()}"); + } + + try { + /** + * Create 'scheduleId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleId'); + } catch (\Throwable $th) { + Console::warning("'scheduleId' from {$id}: {$th->getMessage()}"); + } + + break; + case 'sessions': try { /** diff --git a/src/Appwrite/Utopia/Response/Model/Execution.php b/src/Appwrite/Utopia/Response/Model/Execution.php index fbd9619a40..90fbdc9689 100644 --- a/src/Appwrite/Utopia/Response/Model/Execution.php +++ b/src/Appwrite/Utopia/Response/Model/Execution.php @@ -4,6 +4,7 @@ namespace Appwrite\Utopia\Response\Model; use Appwrite\Utopia\Response; use Appwrite\Utopia\Response\Model; +use Utopia\Database\DateTime; use Utopia\Database\Helpers\Role; class Execution extends Model @@ -110,6 +111,13 @@ class Execution extends Model 'default' => 0, 'example' => 0.400, ]) + ->addRule('scheduledAt', [ + 'type' => self::TYPE_DATETIME, + 'description' => 'The scheduled time for execution. If left empty, execution will be queued immediately.', + 'required' => false, + 'default' => DateTime::now(), + 'example' => self::TYPE_DATETIME_EXAMPLE, + ]) ; } diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index bf969d388a..39f97cba65 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -269,6 +269,7 @@ class FunctionsCustomClientTest extends Scope // Schedule execution for the future \date_default_timezone_set('UTC'); $futureTime = (new \DateTime())->add(new \DateInterval('PT10S'))->format('Y-m-d H:i:s'); + $futureTimeIso = (new \DateTime($futureTime))->format('Y-m-d\TH:i:s.vP'); $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([ 'content-type' => 'application/json', @@ -286,9 +287,24 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(202, $execution['headers']['status-code']); $this->assertEquals('scheduled', $execution['body']['status']); + $this->assertEquals($futureTimeIso, $execution['body']['scheduledAt']); $executionId = $execution['body']['$id']; + // List executions and ensure it has schedule date + $response = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions', [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertGreaterThan(0, \count($response['body']['executions'])); + $recentExecution = $response['body']['executions'][0]; + $this->assertEquals($executionId, $recentExecution['$id']); + $this->assertEquals($futureTimeIso, $recentExecution['scheduledAt']); + sleep(20); $execution = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/executions/' . $executionId, [ @@ -303,6 +319,7 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals('/custom', $execution['body']['requestPath']); $this->assertEquals('GET', $execution['body']['requestMethod']); $this->assertGreaterThan(0, $execution['body']['duration']); + $this->assertEquals($futureTimeIso, $execution['body']['scheduledAt']); /* Test for FAILURE */ From 9e3c5b9368745e4a06b80d3822612adf68545139 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 22 Jul 2024 19:40:05 +0900 Subject: [PATCH 23/43] Add variable runtimes sizes migrations --- src/Appwrite/Migration/Version/V21.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Appwrite/Migration/Version/V21.php b/src/Appwrite/Migration/Version/V21.php index 01fc2d19e0..be6c38be67 100644 --- a/src/Appwrite/Migration/Version/V21.php +++ b/src/Appwrite/Migration/Version/V21.php @@ -87,6 +87,13 @@ class V21 extends Migration Console::warning("'scopes' from {$id}: {$th->getMessage()}"); } + // Create size attribute + try { + $this->createAttributeFromCollection($this->projectDB, $id, 'size'); + } catch (Throwable $th) { + Console::warning("'size' from {$id}: {$th->getMessage()}"); + } + break; case 'executions': // Create requestMethod index @@ -133,7 +140,14 @@ class V21 extends Migration // Add accessedAt attribute $document->setAttribute('accessedAt', DateTime::now()); break; + case 'functions': + // Add scopes attribute + $document->setAttribute('scopes', []); + + // Add size attribute + $document->setAttribute('size', 's-1vcpu-512m'); } + return $document; } } From 4f558d35be000646725c70f9b3bdda0f70f0829f Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 22 Jul 2024 19:42:29 +0900 Subject: [PATCH 24/43] Improvement v3 -> v4 transition --- app/controllers/api/functions.php | 7 ------- src/Executor/Executor.php | 13 +++++++++++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index a810d7be5b..d2a1790d94 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -1621,13 +1621,6 @@ App::post('/v1/functions/:functionId/executions') $version = $function->getAttribute('version', 'v2'); $runtimes = Config::getParam($version === 'v2' ? 'runtimes-v2' : 'runtimes', []); - /** Auto-update v3 runtimes into v4, remove me after we do migrations */ - if ($version === 'v3') { - $function->setAttribute('version', 'v4'); - $function = $dbForProject->updateDocument('functions', $function->getId(), $function); - $version = 'v4'; - } - $runtime = (isset($runtimes[$function->getAttribute('runtime', '')])) ? $runtimes[$function->getAttribute('runtime', '')] : null; if (\is_null($runtime)) { diff --git a/src/Executor/Executor.php b/src/Executor/Executor.php index 29ed756932..3206381e95 100644 --- a/src/Executor/Executor.php +++ b/src/Executor/Executor.php @@ -75,6 +75,12 @@ class Executor $runtimeId = "$projectId-$deploymentId-build"; $route = "/runtimes"; $timeout = (int) System::getEnv('_APP_FUNCTIONS_BUILD_TIMEOUT', 900); + + // Remove after migration + if ($version == 'v3') { + $version = 'v4'; + } + $params = [ 'runtimeId' => $runtimeId, 'source' => $source, @@ -188,6 +194,13 @@ class Executor $runtimeId = "$projectId-$deploymentId"; $route = '/runtimes/' . $runtimeId . '/execution'; + + + // Remove after migration + if ($version == 'v3') { + $version = 'v4'; + } + $params = [ 'runtimeId' => $runtimeId, 'variables' => $variables, From 4b585466e1a43930d2efe097c3f7de0e236ce561 Mon Sep 17 00:00:00 2001 From: Bradley Schofield Date: Mon, 22 Jul 2024 20:03:45 +0900 Subject: [PATCH 25/43] Update api.php --- app/controllers/shared/api.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 583a0160a1..672366fa01 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -206,6 +206,7 @@ App::init() throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET); } + // Remove after migration if(!\str_contains($apiKey, '_')) { $keyType = API_KEY_STANDARD; $authKey = $apiKey; From cb3c7505fe2157fde54375a713a74eace67dc00e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 23 Jul 2024 14:28:13 +0200 Subject: [PATCH 26/43] Fix Router dynamic keys --- app/controllers/general.php | 14 ++++++++++++++ src/Appwrite/Platform/Workers/Functions.php | 3 +++ 2 files changed, 17 insertions(+) diff --git a/app/controllers/general.php b/app/controllers/general.php index 92ddec58f5..469a3a2f6b 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -2,6 +2,7 @@ require_once __DIR__ . '/../init.php'; +use Ahc\Jwt\JWT; use Appwrite\Auth\Auth; use Appwrite\Event\Certificate; use Appwrite\Event\Event; @@ -163,7 +164,15 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo throw new AppwriteException(AppwriteException::USER_UNAUTHORIZED, 'To execute function using domain, execute permissions must include "any" or "guests"'); } + $jwtExpiry = $function->getAttribute('timeout', 900); + $jwtObj = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', $jwtExpiry, 0); + $apiKey = $jwtObj->encode([ + 'projectId' => $project->getId(), + 'scopes' => $function->getAttribute('scopes', []) + ]); + $headers = \array_merge([], $requestHeaders); + $headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $apiKey; $headers['x-appwrite-trigger'] = 'http'; $headers['x-appwrite-user-id'] = ''; $headers['x-appwrite-user-jwt'] = ''; @@ -242,8 +251,13 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo $vars[$var->getAttribute('key')] = $var->getAttribute('value', ''); } + $protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') == 'disabled' ? 'http' : 'https'; + $hostname = System::getEnv('_APP_DOMAIN'); + $endpoint = $protocol . '://' . $hostname . "/v1"; + // Appwrite vars $vars = \array_merge($vars, [ + 'APPWRITE_FUNCTION_API_ENDPOINT' => $endpoint, 'APPWRITE_FUNCTION_ID' => $functionId, 'APPWRITE_FUNCTION_NAME' => $function->getAttribute('name'), 'APPWRITE_FUNCTION_DEPLOYMENT' => $deployment->getId(), diff --git a/src/Appwrite/Platform/Workers/Functions.php b/src/Appwrite/Platform/Workers/Functions.php index b071a79bfd..0be652e12d 100644 --- a/src/Appwrite/Platform/Workers/Functions.php +++ b/src/Appwrite/Platform/Workers/Functions.php @@ -358,6 +358,9 @@ class Functions extends Action $headers['x-appwrite-event'] = $event ?? ''; $headers['x-appwrite-user-id'] = $user->getId() ?? ''; $headers['x-appwrite-user-jwt'] = $jwt ?? ''; + $headers['x-appwrite-country-code'] = ''; + $headers['x-appwrite-continent-code'] = ''; + $headers['x-appwrite-continent-eu'] = 'false'; /** Create execution or update execution status */ $execution = $dbForProject->getDocument('executions', $executionId ?? ''); From 51c163a42f6fe556425e8b18075635f2892c4b00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 24 Jul 2024 06:55:36 +0000 Subject: [PATCH 27/43] Apply migrations properly --- src/Appwrite/Migration/Version/V16.php | 30 ------------------- src/Appwrite/Migration/Version/V21.php | 29 ++++++++++++++++++ src/Appwrite/Utopia/Response/Filters/V18.php | 7 +++++ .../unit/Utopia/Response/Filters/V18Test.php | 26 ++++++++++++++++ 4 files changed, 62 insertions(+), 30 deletions(-) diff --git a/src/Appwrite/Migration/Version/V16.php b/src/Appwrite/Migration/Version/V16.php index 636a301717..49f244598e 100644 --- a/src/Appwrite/Migration/Version/V16.php +++ b/src/Appwrite/Migration/Version/V16.php @@ -48,36 +48,6 @@ class V16 extends Migration $this->projectDB->setNamespace("_{$this->project->getInternalId()}"); switch ($id) { - case 'executions': - try { - /** - * Create 'scheduledAt' attribute - */ - $this->createAttributeFromCollection($this->projectDB, $id, 'scheduledAt'); - } catch (\Throwable $th) { - Console::warning("'scheduledAt' from {$id}: {$th->getMessage()}"); - } - - try { - /** - * Create 'scheduleInternalId' attribute - */ - $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleInternalId'); - } catch (\Throwable $th) { - Console::warning("'scheduleInternalId' from {$id}: {$th->getMessage()}"); - } - - try { - /** - * Create 'scheduleId' attribute - */ - $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleId'); - } catch (\Throwable $th) { - Console::warning("'scheduleId' from {$id}: {$th->getMessage()}"); - } - - break; - case 'sessions': try { /** diff --git a/src/Appwrite/Migration/Version/V21.php b/src/Appwrite/Migration/Version/V21.php index be6c38be67..1eda618c52 100644 --- a/src/Appwrite/Migration/Version/V21.php +++ b/src/Appwrite/Migration/Version/V21.php @@ -69,6 +69,35 @@ class V21 extends Migration Console::warning("'accessedAt' from {$id}: {$th->getMessage()}"); } break; + case 'executions': + try { + /** + * Create 'scheduledAt' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'scheduledAt'); + } catch (\Throwable $th) { + Console::warning("'scheduledAt' from {$id}: {$th->getMessage()}"); + } + + try { + /** + * Create 'scheduleInternalId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleInternalId'); + } catch (\Throwable $th) { + Console::warning("'scheduleInternalId' from {$id}: {$th->getMessage()}"); + } + + try { + /** + * Create 'scheduleId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleId'); + } catch (\Throwable $th) { + Console::warning("'scheduleId' from {$id}: {$th->getMessage()}"); + } + + break; case 'schedules': // Create data attribute try { diff --git a/src/Appwrite/Utopia/Response/Filters/V18.php b/src/Appwrite/Utopia/Response/Filters/V18.php index b32856bdb4..d0aa680e3b 100644 --- a/src/Appwrite/Utopia/Response/Filters/V18.php +++ b/src/Appwrite/Utopia/Response/Filters/V18.php @@ -14,6 +14,7 @@ class V18 extends Filter $parsedResponse = match($model) { Response::MODEL_FUNCTION => $this->parseFunction($content), + Response::MODEL_EXECUTION => $this->parseExecution($content), Response::MODEL_PROJECT => $this->parseProject($content), default => $parsedResponse, }; @@ -21,6 +22,12 @@ class V18 extends Filter return $parsedResponse; } + protected function parseExecution(array $content) + { + unset($content['scheduledAt']); + return $content; + } + protected function parseFunction(array $content) { unset($content['scopes']); diff --git a/tests/unit/Utopia/Response/Filters/V18Test.php b/tests/unit/Utopia/Response/Filters/V18Test.php index 36719a7620..c4011c08a1 100644 --- a/tests/unit/Utopia/Response/Filters/V18Test.php +++ b/tests/unit/Utopia/Response/Filters/V18Test.php @@ -50,6 +50,32 @@ class V18Test extends TestCase $this->assertEquals($expected, $result); } + + public function executionProvider(): array + { + return [ + 'remove scheduledAt' => [ + [ + 'scheduledAt' => '2024-07-13T09:00:00.000Z', + ], + [ + ] + ] + ]; + } + + /** + * @dataProvider executionProvider + */ + public function testExecution(array $content, array $expected): void + { + $model = Response::MODEL_EXECUTION; + + $result = $this->filter->parse($content, $model); + + $this->assertEquals($expected, $result); + } + public function projectProvider(): array { return [ From ab55e6f5117407bbf7e1e635d1f041bba33cf155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 24 Jul 2024 07:00:20 +0000 Subject: [PATCH 28/43] Linter fix --- src/Appwrite/Migration/Version/V21.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Migration/Version/V21.php b/src/Appwrite/Migration/Version/V21.php index 1eda618c52..a75ba58949 100644 --- a/src/Appwrite/Migration/Version/V21.php +++ b/src/Appwrite/Migration/Version/V21.php @@ -97,7 +97,7 @@ class V21 extends Migration Console::warning("'scheduleId' from {$id}: {$th->getMessage()}"); } - break; + break; case 'schedules': // Create data attribute try { From c42a2bc5b7e99ecda80ee02ad3cb17bb64105af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 24 Jul 2024 07:11:48 +0000 Subject: [PATCH 29/43] PR review changes --- src/Appwrite/Migration/Version/V21.php | 56 +++++++++++++------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/src/Appwrite/Migration/Version/V21.php b/src/Appwrite/Migration/Version/V21.php index a75ba58949..7dd2912234 100644 --- a/src/Appwrite/Migration/Version/V21.php +++ b/src/Appwrite/Migration/Version/V21.php @@ -68,35 +68,6 @@ class V21 extends Migration } catch (Throwable $th) { Console::warning("'accessedAt' from {$id}: {$th->getMessage()}"); } - break; - case 'executions': - try { - /** - * Create 'scheduledAt' attribute - */ - $this->createAttributeFromCollection($this->projectDB, $id, 'scheduledAt'); - } catch (\Throwable $th) { - Console::warning("'scheduledAt' from {$id}: {$th->getMessage()}"); - } - - try { - /** - * Create 'scheduleInternalId' attribute - */ - $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleInternalId'); - } catch (\Throwable $th) { - Console::warning("'scheduleInternalId' from {$id}: {$th->getMessage()}"); - } - - try { - /** - * Create 'scheduleId' attribute - */ - $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleId'); - } catch (\Throwable $th) { - Console::warning("'scheduleId' from {$id}: {$th->getMessage()}"); - } - break; case 'schedules': // Create data attribute @@ -145,6 +116,33 @@ class V21 extends Migration } catch (\Throwable $th) { Console::warning("'_key_deployment' from {$id}: {$th->getMessage()}"); } + + try { + /** + * Create 'scheduledAt' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'scheduledAt'); + } catch (\Throwable $th) { + Console::warning("'scheduledAt' from {$id}: {$th->getMessage()}"); + } + + try { + /** + * Create 'scheduleInternalId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleInternalId'); + } catch (\Throwable $th) { + Console::warning("'scheduleInternalId' from {$id}: {$th->getMessage()}"); + } + + try { + /** + * Create 'scheduleId' attribute + */ + $this->createAttributeFromCollection($this->projectDB, $id, 'scheduleId'); + } catch (\Throwable $th) { + Console::warning("'scheduleId' from {$id}: {$th->getMessage()}"); + } } usleep(50000); From d2b2d1a1354887ba67f1ff5acbb87135684f47d9 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 24 Jul 2024 16:33:37 +0545 Subject: [PATCH 30/43] fix file size calculations --- app/controllers/api/storage.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 652644d44b..b080066653 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -63,7 +63,7 @@ App::post('/v1/storage/buckets') ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, no user is granted with any permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) ->param('fileSecurity', false, new Boolean(true), 'Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) ->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true) - ->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1024 * 1024, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1024 * 1024), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan']) + ->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan']) ->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true) ->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true) ->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true) @@ -240,7 +240,7 @@ App::put('/v1/storage/buckets/:bucketId') ->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) ->param('fileSecurity', false, new Boolean(true), 'Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](https://appwrite.io/docs/permissions).', true) ->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true) - ->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1024 * 1024, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1024 * 1024), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan']) + ->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan']) ->param('allowedFileExtensions', [], new ArrayList(new Text(64), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Allowed file extensions. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' extensions are allowed, each 64 characters long.', true) ->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD]), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true) ->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true) From 5c65fa895d0fe788260b8261495795f7f5d3f54d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Thu, 25 Jul 2024 06:54:50 +0000 Subject: [PATCH 31/43] PR review changes --- tests/e2e/Services/Functions/FunctionsCustomClientTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index a44420b544..4e501692fa 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -8,6 +8,7 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; +use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Role; @@ -269,7 +270,7 @@ class FunctionsCustomClientTest extends Scope // Schedule execution for the future \date_default_timezone_set('UTC'); $futureTime = (new \DateTime())->add(new \DateInterval('PT10S'))->format('Y-m-d H:i:s'); - $futureTimeIso = (new \DateTime($futureTime))->format('Y-m-d\TH:i:s.vP'); + $futureTimeIso = DateTime::formatTz($futureTime); $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $function['body']['$id'] . '/executions', array_merge([ 'content-type' => 'application/json', @@ -298,7 +299,6 @@ class FunctionsCustomClientTest extends Scope 'x-appwrite-key' => $this->getProject()['apiKey'], ]); - $this->assertEquals(200, $response['headers']['status-code']); $this->assertGreaterThan(0, \count($response['body']['executions'])); $recentExecution = $response['body']['executions'][0]; From df5c495e8ffa8711873300146a8e9a18c16c8ea1 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Fri, 26 Jul 2024 16:06:40 +0530 Subject: [PATCH 32/43] Remove APPWRITE_API_KEY and add scopes --- app/config/functionTemplates.php | 335 ++++--------------------------- 1 file changed, 36 insertions(+), 299 deletions(-) diff --git a/app/config/functionTemplates.php b/app/config/functionTemplates.php index e00db3e44e..940229b83c 100644 --- a/app/config/functionTemplates.php +++ b/app/config/functionTemplates.php @@ -81,16 +81,8 @@ return [ 'providerRepositoryId' => 'templates', 'providerOwner' => 'appwrite', 'providerBranch' => 'main', - 'variables' => [ - [ - 'name' => 'APPWRITE_API_KEY', - 'description' => 'The API Key to authenticate against Appwrite\'s Server APIs. Learn more.', - 'value' => '', - 'placeholder' => 'd1efb...aec35', - 'required' => false, - 'type' => 'password' - ] - ] + 'variables' => [], + 'scopes' => ["users.read"] ], [ 'icon' => 'icon-upstash', @@ -620,22 +612,6 @@ return [ 'providerOwner' => 'appwrite', 'providerBranch' => 'main', 'variables' => [ - [ - 'name' => 'APPWRITE_API_KEY', - 'description' => 'The API Key to authenticate against Appwrite\'s Server APIs. Learn more.', - 'value' => '', - 'placeholder' => 'd1efb...aec35', - 'required' => true, - 'type' => 'password' - ], - [ - 'name' => 'APPWRITE_ENDPOINT', - 'description' => 'The API endpoint of the Appwrite. Learn more.', - 'value' => 'https://cloud.appwrite.io/v1', - 'placeholder' => 'https://cloud.appwrite.io/v1', - 'required' => false, - 'type' => 'url' - ], [ 'name' => 'APPWRITE_DATABASE_ID', 'description' => 'The ID of the database to store the short URLs. Learn more.', @@ -660,7 +636,8 @@ return [ 'required' => true, 'type' => 'url' ] - ] + ], + 'scopes' => ["databases.read", "databases.write", "collections.write", "attributes.write", "documents.read", "documents.write"] ], [ 'icon' => 'icon-algolia', @@ -698,14 +675,6 @@ return [ 'providerOwner' => 'appwrite', 'providerBranch' => 'main', 'variables' => [ - [ - 'name' => 'APPWRITE_API_KEY', - 'description' => 'The API Key to authenticate against Appwrite\'s Server APIs. Learn more.', - 'value' => '', - 'placeholder' => 'd1efb...aec35', - 'required' => true, - 'type' => 'password' - ], [ 'name' => 'APPWRITE_DATABASE_ID', 'description' => 'The ID of the Appwrite database that contains the collection to sync. Learn more.', @@ -748,15 +717,8 @@ return [ 'required' => true, 'type' => 'password' ], - [ - 'name' => 'APPWRITE_ENDPOINT', - 'description' => 'The URL endpoint of the Appwrite server. Learn more.', - 'value' => 'https://cloud.appwrite.io/v1', - 'placeholder' => 'https://cloud.appwrite.io/v1', - 'required' => false, - 'type' => 'url' - ] - ] + ], + 'scopes' => ["databases.read", "collections.read", "documents.read"] ], [ 'icon' => 'icon-meilisearch', @@ -806,14 +768,6 @@ return [ 'providerOwner' => 'appwrite', 'providerBranch' => 'main', 'variables' => [ - [ - 'name' => 'APPWRITE_API_KEY', - 'description' => 'The API Key to authenticate against Appwrite\'s Server APIs. Learn more.', - 'value' => '', - 'placeholder' => 'd1efb...aec35', - 'required' => true, - 'type' => 'password' - ], [ 'name' => 'APPWRITE_DATABASE_ID', 'description' => 'The ID of the Appwrite database that contains the collection to sync. Learn more.', @@ -856,15 +810,8 @@ return [ 'required' => true, 'type' => 'text' ], - [ - 'name' => 'APPWRITE_ENDPOINT', - 'description' => 'The URL endpoint of the Appwrite server. Learn more.', - 'value' => 'https://cloud.appwrite.io/v1', - 'placeholder' => 'https://cloud.appwrite.io/v1', - 'required' => false, - 'type' => 'url' - ] - ] + ], + 'scopes' => ["databases.read", "collections.read", "documents.read"] ], [ 'icon' => 'icon-vonage', @@ -1112,22 +1059,6 @@ return [ 'providerOwner' => 'appwrite', 'providerBranch' => 'main', 'variables' => [ - [ - 'name' => 'APPWRITE_API_KEY', - 'description' => 'The API Key to authenticate against Appwrite\'s Server APIs. Learn more.', - 'value' => '', - 'placeholder' => 'd1efb...aec35', - 'required' => true, - 'type' => 'password' - ], - [ - 'name' => 'APPWRITE_ENDPOINT', - 'description' => 'The URL endpoint of the Appwrite server. Learn more.', - 'value' => 'https://cloud.appwrite.io/v1', - 'placeholder' => 'https://cloud.appwrite.io/v1', - 'required' => false, - 'type' => 'url' - ], [ 'name' => 'STRIPE_SECRET_KEY', 'description' => 'Secret for sending requests to the Stripe API. Learn more.', @@ -1142,7 +1073,8 @@ return [ 'required' => true, 'type' => 'password' ] - ] + ], + 'scopes' => ["users.read", "sessions.write", "users.write"] ], [ 'icon' => 'icon-stripe', @@ -1168,22 +1100,6 @@ return [ 'providerOwner' => 'appwrite', 'providerBranch' => 'main', 'variables' => [ - [ - 'name' => 'APPWRITE_API_KEY', - 'description' => 'The API Key to authenticate against Appwrite\'s Server APIs. Learn more.', - 'value' => '', - 'placeholder' => 'd1efb...aec35', - 'required' => true, - 'type' => 'password' - ], - [ - 'name' => 'APPWRITE_ENDPOINT', - 'description' => 'The URL endpoint of the Appwrite server. Learn more.', - 'value' => 'https://cloud.appwrite.io/v1', - 'placeholder' => 'https://cloud.appwrite.io/v1', - 'required' => false, - 'type' => 'url' - ], [ 'name' => 'STRIPE_SECRET_KEY', 'description' => 'Secret for sending requests to the Stripe API. Learn more.', @@ -1214,7 +1130,8 @@ return [ 'required' => false, 'type' => 'text' ] - ] + ], + 'scopes' => ["databases.read", "databases.write", "collections.write", "attributes.write", "documents.read", "documents.write"] ], [ 'icon' => 'icon-chat', @@ -1306,22 +1223,6 @@ return [ 'providerOwner' => 'appwrite', 'providerBranch' => 'main', 'variables' => [ - [ - 'name' => 'APPWRITE_API_KEY', - 'description' => 'The API Key to authenticate against Appwrite\'s Server APIs. Learn more.', - 'value' => '', - 'placeholder' => 'd1efb...aec35', - 'required' => true, - 'type' => 'password' - ], - [ - 'name' => 'APPWRITE_ENDPOINT', - 'description' => 'The URL endpoint of the Appwrite server. Learn more.', - 'value' => 'https://cloud.appwrite.io/v1', - 'placeholder' => 'https://cloud.appwrite.io/v1', - 'required' => false, - 'type' => 'url' - ], [ 'name' => 'APPWRITE_DATABASE_ID', 'description' => 'The ID of the database where the responses are stored. Learn more.', @@ -1353,7 +1254,8 @@ return [ 'required' => true, 'type' => 'password' ] - ] + ], + 'scopes' => ["databases.read", "databases.write", "collections.read", "collections.write", "attributes.write", "documents.read", "documents.write", "buckets.read", "buckets.write", "files.read"] ], [ 'icon' => 'icon-eye', @@ -1379,22 +1281,6 @@ return [ 'providerOwner' => 'appwrite', 'providerBranch' => 'main', 'variables' => [ - [ - 'name' => 'APPWRITE_API_KEY', - 'description' => 'The API Key to authenticate against Appwrite\'s server APIs. Learn more.', - 'value' => '', - 'placeholder' => 'd1efb...aec35', - 'required' => true, - 'type' => 'password' - ], - [ - 'name' => 'APPWRITE_ENDPOINT', - 'description' => 'The URL endpoint of the Appwrite server. Learn more.', - 'value' => 'https://cloud.appwrite.io/v1', - 'placeholder' => 'https://cloud.appwrite.io/v1', - 'required' => false, - 'type' => 'url' - ], [ 'name' => 'APPWRITE_DATABASE_ID', 'description' => 'The ID of the database where the responses are stored. Learn more.', @@ -1426,7 +1312,8 @@ return [ 'required' => true, 'type' => 'password' ] - ] + ], + "scopes" => ["databases.read", "databases.write", "collections.read", "collections.write", "attributes.write", "documents.read", "documents.write", "buckets.read", "buckets.write", "files.read"] ], [ 'icon' => 'icon-text', @@ -1452,22 +1339,6 @@ return [ 'providerOwner' => 'appwrite', 'providerBranch' => 'main', 'variables' => [ - [ - 'name' => 'APPWRITE_API_KEY', - 'description' => 'The API Key to authenticate against Appwrite\'s server APIs. Learn more.', - 'value' => '', - 'placeholder' => 'd1efb...aec35', - 'required' => true, - 'type' => 'password' - ], - [ - 'name' => 'APPWRITE_ENDPOINT', - 'description' => 'The URL endpoint of the Appwrite server. Learn more.', - 'value' => 'https://cloud.appwrite.io/v1', - 'placeholder' => 'https://cloud.appwrite.io/v1', - 'required' => false, - 'type' => 'url' - ], [ 'name' => 'APPWRITE_DATABASE_ID', 'description' => 'The ID of the database where the responses are stored. Learn more.', @@ -1499,7 +1370,8 @@ return [ 'required' => true, 'type' => 'password' ] - ] + ], + "scopes" => ["databases.read", "databases.write", "collections.read", "collections.write", "attributes.write", "documents.read", "documents.write", "buckets.read", "buckets.write", "files.read"] ], [ 'icon' => 'icon-chat', @@ -1525,22 +1397,6 @@ return [ 'providerOwner' => 'appwrite', 'providerBranch' => 'main', 'variables' => [ - [ - 'name' => 'APPWRITE_API_KEY', - 'description' => 'The API Key to authenticate against Appwrite\'s server APIs. Learn more.', - 'value' => '', - 'placeholder' => 'd1efb...aec35', - 'required' => true, - 'type' => 'password' - ], - [ - 'name' => 'APPWRITE_ENDPOINT', - 'description' => 'The URL endpoint of the Appwrite server. Learn more.', - 'value' => 'https://cloud.appwrite.io/v1', - 'placeholder' => 'https://cloud.appwrite.io/v1', - 'required' => false, - 'type' => 'url' - ], [ 'name' => 'APPWRITE_DATABASE_ID', 'description' => 'The ID of the database where the responses are stored. Learn more.', @@ -1572,7 +1428,8 @@ return [ 'required' => true, 'type' => 'password' ] - ] + ], + "scopes" => ["buckets.read", "buckets.write", "files.read", "files.write"] ], [ 'icon' => 'icon-chip', @@ -1640,22 +1497,6 @@ return [ 'required' => true, 'type' => 'password' ], - [ - 'name' => 'APPWRITE_API_KEY', - 'description' => 'The API Key to authenticate against Appwrite\'s server APIs. Learn more.', - 'value' => '', - 'placeholder' => 'd1efb...aec35', - 'required' => true, - 'type' => 'password' - ], - [ - 'name' => 'APPWRITE_ENDPOINT', - 'description' => 'The URL endpoint of the Appwrite server. Learn more.', - 'value' => 'https://cloud.appwrite.io/v1', - 'placeholder' => 'https://cloud.appwrite.io/v1', - 'required' => false, - 'type' => 'url' - ], [ 'name' => 'APPWRITE_BUCKET_ID', 'description' => 'The ID of the bucket where audio is stored. Learn more.', @@ -1663,7 +1504,8 @@ return [ 'required' => true, 'type' => 'text' ] - ] + ], + "scopes" => ["buckets.write", "files.read", "files.write"] ], [ 'icon' => 'icon-chip', @@ -1785,22 +1627,6 @@ return [ 'required' => true, 'type' => 'text' ], - [ - 'name' => 'APPWRITE_API_KEY', - 'description' => 'The API Key to authenticate against Appwrite\'s server APIs. Learn more.', - 'value' => '', - 'placeholder' => 'd1efb...aec35', - 'required' => true, - 'type' => 'password' - ], - [ - 'name' => 'APPWRITE_ENDPOINT', - 'description' => 'The URL endpoint of the Appwrite server. Learn more.', - 'value' => 'https://cloud.appwrite.io/v1', - 'placeholder' => 'https://cloud.appwrite.io/v1', - 'required' => false, - 'type' => 'url' - ], [ 'name' => 'APPWRITE_DATABASE_ID', 'description' => 'The ID of the database where the documents are stored. Learn more.', @@ -1815,7 +1641,8 @@ return [ 'required' => true, 'type' => 'text' ] - ] + ], + "scopes" => ["databases.read", "collections.read", "documents.read"] ], [ 'icon' => 'icon-chip', @@ -1863,22 +1690,6 @@ return [ 'required' => true, 'type' => 'text' ], - [ - 'name' => 'APPWRITE_API_KEY', - 'description' => 'The API Key to authenticate against Appwrite\'s server APIs. Learn more.', - 'value' => '', - 'placeholder' => 'd1efb...aec35', - 'required' => true, - 'type' => 'password' - ], - [ - 'name' => 'APPWRITE_ENDPOINT', - 'description' => 'The URL endpoint of the Appwrite server. Learn more.', - 'value' => 'https://cloud.appwrite.io/v1', - 'placeholder' => 'https://cloud.appwrite.io/v1', - 'required' => false, - 'type' => 'url' - ], [ 'name' => 'APPWRITE_DATABASE_ID', 'description' => 'The ID of the database where the documents are stored. Learn more.', @@ -1893,7 +1704,8 @@ return [ 'required' => true, 'type' => 'text' ] - ] + ], + "scopes" => ["databases.read", "collections.read", "documents.read"] ], [ 'icon' => 'icon-chat', @@ -1926,22 +1738,6 @@ return [ 'required' => true, 'type' => 'password' ], - [ - 'name' => 'APPWRITE_API_KEY', - 'description' => 'The API Key to authenticate against Appwrite\'s server APIs. Learn more.', - 'value' => '', - 'placeholder' => 'd1efb...aec35', - 'required' => true, - 'type' => 'password' - ], - [ - 'name' => 'APPWRITE_ENDPOINT', - 'description' => 'The URL endpoint of the Appwrite server. Learn more.', - 'value' => 'https://cloud.appwrite.io/v1', - 'placeholder' => 'https://cloud.appwrite.io/v1', - 'required' => false, - 'type' => 'url' - ], [ 'name' => 'APPWRITE_DATABASE_ID', 'description' => 'The ID of the database where the responses are stored. Learn more.', @@ -1963,7 +1759,8 @@ return [ 'required' => true, 'type' => 'text' ] - ] + ], + "scopes" => ["buckets.read", "buckets.write", "files.read", "files.write"] ], [ 'icon' => 'icon-chip', @@ -1996,22 +1793,6 @@ return [ 'required' => true, 'type' => 'password' ], - [ - 'name' => 'APPWRITE_API_KEY', - 'description' => 'The API Key to authenticate against Appwrite\'s server APIs. Learn more.', - 'value' => '', - 'placeholder' => 'd1efb...aec35', - 'required' => true, - 'type' => 'password' - ], - [ - 'name' => 'APPWRITE_ENDPOINT', - 'description' => 'The URL endpoint of the Appwrite server. Learn more.', - 'value' => 'https://cloud.appwrite.io/v1', - 'placeholder' => 'https://cloud.appwrite.io/v1', - 'required' => false, - 'type' => 'url' - ], [ 'name' => 'APPWRITE_BUCKET_ID', 'description' => 'The ID of the bucket where audio is stored. Learn more.', @@ -2019,7 +1800,8 @@ return [ 'required' => true, 'type' => 'text' ] - ] + ], + "scopes" => ["buckets.read", "buckets.write", "files.read", "files.write"] ], [ 'icon' => 'icon-chip', @@ -2085,22 +1867,6 @@ return [ 'providerOwner' => 'appwrite', 'providerBranch' => 'main', 'variables' => [ - [ - 'name' => 'APPWRITE_API_KEY', - 'description' => 'The API Key to authenticate against Appwrite\'s server APIs. Learn more.', - 'value' => '', - 'placeholder' => 'd1efb...aec35', - 'required' => true, - 'type' => 'password' - ], - [ - 'name' => 'APPWRITE_ENDPOINT', - 'description' => 'The URL endpoint of the Appwrite server. Learn more.', - 'value' => 'https://cloud.appwrite.io/v1', - 'placeholder' => 'https://cloud.appwrite.io/v1', - 'required' => false, - 'type' => 'url' - ], [ 'name' => 'APPWRITE_BUCKET_ID', 'description' => 'The ID of the bucket where generated music is stored. Learn more.', @@ -2116,7 +1882,8 @@ return [ 'required' => true, 'type' => 'password' ] - ] + ], + "scopes" => ["buckets.read", "buckets.write", "files.read", "files.write"] ], [ 'icon' => 'icon-chip', @@ -2176,22 +1943,6 @@ return [ 'providerOwner' => 'appwrite', 'providerBranch' => 'main', 'variables' => [ - [ - 'name' => 'APPWRITE_API_KEY', - 'description' => 'The API Key to authenticate against Appwrite\'s Server APIs. Learn more.', - 'value' => '', - 'placeholder' => 'd1efb...aec35', - 'required' => true, - 'type' => 'password' - ], - [ - 'name' => 'APPWRITE_ENDPOINT', - 'description' => 'The URL endpoint of the Appwrite server. Learn more.', - 'value' => 'https://cloud.appwrite.io/v1', - 'placeholder' => 'https://cloud.appwrite.io/v1', - 'required' => false, - 'type' => 'url' - ], [ 'name' => 'LEMON_SQUEEZY_API_KEY', 'description' => 'API key for sending requests to the Lemon Squeezy API. Learn more.', @@ -2220,7 +1971,8 @@ return [ 'required' => true, 'type' => 'text' ] - ] + ], + "scopes" => ["users.read", "users.write"] ], [ 'icon' => 'icon-currency-dollar', @@ -2246,22 +1998,6 @@ return [ 'providerOwner' => 'appwrite', 'providerBranch' => 'main', 'variables' => [ - [ - 'name' => 'APPWRITE_API_KEY', - 'description' => 'The API Key to authenticate against Appwrite\'s Server APIs. Learn more.', - 'value' => '', - 'placeholder' => 'd1efb...aec35', - 'required' => true, - 'type' => 'password' - ], - [ - 'name' => 'APPWRITE_ENDPOINT', - 'description' => 'The URL endpoint of the Appwrite server. Learn more.', - 'value' => 'https://cloud.appwrite.io/v1', - 'placeholder' => 'https://cloud.appwrite.io/v1', - 'required' => false, - 'type' => 'url' - ], [ 'name' => 'APPWRITE_DATABASE_ID', 'description' => 'The ID of the database to store paid orders. Learn more.', @@ -2306,6 +2042,7 @@ return [ 'required' => true, 'type' => 'text' ] - ] + ], + "scopes" => ["users.read", "users.write"] ] ]; From 03de5782932c6d35236f06b6b2b1366c98681802 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Fri, 26 Jul 2024 17:47:03 +0530 Subject: [PATCH 33/43] Rename as per convensions --- ...onTemplates.php => function-templates.php} | 0 app/controllers/api/functions.php | 6 ++--- app/init.php | 2 +- src/Appwrite/Utopia/Response.php | 26 ++++++++----------- ...ctionTemplate.php => TemplateFunction.php} | 10 +++---- ...untimeTemplate.php => TemplateRuntime.php} | 6 ++--- ...iableTemplate.php => TemplateVariable.php} | 6 ++--- 7 files changed, 26 insertions(+), 30 deletions(-) rename app/config/{functionTemplates.php => function-templates.php} (100%) rename src/Appwrite/Utopia/Response/Model/{FunctionTemplate.php => TemplateFunction.php} (94%) rename src/Appwrite/Utopia/Response/Model/{RuntimeTemplate.php => TemplateRuntime.php} (92%) rename src/Appwrite/Utopia/Response/Model/{VariableTemplate.php => TemplateVariable.php} (92%) diff --git a/app/config/functionTemplates.php b/app/config/function-templates.php similarity index 100% rename from app/config/functionTemplates.php rename to app/config/function-templates.php diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 1318f2ce3f..7a9a217ad4 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -2360,12 +2360,12 @@ App::get('/v1/functions/templates') ->label('sdk.description', '/docs/references/functions/get-function-templates.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_FUNCTION_TEMPLATE_LIST) + ->label('sdk.response.model', Response::MODEL_TEMPLATE_FUNCTION_LIST) ->inject('response') ->action(function (Response $response) { - $templates = Config::getParam('functionTemplates', []); + $templates = Config::getParam('function-templates', []); $response->dynamic(new Document([ 'templates' => $templates, 'total' => \count($templates), - ]), Response::MODEL_FUNCTION_TEMPLATE_LIST); + ]), Response::MODEL_TEMPLATE_FUNCTION_LIST); }); diff --git a/app/init.php b/app/init.php index 81688fcbd3..e7a07a40d0 100644 --- a/app/init.php +++ b/app/init.php @@ -303,7 +303,7 @@ Config::load('storage-logos', __DIR__ . '/config/storage/logos.php'); Config::load('storage-mimes', __DIR__ . '/config/storage/mimes.php'); Config::load('storage-inputs', __DIR__ . '/config/storage/inputs.php'); Config::load('storage-outputs', __DIR__ . '/config/storage/outputs.php'); -Config::load('functionTemplates', __DIR__ . '/config/functionTemplates.php'); // List of function templates +Config::load('function-templates', __DIR__ . '/config/function-templates.php'); // List of function templates /** * New DB Filters diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 59a654849a..d2e78bb310 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -44,7 +44,6 @@ use Appwrite\Utopia\Response\Model\ErrorDev; use Appwrite\Utopia\Response\Model\Execution; use Appwrite\Utopia\Response\Model\File; use Appwrite\Utopia\Response\Model\Func; -use Appwrite\Utopia\Response\Model\FunctionTemplate; use Appwrite\Utopia\Response\Model\Headers; use Appwrite\Utopia\Response\Model\HealthAntivirus; use Appwrite\Utopia\Response\Model\HealthCertificate; @@ -83,13 +82,15 @@ use Appwrite\Utopia\Response\Model\Provider; use Appwrite\Utopia\Response\Model\ProviderRepository; use Appwrite\Utopia\Response\Model\Rule; use Appwrite\Utopia\Response\Model\Runtime; -use Appwrite\Utopia\Response\Model\RuntimeTemplate; use Appwrite\Utopia\Response\Model\Session; use Appwrite\Utopia\Response\Model\Subscriber; use Appwrite\Utopia\Response\Model\Target; use Appwrite\Utopia\Response\Model\Team; use Appwrite\Utopia\Response\Model\TemplateEmail; +use Appwrite\Utopia\Response\Model\TemplateFunction; +use Appwrite\Utopia\Response\Model\TemplateRuntime; use Appwrite\Utopia\Response\Model\TemplateSMS; +use Appwrite\Utopia\Response\Model\TemplateVariable; use Appwrite\Utopia\Response\Model\Token; use Appwrite\Utopia\Response\Model\Topic; use Appwrite\Utopia\Response\Model\UsageBuckets; @@ -103,7 +104,6 @@ use Appwrite\Utopia\Response\Model\UsageStorage; use Appwrite\Utopia\Response\Model\UsageUsers; use Appwrite\Utopia\Response\Model\User; use Appwrite\Utopia\Response\Model\Variable; -use Appwrite\Utopia\Response\Model\VariableTemplate; use Appwrite\Utopia\Response\Model\VcsContent; use Appwrite\Utopia\Response\Model\Webhook; use Exception; @@ -254,12 +254,10 @@ class Response extends SwooleResponse public const MODEL_BUILD_LIST = 'buildList'; // Not used anywhere yet public const MODEL_FUNC_PERMISSIONS = 'funcPermissions'; public const MODEL_HEADERS = 'headers'; - public const MODEL_FUNCTION_TEMPLATE = 'functionTemplate'; - public const MODEL_FUNCTION_TEMPLATE_LIST = 'functionTemplateList'; - public const MODEL_RUNTIME_TEMPLATE = 'runtimeTemplate'; - public const MODEL_VARIABLE_TEMPLATE = 'variableTemplate'; - public const MODEL_RUNTIME_TEMPLATE_LIST = 'runtimeTemplateList'; - public const MODEL_VARIABLE_TEMPLATE_LIST = 'variableTemplateList'; + public const MODEL_TEMPLATE_FUNCTION = 'templateFunction'; + public const MODEL_TEMPLATE_FUNCTION_LIST = 'templateFunctionList'; + public const MODEL_TEMPLATE_RUNTIME = 'templateRuntime'; + public const MODEL_TEMPLATE_VARIABLE = 'templateVariable'; // Proxy public const MODEL_PROXY_RULE = 'proxyRule'; @@ -349,9 +347,7 @@ class Response extends SwooleResponse ->setModel(new BaseList('Teams List', self::MODEL_TEAM_LIST, 'teams', self::MODEL_TEAM)) ->setModel(new BaseList('Memberships List', self::MODEL_MEMBERSHIP_LIST, 'memberships', self::MODEL_MEMBERSHIP)) ->setModel(new BaseList('Functions List', self::MODEL_FUNCTION_LIST, 'functions', self::MODEL_FUNCTION)) - ->setModel(new BaseList('Function Templates List', self::MODEL_FUNCTION_TEMPLATE_LIST, 'templates', self::MODEL_FUNCTION_TEMPLATE)) - ->setModel(new BaseList('Runtime Templates List', self::MODEL_RUNTIME_TEMPLATE_LIST, 'runtimes', self::MODEL_RUNTIME_TEMPLATE)) - ->setModel(new BaseList('Variable Templates List', self::MODEL_VARIABLE_TEMPLATE_LIST, 'variables', self::MODEL_VARIABLE_TEMPLATE)) + ->setModel(new BaseList('Function Templates List', self::MODEL_TEMPLATE_FUNCTION_LIST, 'templates', self::MODEL_TEMPLATE_FUNCTION)) ->setModel(new BaseList('Installations List', self::MODEL_INSTALLATION_LIST, 'installations', self::MODEL_INSTALLATION)) ->setModel(new BaseList('Provider Repositories List', self::MODEL_PROVIDER_REPOSITORY_LIST, 'providerRepositories', self::MODEL_PROVIDER_REPOSITORY)) ->setModel(new BaseList('Branches List', self::MODEL_BRANCH_LIST, 'branches', self::MODEL_BRANCH)) @@ -421,9 +417,9 @@ class Response extends SwooleResponse ->setModel(new Team()) ->setModel(new Membership()) ->setModel(new Func()) - ->setModel(new FunctionTemplate()) - ->setModel(new RuntimeTemplate()) - ->setModel(new VariableTemplate()) + ->setModel(new TemplateFunction()) + ->setModel(new TemplateRuntime()) + ->setModel(new TemplateVariable()) ->setModel(new Installation()) ->setModel(new ProviderRepository()) ->setModel(new Detection()) diff --git a/src/Appwrite/Utopia/Response/Model/FunctionTemplate.php b/src/Appwrite/Utopia/Response/Model/TemplateFunction.php similarity index 94% rename from src/Appwrite/Utopia/Response/Model/FunctionTemplate.php rename to src/Appwrite/Utopia/Response/Model/TemplateFunction.php index f67400b095..5fdbd95a35 100644 --- a/src/Appwrite/Utopia/Response/Model/FunctionTemplate.php +++ b/src/Appwrite/Utopia/Response/Model/TemplateFunction.php @@ -5,7 +5,7 @@ namespace Appwrite\Utopia\Response\Model; use Appwrite\Utopia\Response; use Appwrite\Utopia\Response\Model; -class FunctionTemplate extends Model +class TemplateFunction extends Model { public function __construct() { @@ -68,7 +68,7 @@ class FunctionTemplate extends Model 'array' => true, ]) ->addRule('runtimes', [ - 'type' => Response::MODEL_RUNTIME_TEMPLATE, + 'type' => Response::MODEL_TEMPLATE_RUNTIME, 'description' => 'List of runtimes that can be used with this template.', 'default' => [], 'example' => [], @@ -105,7 +105,7 @@ class FunctionTemplate extends Model 'example' => 'main', ]) ->addRule('variables', [ - 'type' => Response::MODEL_VARIABLE_TEMPLATE, + 'type' => Response::MODEL_TEMPLATE_VARIABLE, 'description' => 'Function variables.', 'default' => [], 'example' => [], @@ -121,7 +121,7 @@ class FunctionTemplate extends Model */ public function getName(): string { - return 'Function Template'; + return 'Template Function'; } /** @@ -131,6 +131,6 @@ class FunctionTemplate extends Model */ public function getType(): string { - return Response::MODEL_FUNCTION_TEMPLATE; + return Response::MODEL_TEMPLATE_FUNCTION; } } diff --git a/src/Appwrite/Utopia/Response/Model/RuntimeTemplate.php b/src/Appwrite/Utopia/Response/Model/TemplateRuntime.php similarity index 92% rename from src/Appwrite/Utopia/Response/Model/RuntimeTemplate.php rename to src/Appwrite/Utopia/Response/Model/TemplateRuntime.php index de1a94302a..c08ea9b32a 100644 --- a/src/Appwrite/Utopia/Response/Model/RuntimeTemplate.php +++ b/src/Appwrite/Utopia/Response/Model/TemplateRuntime.php @@ -5,7 +5,7 @@ namespace Appwrite\Utopia\Response\Model; use Appwrite\Utopia\Response; use Appwrite\Utopia\Response\Model; -class RuntimeTemplate extends Model +class TemplateRuntime extends Model { public function __construct() { @@ -44,7 +44,7 @@ class RuntimeTemplate extends Model */ public function getName(): string { - return 'Runtime Template'; + return 'Template Runtime'; } /** @@ -54,6 +54,6 @@ class RuntimeTemplate extends Model */ public function getType(): string { - return Response::MODEL_RUNTIME_TEMPLATE; + return Response::MODEL_TEMPLATE_RUNTIME; } } diff --git a/src/Appwrite/Utopia/Response/Model/VariableTemplate.php b/src/Appwrite/Utopia/Response/Model/TemplateVariable.php similarity index 92% rename from src/Appwrite/Utopia/Response/Model/VariableTemplate.php rename to src/Appwrite/Utopia/Response/Model/TemplateVariable.php index fdb464681d..b0fd919dbf 100644 --- a/src/Appwrite/Utopia/Response/Model/VariableTemplate.php +++ b/src/Appwrite/Utopia/Response/Model/TemplateVariable.php @@ -5,7 +5,7 @@ namespace Appwrite\Utopia\Response\Model; use Appwrite\Utopia\Response; use Appwrite\Utopia\Response\Model; -class VariableTemplate extends Model +class TemplateVariable extends Model { public function __construct() { @@ -50,7 +50,7 @@ class VariableTemplate extends Model */ public function getName(): string { - return 'Variable Template'; + return 'Template Variable'; } /** @@ -60,6 +60,6 @@ class VariableTemplate extends Model */ public function getType(): string { - return Response::MODEL_VARIABLE_TEMPLATE; + return Response::MODEL_TEMPLATE_VARIABLE; } } From af94fc7882d623505b3def58a15f537f9a1edbf8 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Fri, 26 Jul 2024 17:59:18 +0530 Subject: [PATCH 34/43] Add test for endpoint --- .../Functions/FunctionsCustomServerTest.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index c9f9e4443f..bed86a1d72 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -2097,4 +2097,18 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); } + + public function testGetFunctionTemplates() + { + $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ], $this->getHeaders())); + + $this->assertEquals(200, $templates['headers']['status-code']); + $this->assertGreaterThan(0, $templates['body']['total']); + $this->assertIsArray($templates['body']['templates']); + $this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]); + } } From 7dca530f7078e7ec37dce0ee5d6fd9adf85e307b Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Mon, 29 Jul 2024 16:02:49 +0530 Subject: [PATCH 35/43] Add pagination to templates --- app/controllers/api/functions.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index d294d5e4ee..995954397a 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -2368,9 +2368,11 @@ App::get('/v1/functions/templates') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TEMPLATE_FUNCTION_LIST) + ->param('limit', 10, new Range(0, 200), 'Limit the number of templates returned in the response. Max limit: 100.', true) + ->param('offset', 0, new Range(0, 200), 'Offset the list of returned templates. Max offset: 100.', true) ->inject('response') - ->action(function (Response $response) { - $templates = Config::getParam('function-templates', []); + ->action(function (int $limit, int $offset, Response $response) { + $templates = \array_slice(Config::getParam('function-templates', []), $offset, $limit); $response->dynamic(new Document([ 'templates' => $templates, 'total' => \count($templates), From 9f0f5657cb8ef0667bc39218665a4d13c50512c9 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Mon, 29 Jul 2024 17:55:58 +0530 Subject: [PATCH 36/43] Add filtering using usecase --- app/config/function-templates.php | 78 +++++++++++++++---------------- app/config/template-usecases.php | 10 ++++ app/controllers/api/functions.php | 23 ++++++--- app/init.php | 1 + 4 files changed, 66 insertions(+), 46 deletions(-) create mode 100644 app/config/template-usecases.php diff --git a/app/config/function-templates.php b/app/config/function-templates.php index 940229b83c..c8562648be 100644 --- a/app/config/function-templates.php +++ b/app/config/function-templates.php @@ -56,7 +56,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['Starter'], + 'usecases' => ['starter'], 'runtimes' => [ ...getRuntimes(TEMPLATE_RUNTIMES['NODE'], 'npm install', 'src/main.js', 'node/starter'), ...getRuntimes( @@ -93,7 +93,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['Databases'], + 'usecases' => ['databases'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -136,7 +136,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['Databases'], + 'usecases' => ['databases'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -178,7 +178,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['Databases'], + 'usecases' => ['databases'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -229,7 +229,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['Databases'], + 'usecases' => ['databases'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -265,7 +265,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['Databases'], + 'usecases' => ['databases'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -331,7 +331,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -391,7 +391,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['Messaging'], + 'usecases' => ['messaging'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -453,7 +453,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -488,7 +488,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -534,7 +534,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['Utilities'], + 'usecases' => ['utilities'], 'runtimes' => [ ...getRuntimes(TEMPLATE_RUNTIMES['NODE'], 'npm install', 'src/main.js', 'node/generate-pdf') ], @@ -555,7 +555,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['Dev Tools'], + 'usecases' => ['dev-tools'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -597,7 +597,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['Utilities'], + 'usecases' => ['utilities'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -648,7 +648,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['Databases'], + 'usecases' => ['databases'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -729,7 +729,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['Databases'], + 'usecases' => ['databases'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -822,7 +822,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['Messaging'], + 'usecases' => ['messaging'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -907,7 +907,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['Messaging'], + 'usecases' => ['messaging'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -962,7 +962,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['Utilities'], + 'usecases' => ['utilities'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1044,7 +1044,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['Utilities'], + 'usecases' => ['utilities'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1085,7 +1085,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['Utilities'], + 'usecases' => ['utilities'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1142,7 +1142,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 30, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1175,7 +1175,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 30, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1208,7 +1208,7 @@ return [ 'events' => ['buckets.*.files.*.create'], 'cron' => '', 'timeout' => 15, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1266,7 +1266,7 @@ return [ 'events' => ['buckets.*.files.*.create'], 'cron' => '', 'timeout' => 15, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1324,7 +1324,7 @@ return [ 'events' => ['buckets.*.files.*.create'], 'cron' => '', 'timeout' => 15, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1382,7 +1382,7 @@ return [ 'events' => ['databases.*.collections.*.documents.*.create'], 'cron' => '', 'timeout' => 15, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1440,7 +1440,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 300, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1474,7 +1474,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 300, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1516,7 +1516,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1556,7 +1556,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 300, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1590,7 +1590,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 30, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1653,7 +1653,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 30, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1716,7 +1716,7 @@ return [ 'cron' => '', 'events' => [], 'timeout' => 15, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1771,7 +1771,7 @@ return [ 'cron' => '', 'events' => [], 'timeout' => 15, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1812,7 +1812,7 @@ return [ 'cron' => '', 'events' => [], 'timeout' => 15, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1852,7 +1852,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1894,7 +1894,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 300, - 'usecases' => ['AI'], + 'usecases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1928,7 +1928,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['Utilities'], + 'usecases' => ['utilities'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1983,7 +1983,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['Utilities'], + 'usecases' => ['utilities'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], diff --git a/app/config/template-usecases.php b/app/config/template-usecases.php new file mode 100644 index 0000000000..bc918e8691 --- /dev/null +++ b/app/config/template-usecases.php @@ -0,0 +1,10 @@ +desc('Get function templates') ->groups(['api', 'functions']) - ->label('scope', 'functions.read') + ->label('scope', 'public') ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') ->label('sdk.method', 'getFunctionTemplates') @@ -2368,13 +2368,22 @@ App::get('/v1/functions/templates') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TEMPLATE_FUNCTION_LIST) - ->param('limit', 10, new Range(0, 200), 'Limit the number of templates returned in the response. Max limit: 100.', true) - ->param('offset', 0, new Range(0, 200), 'Offset the list of returned templates. Max offset: 100.', true) + ->param('usecases', [], new ArrayList(new WhiteList(Config::getParam('template-usecases')), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of usecases allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' usecases are allowed.', true) + ->param('limit', 100, new Range(0, 200), 'Limit the number of templates returned in the response. Max limit: 200.', true) + ->param('offset', 0, new Range(0, 200), 'Offset the list of returned templates. Max offset: 200.', true) ->inject('response') - ->action(function (int $limit, int $offset, Response $response) { - $templates = \array_slice(Config::getParam('function-templates', []), $offset, $limit); + ->action(function (array $usecases, int $limit, int $offset, Response $response) { + $templates = Config::getParam('function-templates', []); + + if (!empty($usecases)) { + $templates = \array_filter($templates, function ($template) use ($usecases) { + return \count(\array_intersect($usecases, $template['usecases'])) > 0; + }); + } + + $responseTemplates = \array_slice($templates, $offset, $limit); $response->dynamic(new Document([ - 'templates' => $templates, - 'total' => \count($templates), + 'templates' => $responseTemplates, + 'total' => \count($responseTemplates), ]), Response::MODEL_TEMPLATE_FUNCTION_LIST); }); diff --git a/app/init.php b/app/init.php index e7a07a40d0..acf30ac452 100644 --- a/app/init.php +++ b/app/init.php @@ -304,6 +304,7 @@ Config::load('storage-mimes', __DIR__ . '/config/storage/mimes.php'); Config::load('storage-inputs', __DIR__ . '/config/storage/inputs.php'); Config::load('storage-outputs', __DIR__ . '/config/storage/outputs.php'); Config::load('function-templates', __DIR__ . '/config/function-templates.php'); // List of function templates +Config::load('template-usecases', __DIR__ . '/config/template-usecases.php'); /** * New DB Filters From 6265a25e90a67af3aa473426c3a721cae7b2468c Mon Sep 17 00:00:00 2001 From: Binyamin Yawitz <316103+byawitz@users.noreply.github.com> Date: Mon, 29 Jul 2024 08:39:11 -0400 Subject: [PATCH 37/43] tests: Refactor deployment check in function tests --- .../e2e/Services/Functions/FunctionsBase.php | 25 +++ .../Functions/FunctionsCustomClientTest.php | 120 +--------- .../Functions/FunctionsCustomServerTest.php | 211 ++---------------- 3 files changed, 45 insertions(+), 311 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsBase.php b/tests/e2e/Services/Functions/FunctionsBase.php index c45ebbe068..abefb5c9a3 100644 --- a/tests/e2e/Services/Functions/FunctionsBase.php +++ b/tests/e2e/Services/Functions/FunctionsBase.php @@ -15,6 +15,31 @@ trait FunctionsBase Console::execute('cd ' . realpath(__DIR__ . "/../../../resources/functions") . "/$folder && tar --exclude code.tar.gz -czf code.tar.gz .", '', $this->stdout, $this->stderr); } + protected function awaitDeploymentIsBuilt($functionId, $deploymentId, $checkForSuccess = true): void + { + while (true) { + $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-key' => $this->getProject()['apiKey'], + ]); + + if ( + $deployment['headers']['status-code'] >= 400 + || \in_array($deployment['body']['status'], ['ready', 'failed']) + ) { + break; + } + + \sleep(1); + } + + if($checkForSuccess) { + $this->assertEquals(200, $deployment['headers']['status-code']); + $this->assertEquals('ready', $deployment['body']['status'], \json_encode($deployment['body'])); + } + } + // /** // * @depends testCreateTeam // */ diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 4e501692fa..8edd82b69a 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -120,25 +120,7 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); - // Poll until deployment is built - while (true) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; - } - - \sleep(1); - } - - $this->assertEquals('ready', $deployment['body']['status'], \json_encode($deployment['body'])); + $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId); $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ 'content-type' => 'application/json', @@ -239,25 +221,7 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); - // Poll until deployment is built - while (true) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; - } - - \sleep(1); - } - - $this->assertEquals('ready', $deployment['body']['status']); + $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId); $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ 'content-type' => 'application/json', @@ -418,25 +382,7 @@ class FunctionsCustomClientTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; - // Poll until deployment is built - while (true) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; - } - - \sleep(1); - } - - $this->assertEquals('ready', $deployment['body']['status'], \json_encode($deployment['body'])); + $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId); $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ 'content-type' => 'application/json', @@ -548,25 +494,7 @@ class FunctionsCustomClientTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; - // Poll until deployment is built - while (true) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; - } - - \sleep(1); - } - - $this->assertEquals('ready', $deployment['body']['status']); + $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId); // Why do we have to do this? $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ @@ -805,25 +733,7 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); - // Poll until deployment is built - while (true) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; - } - - \sleep(1); - } - - $this->assertEquals('ready', $deployment['body']['status']); + $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId); $function = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ 'content-type' => 'application/json', @@ -916,25 +826,7 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); - // Poll until deployment is built - while (true) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; - } - - \sleep(1); - } - - $this->assertEquals('ready', $deployment['body']['status']); + $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId); $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index c9f9e4443f..e3148752c8 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -426,25 +426,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); - // Poll until deployment is built - while (true) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; - } - - \sleep(1); - } - $this->assertEquals('ready', $deployment['body']['status'], \json_encode($deployment['body'])); - + $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId); $functionDetails = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ 'content-type' => 'application/json', @@ -483,26 +465,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(true, (new DatetimeValidator())->isValid($deployment['body']['$createdAt'])); $this->assertEquals('index.php', $deployment['body']['entrypoint']); - // Poll until deployment is built - while (true) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $data['functionId'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; - } - - \sleep(1); - } - - $this->assertEquals(200, $deployment['headers']['status-code']); - $this->assertEquals('ready', $deployment['body']['status']); + $this->awaitDeploymentIsBuilt($data['functionId'], $deploymentId); return array_merge($data, ['deploymentId' => $deploymentId]); } @@ -1167,25 +1130,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(202, $deployment['headers']['status-code']); - // Poll until deployment is built - while (true) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $functionId . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; - } - - \sleep(1); - } - - $this->assertEquals('ready', $deployment['body']['status']); + $this->awaitDeploymentIsBuilt($functionId, $deploymentId); $execution = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/executions', array_merge([ 'content-type' => 'application/json', @@ -1264,7 +1209,7 @@ class FunctionsCustomServerTest extends Scope * @param string $entrypoint * * @dataProvider provideCustomExecutions - * @depends testTimeout + * @depends testTimeout */ public function testCreateCustomExecution(string $folder, string $name, string $entrypoint, string $runtimeName, string $runtimeVersion) { @@ -1310,23 +1255,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - // Poll until deployment is built - while (true) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; - } - - \sleep(1); - } + $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -1435,23 +1364,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - // Poll until deployment is built - while (true) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; - } - - \sleep(1); - } + $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -1543,23 +1456,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - // Poll until deployment is built - while (true) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; - } - - \sleep(1); - } + $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -1654,23 +1551,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - // Poll until deployment is built - while (true) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; - } - - \sleep(1); - } + $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -1738,23 +1619,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - // Poll until deployment is built - while (true) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; - } - - \sleep(1); - } + $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -1845,23 +1710,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - // Poll until deployment is built - while (true) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; - } - - \sleep(1); - } + $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -1948,23 +1797,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - // Poll until deployment is built - while (true) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; - } - - \sleep(1); - } + $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -2050,23 +1883,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - // Poll until deployment is built - while (true) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; - } - - \sleep(1); - } + $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -2081,9 +1898,9 @@ class FunctionsCustomServerTest extends Scope $proxyClient = new Client(); $proxyClient->setEndpoint('http://' . $domain); - $bytes = pack('C*', ...[0,20,255]); + $bytes = pack('C*', ...[0, 20, 255]); - $response = $proxyClient->call(Client::METHOD_POST, '/', [ 'content-type' => 'text/plain' ], $bytes, false); + $response = $proxyClient->call(Client::METHOD_POST, '/', ['content-type' => 'text/plain'], $bytes, false); $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals(\md5($bytes), $response['body']); From 0369e8913fdbdaf6e82dbc4a7cd57a1c94699761 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Mon, 29 Jul 2024 18:59:16 +0530 Subject: [PATCH 38/43] Add filter by runtimes --- app/controllers/api/functions.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 7e3f6e1475..b5b7a2a4f4 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -2368,13 +2368,20 @@ App::get('/v1/functions/templates') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TEMPLATE_FUNCTION_LIST) + ->param('runtimes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('runtimes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of runtimes allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' runtimes are allowed.', true) ->param('usecases', [], new ArrayList(new WhiteList(Config::getParam('template-usecases')), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of usecases allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' usecases are allowed.', true) ->param('limit', 100, new Range(0, 200), 'Limit the number of templates returned in the response. Max limit: 200.', true) ->param('offset', 0, new Range(0, 200), 'Offset the list of returned templates. Max offset: 200.', true) ->inject('response') - ->action(function (array $usecases, int $limit, int $offset, Response $response) { + ->action(function (array $runtimes, array $usecases, int $limit, int $offset, Response $response) { $templates = Config::getParam('function-templates', []); + if (!empty($runtimes)) { + $templates = \array_filter($templates, function ($template) use ($runtimes) { + return \count(\array_intersect($runtimes, \array_column($template['runtimes'], 'name'))) > 0; + }); + } + if (!empty($usecases)) { $templates = \array_filter($templates, function ($template) use ($usecases) { return \count(\array_intersect($usecases, $template['usecases'])) > 0; From df9f4ffb040cff4122de358a0bf72a2cee7ab675 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Mon, 29 Jul 2024 19:09:48 +0530 Subject: [PATCH 39/43] Add tests for filtering and pagination --- .../Functions/FunctionsCustomClientTest.php | 39 +++++++++++++++++++ .../Functions/FunctionsCustomServerTest.php | 14 ------- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 4e501692fa..4be2d1d893 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -963,4 +963,43 @@ class FunctionsCustomClientTest extends Scope return []; } + + public function testGetFunctionTemplates() + { + $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders())); + + $this->assertEquals(200, $templates['headers']['status-code']); + $this->assertGreaterThan(0, $templates['body']['total']); + $this->assertIsArray($templates['body']['templates']); + $this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]); + + $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'usecases' => ['starter', 'ai'], + 'runtimes' => ['bun-1.0', 'dart-2.16'] + ]); + + $this->assertEquals(200, $templates['headers']['status-code']); + $this->assertEquals(3, $templates['body']['total']); + $this->assertIsArray($templates['body']['templates']); + $this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]); + + $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'limit' => 10, + 'offset' => 2, + ]); + + $this->assertEquals(200, $templates['headers']['status-code']); + $this->assertEquals(10, $templates['body']['total']); + $this->assertIsArray($templates['body']['templates']); + $this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]); + } } diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index bed86a1d72..c9f9e4443f 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -2097,18 +2097,4 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); } - - public function testGetFunctionTemplates() - { - $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ], $this->getHeaders())); - - $this->assertEquals(200, $templates['headers']['status-code']); - $this->assertGreaterThan(0, $templates['body']['total']); - $this->assertIsArray($templates['body']['templates']); - $this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]); - } } From 198b460e227c195986d42c8ccd2351a128f9bd62 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Tue, 30 Jul 2024 02:25:18 +0530 Subject: [PATCH 40/43] Improve tests --- app/config/template-usecases.php | 10 ---------- app/controllers/api/functions.php | 14 ++++++-------- app/init.php | 3 +-- .../references/functions/get-function-templates.md | 1 - .../functions/list-function-templates.md | 1 + .../Functions/FunctionsCustomClientTest.php | 13 +++++++++++-- 6 files changed, 19 insertions(+), 23 deletions(-) delete mode 100644 app/config/template-usecases.php delete mode 100644 docs/references/functions/get-function-templates.md create mode 100644 docs/references/functions/list-function-templates.md diff --git a/app/config/template-usecases.php b/app/config/template-usecases.php deleted file mode 100644 index bc918e8691..0000000000 --- a/app/config/template-usecases.php +++ /dev/null @@ -1,10 +0,0 @@ -desc('Get function templates') - ->groups(['api', 'functions']) + ->desc('List function templates') ->label('scope', 'public') - ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) ->label('sdk.namespace', 'functions') - ->label('sdk.method', 'getFunctionTemplates') - ->label('sdk.description', '/docs/references/functions/get-function-templates.md') + ->label('sdk.method', 'listFunctionTemplates') + ->label('sdk.description', '/docs/references/functions/list-function-templates.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TEMPLATE_FUNCTION_LIST) ->param('runtimes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('runtimes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of runtimes allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' runtimes are allowed.', true) - ->param('usecases', [], new ArrayList(new WhiteList(Config::getParam('template-usecases')), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of usecases allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' usecases are allowed.', true) - ->param('limit', 100, new Range(0, 200), 'Limit the number of templates returned in the response. Max limit: 200.', true) - ->param('offset', 0, new Range(0, 200), 'Offset the list of returned templates. Max offset: 200.', true) + ->param('usecases', [], new ArrayList(new WhiteList(['dev-tools','starter','databases','ai','messaging','utilities']), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of usecases allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' usecases are allowed.', true) + ->param('limit', 100, new Range(0, 100), 'Limit the number of templates returned in the response. Max limit: 100.', true) + ->param('offset', 0, new Range(0, 100), 'Offset the list of returned templates. Max offset: 100.', true) ->inject('response') ->action(function (array $runtimes, array $usecases, int $limit, int $offset, Response $response) { $templates = Config::getParam('function-templates', []); diff --git a/app/init.php b/app/init.php index acf30ac452..5ecd78cf00 100644 --- a/app/init.php +++ b/app/init.php @@ -303,8 +303,7 @@ Config::load('storage-logos', __DIR__ . '/config/storage/logos.php'); Config::load('storage-mimes', __DIR__ . '/config/storage/mimes.php'); Config::load('storage-inputs', __DIR__ . '/config/storage/inputs.php'); Config::load('storage-outputs', __DIR__ . '/config/storage/outputs.php'); -Config::load('function-templates', __DIR__ . '/config/function-templates.php'); // List of function templates -Config::load('template-usecases', __DIR__ . '/config/template-usecases.php'); +Config::load('function-templates', __DIR__ . '/config/function-templates.php'); /** * New DB Filters diff --git a/docs/references/functions/get-function-templates.md b/docs/references/functions/get-function-templates.md deleted file mode 100644 index 8edc21e996..0000000000 --- a/docs/references/functions/get-function-templates.md +++ /dev/null @@ -1 +0,0 @@ -Get function templates from marketplace. \ No newline at end of file diff --git a/docs/references/functions/list-function-templates.md b/docs/references/functions/list-function-templates.md new file mode 100644 index 0000000000..b6195b43c3 --- /dev/null +++ b/docs/references/functions/list-function-templates.md @@ -0,0 +1 @@ +List function templates from marketplace. \ No newline at end of file diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 4be2d1d893..87638f1b11 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -988,18 +988,27 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(3, $templates['body']['total']); $this->assertIsArray($templates['body']['templates']); $this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]); + $this->assertEquals('starter', $templates['body']['templates'][0]['usecases'][0]); + $this->assertEquals('ai', $templates['body']['templates'][1]['usecases'][0]); + $this->assertEquals('ai', $templates['body']['templates'][2]['usecases'][0]); + $this->assertContains('bun-1.0', array_column($templates['body']['templates'][0]['runtimes'], 'name')); + $this->assertContains('dart-2.16', array_column($templates['body']['templates'][1]['runtimes'], 'name')); $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'limit' => 10, + 'limit' => 5, 'offset' => 2, + 'usecases' => ['databases'], + 'runtimes' => ['node-16.0'] ]); $this->assertEquals(200, $templates['headers']['status-code']); - $this->assertEquals(10, $templates['body']['total']); + $this->assertEquals(5, $templates['body']['total']); $this->assertIsArray($templates['body']['templates']); $this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]); + $this->assertEquals('databases', $templates['body']['templates'][0]['usecases'][0]); + $this->assertContains('node-16.0', array_column($templates['body']['templates'][0]['runtimes'], 'name')); } } From c9f8671af16add468bcacfb4978a65b55ba5583e Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Tue, 30 Jul 2024 15:13:38 +0530 Subject: [PATCH 41/43] Small fixes --- app/controllers/api/functions.php | 10 +++++----- docs/references/functions/list-function-templates.md | 1 - docs/references/functions/list-templates.md | 1 + .../Utopia/Response/Model/TemplateFunction.php | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) delete mode 100644 docs/references/functions/list-function-templates.md create mode 100644 docs/references/functions/list-templates.md diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 79134eebbb..88e919730e 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -2361,15 +2361,15 @@ App::get('/v1/functions/templates') ->desc('List function templates') ->label('scope', 'public') ->label('sdk.namespace', 'functions') - ->label('sdk.method', 'listFunctionTemplates') - ->label('sdk.description', '/docs/references/functions/list-function-templates.md') + ->label('sdk.method', 'listTemplates') + ->label('sdk.description', '/docs/references/functions/list-templates.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TEMPLATE_FUNCTION_LIST) ->param('runtimes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('runtimes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of runtimes allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' runtimes are allowed.', true) - ->param('usecases', [], new ArrayList(new WhiteList(['dev-tools','starter','databases','ai','messaging','utilities']), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of usecases allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' usecases are allowed.', true) - ->param('limit', 100, new Range(0, 100), 'Limit the number of templates returned in the response. Max limit: 100.', true) - ->param('offset', 0, new Range(0, 100), 'Offset the list of returned templates. Max offset: 100.', true) + ->param('useCases', [], new ArrayList(new WhiteList(['dev-tools','starter','databases','ai','messaging','utilities']), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of usecases allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' usecases are allowed.', true) + ->param('limit', 25, new Range(1, 5000), 'Limit the number of templates returned in the response. Default limit is 25, and maximum limit is 5000.', true) + ->param('offset', 0, new Range(0, 5000), 'Offset the list of returned templates. Maximum offset is 5000.', true) ->inject('response') ->action(function (array $runtimes, array $usecases, int $limit, int $offset, Response $response) { $templates = Config::getParam('function-templates', []); diff --git a/docs/references/functions/list-function-templates.md b/docs/references/functions/list-function-templates.md deleted file mode 100644 index b6195b43c3..0000000000 --- a/docs/references/functions/list-function-templates.md +++ /dev/null @@ -1 +0,0 @@ -List function templates from marketplace. \ No newline at end of file diff --git a/docs/references/functions/list-templates.md b/docs/references/functions/list-templates.md new file mode 100644 index 0000000000..ed43b9cbf4 --- /dev/null +++ b/docs/references/functions/list-templates.md @@ -0,0 +1 @@ +List available function templates. You can use template details in [createFunction](/docs/references/cloud/server-nodejs/functions#create) method. \ No newline at end of file diff --git a/src/Appwrite/Utopia/Response/Model/TemplateFunction.php b/src/Appwrite/Utopia/Response/Model/TemplateFunction.php index 5fdbd95a35..7a3a4cfbd5 100644 --- a/src/Appwrite/Utopia/Response/Model/TemplateFunction.php +++ b/src/Appwrite/Utopia/Response/Model/TemplateFunction.php @@ -16,7 +16,7 @@ class TemplateFunction extends Model 'default' => '', 'example' => 'icon-lightning-bolt', ]) - ->addRule('$id', [ + ->addRule('id', [ 'type' => self::TYPE_STRING, 'description' => 'Function Template ID.', 'default' => '', @@ -60,9 +60,9 @@ class TemplateFunction extends Model 'default' => 15, 'example' => 300, ]) - ->addRule('usecases', [ + ->addRule('useCases', [ 'type' => self::TYPE_STRING, - 'description' => 'Function usecases.', + 'description' => 'Function use cases.', 'default' => [], 'example' => 'Starter', 'array' => true, From 939adfd09bff8992e46ff80b900792eb761dcacb Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Tue, 30 Jul 2024 16:11:04 +0530 Subject: [PATCH 42/43] Improve tests --- app/config/function-templates.php | 78 +++++++++---------- app/controllers/api/functions.php | 4 +- .../Functions/FunctionsCustomClientTest.php | 71 +++++++++++++++-- 3 files changed, 104 insertions(+), 49 deletions(-) diff --git a/app/config/function-templates.php b/app/config/function-templates.php index c8562648be..dddd8596f4 100644 --- a/app/config/function-templates.php +++ b/app/config/function-templates.php @@ -56,7 +56,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['starter'], + 'useCases' => ['starter'], 'runtimes' => [ ...getRuntimes(TEMPLATE_RUNTIMES['NODE'], 'npm install', 'src/main.js', 'node/starter'), ...getRuntimes( @@ -93,7 +93,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['databases'], + 'useCases' => ['databases'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -136,7 +136,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['databases'], + 'useCases' => ['databases'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -178,7 +178,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['databases'], + 'useCases' => ['databases'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -229,7 +229,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['databases'], + 'useCases' => ['databases'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -265,7 +265,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['databases'], + 'useCases' => ['databases'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -331,7 +331,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -391,7 +391,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['messaging'], + 'useCases' => ['messaging'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -453,7 +453,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -488,7 +488,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -534,7 +534,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['utilities'], + 'useCases' => ['utilities'], 'runtimes' => [ ...getRuntimes(TEMPLATE_RUNTIMES['NODE'], 'npm install', 'src/main.js', 'node/generate-pdf') ], @@ -555,7 +555,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['dev-tools'], + 'useCases' => ['dev-tools'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -597,7 +597,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['utilities'], + 'useCases' => ['utilities'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -648,7 +648,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['databases'], + 'useCases' => ['databases'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -729,7 +729,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['databases'], + 'useCases' => ['databases'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -822,7 +822,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['messaging'], + 'useCases' => ['messaging'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -907,7 +907,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['messaging'], + 'useCases' => ['messaging'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -962,7 +962,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['utilities'], + 'useCases' => ['utilities'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1044,7 +1044,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['utilities'], + 'useCases' => ['utilities'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1085,7 +1085,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['utilities'], + 'useCases' => ['utilities'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1142,7 +1142,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 30, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1175,7 +1175,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 30, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1208,7 +1208,7 @@ return [ 'events' => ['buckets.*.files.*.create'], 'cron' => '', 'timeout' => 15, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1266,7 +1266,7 @@ return [ 'events' => ['buckets.*.files.*.create'], 'cron' => '', 'timeout' => 15, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1324,7 +1324,7 @@ return [ 'events' => ['buckets.*.files.*.create'], 'cron' => '', 'timeout' => 15, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1382,7 +1382,7 @@ return [ 'events' => ['databases.*.collections.*.documents.*.create'], 'cron' => '', 'timeout' => 15, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1440,7 +1440,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 300, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1474,7 +1474,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 300, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1516,7 +1516,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1556,7 +1556,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 300, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1590,7 +1590,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 30, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1653,7 +1653,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 30, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1716,7 +1716,7 @@ return [ 'cron' => '', 'events' => [], 'timeout' => 15, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1771,7 +1771,7 @@ return [ 'cron' => '', 'events' => [], 'timeout' => 15, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1812,7 +1812,7 @@ return [ 'cron' => '', 'events' => [], 'timeout' => 15, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1852,7 +1852,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1894,7 +1894,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 300, - 'usecases' => ['ai'], + 'useCases' => ['ai'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1928,7 +1928,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['utilities'], + 'useCases' => ['utilities'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], @@ -1983,7 +1983,7 @@ return [ 'events' => [], 'cron' => '', 'timeout' => 15, - 'usecases' => ['utilities'], + 'useCases' => ['utilities'], 'runtimes' => [ ...getRuntimes( TEMPLATE_RUNTIMES['NODE'], diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 88e919730e..558b123f65 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -2367,7 +2367,7 @@ App::get('/v1/functions/templates') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TEMPLATE_FUNCTION_LIST) ->param('runtimes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('runtimes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of runtimes allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' runtimes are allowed.', true) - ->param('useCases', [], new ArrayList(new WhiteList(['dev-tools','starter','databases','ai','messaging','utilities']), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of usecases allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' usecases are allowed.', true) + ->param('useCases', [], new ArrayList(new WhiteList(['dev-tools','starter','databases','ai','messaging','utilities']), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of use cases allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' use cases are allowed.', true) ->param('limit', 25, new Range(1, 5000), 'Limit the number of templates returned in the response. Default limit is 25, and maximum limit is 5000.', true) ->param('offset', 0, new Range(0, 5000), 'Offset the list of returned templates. Maximum offset is 5000.', true) ->inject('response') @@ -2382,7 +2382,7 @@ App::get('/v1/functions/templates') if (!empty($usecases)) { $templates = \array_filter($templates, function ($template) use ($usecases) { - return \count(\array_intersect($usecases, $template['usecases'])) > 0; + return \count(\array_intersect($usecases, $template['useCases'])) > 0; }); } diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 87638f1b11..61c8afd9bd 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -8,6 +8,7 @@ use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\SideClient; +use Utopia\Config\Config; use Utopia\Database\DateTime; use Utopia\Database\Document; use Utopia\Database\Helpers\ID; @@ -966,6 +967,10 @@ class FunctionsCustomClientTest extends Scope public function testGetFunctionTemplates() { + /** + * Test for SUCCESS + */ + $expectedTemplates = array_slice(Config::getParam('function-templates', []), 0, 25); $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], @@ -975,24 +980,47 @@ class FunctionsCustomClientTest extends Scope $this->assertGreaterThan(0, $templates['body']['total']); $this->assertIsArray($templates['body']['templates']); $this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]); + $this->assertArrayHasKey('useCases', $templates['body']['templates'][0]); + for ($i = 0; $i < 25; $i++) { + $this->assertEquals($expectedTemplates[$i]['name'], $templates['body']['templates'][$i]['name']); + $this->assertEquals($expectedTemplates[$i]['id'], $templates['body']['templates'][$i]['id']); + $this->assertEquals($expectedTemplates[$i]['icon'], $templates['body']['templates'][$i]['icon']); + $this->assertEquals($expectedTemplates[$i]['tagline'], $templates['body']['templates'][$i]['tagline']); + $this->assertEquals($expectedTemplates[$i]['useCases'], $templates['body']['templates'][$i]['useCases']); + $this->assertEquals($expectedTemplates[$i]['vcsProvider'], $templates['body']['templates'][$i]['vcsProvider']); + $this->assertEquals($expectedTemplates[$i]['runtimes'], $templates['body']['templates'][$i]['runtimes']); + $this->assertEquals($expectedTemplates[$i]['variables'], $templates['body']['templates'][$i]['variables']); + } + + $templates_offset = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'limit' => 1, + 'offset' => 2 + ]); + + $this->assertEquals(200, $templates_offset['headers']['status-code']); + $this->assertEquals(1, $templates_offset['body']['total']); + // assert that offset works as expected + $this->assertEquals($templates['body']['templates'][2]['id'], $templates_offset['body']['templates'][0]['id']); $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ - 'usecases' => ['starter', 'ai'], + 'useCases' => ['starter', 'ai'], 'runtimes' => ['bun-1.0', 'dart-2.16'] ]); $this->assertEquals(200, $templates['headers']['status-code']); - $this->assertEquals(3, $templates['body']['total']); + $this->assertGreaterThanOrEqual(3, $templates['body']['total']); $this->assertIsArray($templates['body']['templates']); + foreach ($templates['body']['templates'] as $template) { + $this->assertContains($template['useCases'][0], ['starter', 'ai']); + } $this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]); - $this->assertEquals('starter', $templates['body']['templates'][0]['usecases'][0]); - $this->assertEquals('ai', $templates['body']['templates'][1]['usecases'][0]); - $this->assertEquals('ai', $templates['body']['templates'][2]['usecases'][0]); $this->assertContains('bun-1.0', array_column($templates['body']['templates'][0]['runtimes'], 'name')); - $this->assertContains('dart-2.16', array_column($templates['body']['templates'][1]['runtimes'], 'name')); $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', @@ -1000,7 +1028,7 @@ class FunctionsCustomClientTest extends Scope ], $this->getHeaders()), [ 'limit' => 5, 'offset' => 2, - 'usecases' => ['databases'], + 'useCases' => ['databases'], 'runtimes' => ['node-16.0'] ]); @@ -1008,7 +1036,34 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(5, $templates['body']['total']); $this->assertIsArray($templates['body']['templates']); $this->assertArrayHasKey('runtimes', $templates['body']['templates'][0]); - $this->assertEquals('databases', $templates['body']['templates'][0]['usecases'][0]); + foreach ($templates['body']['templates'] as $template) { + $this->assertContains($template['useCases'][0], ['databases']); + } $this->assertContains('node-16.0', array_column($templates['body']['templates'][0]['runtimes'], 'name')); + + /** + * Test for FAILURE + */ + $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'limit' => 5001, + 'offset' => 10, + ]); + + $this->assertEquals(400, $templates['headers']['status-code']); + $this->assertEquals('Invalid `limit` param: Value must be a valid range between 1 and 5,000', $templates['body']['message']); + + $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'limit' => 5, + 'offset' => 5001, + ]); + + $this->assertEquals(400, $templates['headers']['status-code']); + $this->assertEquals('Invalid `offset` param: Value must be a valid range between 0 and 5,000', $templates['body']['message']); } } From 395f43171890675ceed593738d9d3177d0819028 Mon Sep 17 00:00:00 2001 From: Khushboo Verma <43381712+vermakhushboo@users.noreply.github.com> Date: Tue, 30 Jul 2024 17:16:45 +0530 Subject: [PATCH 43/43] Add endpoint to get template by ID --- app/config/errors.php | 5 +++ app/controllers/api/functions.php | 25 +++++++++++++ docs/references/functions/get-template.md | 1 + src/Appwrite/Extend/Exception.php | 1 + .../Functions/FunctionsCustomClientTest.php | 35 ++++++++++++++++--- 5 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 docs/references/functions/get-template.md diff --git a/app/config/errors.php b/app/config/errors.php index 6337c3205a..dc6dcd5daf 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -529,6 +529,11 @@ return [ 'description' => 'Synchronous function execution timed out. Use asynchronous execution instead, or ensure the execution duration doesn\'t exceed 30 seconds.', 'code' => 408, ], + Exception::FUNCTION_TEMPLATE_NOT_FOUND => [ + 'name' => Exception::FUNCTION_TEMPLATE_NOT_FOUND, + 'description' => 'Function Template with the requested ID could not be found.', + 'code' => 404, + ], /** Builds */ Exception::BUILD_NOT_FOUND => [ diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 558b123f65..a61db99bdf 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -2392,3 +2392,28 @@ App::get('/v1/functions/templates') 'total' => \count($responseTemplates), ]), Response::MODEL_TEMPLATE_FUNCTION_LIST); }); + +App::get('/v1/functions/templates/:templateId') + ->desc('Get function template') + ->label('scope', 'public') + ->label('sdk.namespace', 'functions') + ->label('sdk.method', 'getTemplate') + ->label('sdk.description', '/docs/references/functions/get-template.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_TEMPLATE_FUNCTION) + ->param('templateId', '', new Text(128), 'Template ID.') + ->inject('response') + ->action(function (string $templateId, Response $response) { + $templates = Config::getParam('function-templates', []); + + $template = array_shift(\array_filter($templates, function ($template) use ($templateId) { + return $template['id'] === $templateId; + })); + + if (empty($template)) { + throw new Exception(Exception::FUNCTION_TEMPLATE_NOT_FOUND); + } + + $response->dynamic(new Document($template), Response::MODEL_TEMPLATE_FUNCTION); + }); diff --git a/docs/references/functions/get-template.md b/docs/references/functions/get-template.md new file mode 100644 index 0000000000..ccdcce7352 --- /dev/null +++ b/docs/references/functions/get-template.md @@ -0,0 +1 @@ +Get a function template using ID. You can use template details in [createFunction](/docs/references/cloud/server-nodejs/functions#create) method. \ No newline at end of file diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index dbc7d9425e..884296ff67 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -156,6 +156,7 @@ class Exception extends \Exception public const FUNCTION_RUNTIME_UNSUPPORTED = 'function_runtime_unsupported'; public const FUNCTION_ENTRYPOINT_MISSING = 'function_entrypoint_missing'; public const FUNCTION_SYNCHRONOUS_TIMEOUT = 'function_synchronous_timeout'; + public const FUNCTION_TEMPLATE_NOT_FOUND = 'function_template_not_found'; /** Deployments */ public const DEPLOYMENT_NOT_FOUND = 'deployment_not_found'; diff --git a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php index 61c8afd9bd..f94cb3744a 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomClientTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomClientTest.php @@ -965,7 +965,7 @@ class FunctionsCustomClientTest extends Scope return []; } - public function testGetFunctionTemplates() + public function testListTemplates() { /** * Test for SUCCESS @@ -973,7 +973,6 @@ class FunctionsCustomClientTest extends Scope $expectedTemplates = array_slice(Config::getParam('function-templates', []), 0, 25); $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders())); $this->assertEquals(200, $templates['headers']['status-code']); @@ -994,7 +993,6 @@ class FunctionsCustomClientTest extends Scope $templates_offset = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'limit' => 1, 'offset' => 2 @@ -1007,7 +1005,6 @@ class FunctionsCustomClientTest extends Scope $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'useCases' => ['starter', 'ai'], 'runtimes' => ['bun-1.0', 'dart-2.16'] @@ -1046,7 +1043,6 @@ class FunctionsCustomClientTest extends Scope */ $templates = $this->client->call(Client::METHOD_GET, '/functions/templates', array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'limit' => 5001, 'offset' => 10, @@ -1066,4 +1062,33 @@ class FunctionsCustomClientTest extends Scope $this->assertEquals(400, $templates['headers']['status-code']); $this->assertEquals('Invalid `offset` param: Value must be a valid range between 0 and 5,000', $templates['body']['message']); } + + public function testGetTemplate() + { + /** + * Test for SUCCESS + */ + $template = $this->client->call(Client::METHOD_GET, '/functions/templates/query-neo4j-auradb', array_merge([ + 'content-type' => 'application/json', + ], $this->getHeaders()), []); + + $this->assertEquals(200, $template['headers']['status-code']); + $this->assertIsArray($template['body']); + $this->assertEquals('query-neo4j-auradb', $template['body']['id']); + $this->assertEquals('Query Neo4j AuraDB', $template['body']['name']); + $this->assertEquals('icon-neo4j', $template['body']['icon']); + $this->assertEquals('Graph database with focus on relations between data.', $template['body']['tagline']); + $this->assertEquals(['databases'], $template['body']['useCases']); + $this->assertEquals('github', $template['body']['vcsProvider']); + + /** + * Test for FAILURE + */ + $template = $this->client->call(Client::METHOD_GET, '/functions/templates/invalid-template-id', array_merge([ + 'content-type' => 'application/json', + ], $this->getHeaders()), []); + + $this->assertEquals(404, $template['headers']['status-code']); + $this->assertEquals('Function Template with the requested ID could not be found.', $template['body']['message']); + } }