From 7440213a958a6db243683f9545f68ec40e1a3a7f Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 30 Jun 2020 00:39:16 +0300 Subject: [PATCH 01/17] Updated deps --- composer.json | 4 ++-- composer.lock | 30 +++++++++++++++--------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/composer.json b/composer.json index 62ef9fea9f..b0eec296e0 100644 --- a/composer.json +++ b/composer.json @@ -32,13 +32,13 @@ "appwrite/php-clamav": "1.0.*", - "utopia-php/framework": "0.6.0", + "utopia-php/framework": "0.7.1", "utopia-php/abuse": "0.2.*", "utopia-php/audit": "0.3.*", "utopia-php/cache": "0.2.*", "utopia-php/cli": "0.6.2", "utopia-php/config": "0.2.*", - "utopia-php/locale": "0.2.*", + "utopia-php/locale": "0.3.*", "utopia-php/registry": "0.2.*", "utopia-php/domains": "0.2.*", diff --git a/composer.lock b/composer.lock index c871ced2d1..2e2ad49cf8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "54f5e7c2291eb22ff442d2c491fa4eca", + "content-hash": "b8ee06f97c395bc83a05f92939679724", "packages": [ { "name": "appwrite/php-clamav", @@ -1596,16 +1596,16 @@ }, { "name": "utopia-php/framework", - "version": "0.6.0", + "version": "0.7.1", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "5412a080f6fdf99310f20a803a797ae97de8b539" + "reference": "3810789c1caf16a9ad7811fd38067a35249e75f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/5412a080f6fdf99310f20a803a797ae97de8b539", - "reference": "5412a080f6fdf99310f20a803a797ae97de8b539", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/3810789c1caf16a9ad7811fd38067a35249e75f8", + "reference": "3810789c1caf16a9ad7811fd38067a35249e75f8", "shasum": "" }, "require": { @@ -1636,20 +1636,20 @@ "php", "upf" ], - "time": "2020-06-28T16:54:35+00:00" + "time": "2020-06-29T16:02:35+00:00" }, { "name": "utopia-php/locale", - "version": "0.2.1", + "version": "0.3.0", "source": { "type": "git", "url": "https://github.com/utopia-php/locale.git", - "reference": "f2ed7f0b50fe961d65600871e8f8d9dea3167500" + "reference": "32c32a3bf5c295f3de93569cead7f412fa29ad13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/locale/zipball/f2ed7f0b50fe961d65600871e8f8d9dea3167500", - "reference": "f2ed7f0b50fe961d65600871e8f8d9dea3167500", + "url": "https://api.github.com/repos/utopia-php/locale/zipball/32c32a3bf5c295f3de93569cead7f412fa29ad13", + "reference": "32c32a3bf5c295f3de93569cead7f412fa29ad13", "shasum": "" }, "require": { @@ -1682,7 +1682,7 @@ "upf", "utopia" ], - "time": "2020-06-20T11:41:46+00:00" + "time": "2020-06-29T12:39:35+00:00" }, { "name": "utopia-php/registry", @@ -1942,12 +1942,12 @@ "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "5796d127b0c4ff505b77455148ea9d5269d99758" + "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5796d127b0c4ff505b77455148ea9d5269d99758", - "reference": "5796d127b0c4ff505b77455148ea9d5269d99758", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", + "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5", "shasum": "" }, "require": { @@ -1982,7 +1982,7 @@ "object", "object graph" ], - "time": "2020-06-28T07:02:41+00:00" + "time": "2020-06-29T13:22:24+00:00" }, { "name": "phar-io/manifest", From 6c80bf3a42355753cdba61517dbacf74a92dcd03 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 30 Jun 2020 00:39:34 +0300 Subject: [PATCH 02/17] Updated deps --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 0ec0701b70..2a8265d00d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -50,7 +50,7 @@ services: - ./phpunit.xml:/usr/share/nginx/html/phpunit.xml - ./tests:/usr/share/nginx/html/tests - ./app:/usr/share/nginx/html/app - # - ./vendor:/usr/share/nginx/html/vendor + - ./vendor:/usr/share/nginx/html/vendor - ./docs:/usr/share/nginx/html/docs - ./public:/usr/share/nginx/html/public - ./src:/usr/share/nginx/html/src From ce82bb872eb8c9ef59efbfd7a0b987f50bff310e Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 30 Jun 2020 00:42:03 +0300 Subject: [PATCH 03/17] Added storage config --- app/config/storage/logos.php | 43 +++++++++++++++ app/config/{files => storage/logos}/excel.png | Bin app/config/{files => storage/logos}/none.png | Bin app/config/{files => storage/logos}/pdf.png | Bin app/config/{files => storage/logos}/ppt.png | Bin app/config/{files => storage/logos}/video.png | Bin app/config/{files => storage/logos}/word.png | Bin app/config/storage/mimes.php | 50 ++++++++++++++++++ 8 files changed, 93 insertions(+) create mode 100644 app/config/storage/logos.php rename app/config/{files => storage/logos}/excel.png (100%) rename app/config/{files => storage/logos}/none.png (100%) rename app/config/{files => storage/logos}/pdf.png (100%) rename app/config/{files => storage/logos}/ppt.png (100%) rename app/config/{files => storage/logos}/video.png (100%) rename app/config/{files => storage/logos}/word.png (100%) create mode 100644 app/config/storage/mimes.php diff --git a/app/config/storage/logos.php b/app/config/storage/logos.php new file mode 100644 index 0000000000..3170762dca --- /dev/null +++ b/app/config/storage/logos.php @@ -0,0 +1,43 @@ + __DIR__.'/logos/none.png', + + // Video Files + 'video/mp4' => __DIR__.'/logos/video.png', + 'video/x-flv' => __DIR__.'/logos/video.png', + 'application/x-mpegURL' => __DIR__.'/logos/video.png', + 'video/MP2T' => __DIR__.'/logos/video.png', + 'video/3gpp' => __DIR__.'/logos/video.png', + 'video/quicktime' => __DIR__.'/logos/video.png', + 'video/x-msvideo' => __DIR__.'/logos/video.png', + 'video/x-ms-wmv' => __DIR__.'/logos/video.png', + + // // Microsoft Word + 'application/msword' => __DIR__.'/logos/word.png', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => __DIR__.'/logos/word.png', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => __DIR__.'/logos/word.png', + 'application/vnd.ms-word.document.macroEnabled.12' => __DIR__.'/logos/word.png', + + // // Microsoft Excel + 'application/vnd.ms-excel' => __DIR__.'/logos/excel.png', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => __DIR__.'/logos/excel.png', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => __DIR__.'/logos/excel.png', + 'application/vnd.ms-excel.sheet.macroEnabled.12' => __DIR__.'/logos/excel.png', + 'application/vnd.ms-excel.template.macroEnabled.12' => __DIR__.'/logos/excel.png', + 'application/vnd.ms-excel.addin.macroEnabled.12' => __DIR__.'/logos/excel.png', + 'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => __DIR__.'/logos/excel.png', + + // // Microsoft Power Point + 'application/vnd.ms-powerpoint' => __DIR__.'/logos/ppt.png', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => __DIR__.'/logos/ppt.png', + 'application/vnd.openxmlformats-officedocument.presentationml.template' => __DIR__.'/logos/ppt.png', + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => __DIR__.'/logos/ppt.png', + 'application/vnd.ms-powerpoint.addin.macroEnabled.12' => __DIR__.'/logos/ppt.png', + 'application/vnd.ms-powerpoint.presentation.macroEnabled.12' => __DIR__.'/logos/ppt.png', + 'application/vnd.ms-powerpoint.template.macroEnabled.12' => __DIR__.'/logos/ppt.png', + 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12' => __DIR__.'/logos/ppt.png', + + // Adobe PDF + 'application/pdf' => __DIR__.'/logos/pdf.png', +]; \ No newline at end of file diff --git a/app/config/files/excel.png b/app/config/storage/logos/excel.png similarity index 100% rename from app/config/files/excel.png rename to app/config/storage/logos/excel.png diff --git a/app/config/files/none.png b/app/config/storage/logos/none.png similarity index 100% rename from app/config/files/none.png rename to app/config/storage/logos/none.png diff --git a/app/config/files/pdf.png b/app/config/storage/logos/pdf.png similarity index 100% rename from app/config/files/pdf.png rename to app/config/storage/logos/pdf.png diff --git a/app/config/files/ppt.png b/app/config/storage/logos/ppt.png similarity index 100% rename from app/config/files/ppt.png rename to app/config/storage/logos/ppt.png diff --git a/app/config/files/video.png b/app/config/storage/logos/video.png similarity index 100% rename from app/config/files/video.png rename to app/config/storage/logos/video.png diff --git a/app/config/files/word.png b/app/config/storage/logos/word.png similarity index 100% rename from app/config/files/word.png rename to app/config/storage/logos/word.png diff --git a/app/config/storage/mimes.php b/app/config/storage/mimes.php new file mode 100644 index 0000000000..242a990b8d --- /dev/null +++ b/app/config/storage/mimes.php @@ -0,0 +1,50 @@ + Date: Tue, 30 Jun 2020 00:42:21 +0300 Subject: [PATCH 04/17] Removed env config --- app/workers/certificates.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/workers/certificates.php b/app/workers/certificates.php index 617cef3a98..ed704e0c6f 100644 --- a/app/workers/certificates.php +++ b/app/workers/certificates.php @@ -104,7 +104,7 @@ class CertificatesV1 throw new Exception('Renew isn\'t required'); } - $staging = (Config::getParam('env') === App::MODE_TYPE_PRODUCTION) ? '' : ' --dry-run'; + $staging = (App::isProduction()) ? '' : ' --dry-run'; $response = \shell_exec("certbot certonly --webroot --noninteractive --agree-tos{$staging} \ --email ".App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', 'security@localhost.test')." \ From 3e808d3bf49e4a54c3adcf6adb69bb94222a2658 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 30 Jun 2020 00:43:34 +0300 Subject: [PATCH 05/17] Removed global vars --- app/app.php | 378 +++--- app/controllers/api/account.php | 1980 +++++++++++++++--------------- app/controllers/api/avatars.php | 579 ++++----- app/controllers/api/database.php | 899 +++++++------- app/controllers/api/health.php | 280 ++--- app/controllers/api/locale.php | 209 ++-- app/controllers/api/projects.php | 2 - app/controllers/api/storage.php | 393 +++--- app/controllers/api/teams.php | 2 - app/controllers/api/users.php | 2 - app/controllers/mock.php | 7 +- app/controllers/shared/api.php | 4 +- app/controllers/shared/web.php | 9 +- app/controllers/web/console.php | 109 +- app/controllers/web/home.php | 8 +- app/init.php | 8 +- app/views/layouts/default.phtml | 9 +- public/index.php | 1 + 18 files changed, 2444 insertions(+), 2435 deletions(-) diff --git a/app/app.php b/app/app.php index 05606f1cf4..e9e59ecc99 100644 --- a/app/app.php +++ b/app/app.php @@ -2,7 +2,7 @@ require_once __DIR__.'/init.php'; -global $request, $response, $register, $project; +global $register, $project; use Utopia\App; use Utopia\Request; @@ -18,147 +18,133 @@ use Appwrite\Database\Document; use Appwrite\Database\Validator\Authorization; use Appwrite\Database\Adapter\MySQL as MySQLAdapter; use Appwrite\Database\Adapter\Redis as RedisAdapter; -use Appwrite\Event\Event; use Appwrite\Network\Validator\Origin; -$request = new Request(); -$response = new Response(); +// Config::setParam('domain', $request->getServer('HTTP_HOST', '')); +// Config::setParam('domainVerification', false); +// Config::setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN')); +// Config::setParam('protocol', $request->getServer('HTTP_X_FORWARDED_PROTO', $request->getServer('REQUEST_SCHEME', 'https'))); +// Config::setParam('port', (string) \parse_url(Config::getParam('protocol').'://'.$request->getServer('HTTP_HOST', ''), PHP_URL_PORT)); +// Config::setParam('hostname', \parse_url(Config::getParam('protocol').'://'.$request->getServer('HTTP_HOST', null), PHP_URL_HOST)); -$locale = $request->getParam('locale', $request->getHeader('X-Appwrite-Locale', '')); +// \define('COOKIE_DOMAIN', +// ( +// $request->getServer('HTTP_HOST', null) === 'localhost' || +// $request->getServer('HTTP_HOST', null) === 'localhost:'.Config::getParam('port') || +// (\filter_var(Config::getParam('hostname'), FILTER_VALIDATE_IP) !== false) +// ) +// ? null +// : '.'.Config::getParam('hostname') +// ); +// \define('COOKIE_SAMESITE', Response::COOKIE_SAMESITE_NONE); -if (\in_array($locale, Config::getParam('locales'))) { - Locale::setDefault($locale); -} +// Authorization::disable(); -Config::setParam('env', App::getMode()); -Config::setParam('domain', $request->getServer('HTTP_HOST', '')); -Config::setParam('domainVerification', false); -Config::setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN')); -Config::setParam('protocol', $request->getServer('HTTP_X_FORWARDED_PROTO', $request->getServer('REQUEST_SCHEME', 'https'))); -Config::setParam('port', (string) \parse_url(Config::getParam('protocol').'://'.$request->getServer('HTTP_HOST', ''), PHP_URL_PORT)); -Config::setParam('hostname', \parse_url(Config::getParam('protocol').'://'.$request->getServer('HTTP_HOST', null), PHP_URL_HOST)); +// $project = $consoleDB->getDocument($request->getParam('project', $request->getHeader('X-Appwrite-Project', ''))); -\define('COOKIE_DOMAIN', - ( - $request->getServer('HTTP_HOST', null) === 'localhost' || - $request->getServer('HTTP_HOST', null) === 'localhost:'.Config::getParam('port') || - (\filter_var(Config::getParam('hostname'), FILTER_VALIDATE_IP) !== false) - ) - ? null - : '.'.Config::getParam('hostname') - ); -\define('COOKIE_SAMESITE', Response::COOKIE_SAMESITE_NONE); +// Authorization::enable(); -Authorization::disable(); +// $console = $consoleDB->getDocument('console'); -$project = $consoleDB->getDocument($request->getParam('project', $request->getHeader('X-Appwrite-Project', ''))); +// $mode = $request->getParam('mode', $request->getHeader('X-Appwrite-Mode', 'default')); -Authorization::enable(); +// Auth::setCookieName('a_session_'.$project->getId()); -$console = $consoleDB->getDocument('console'); +// if (APP_MODE_ADMIN === $mode) { +// Auth::setCookieName('a_session_'.$console->getId()); +// } -$mode = $request->getParam('mode', $request->getHeader('X-Appwrite-Mode', 'default')); +// $session = Auth::decodeSession( +// $request->getCookie(Auth::$cookieName, // Get sessions +// $request->getCookie(Auth::$cookieName.'_legacy', // Get fallback session from old clients (no SameSite support) +// $request->getHeader('X-Appwrite-Key', '')))); // Get API Key -Auth::setCookieName('a_session_'.$project->getId()); +// // Get fallback session from clients who block 3rd-party cookies +// $response->addHeader('X-Debug-Fallback', 'false'); -if (APP_MODE_ADMIN === $mode) { - Auth::setCookieName('a_session_'.$console->getId()); -} +// if(empty($session['id']) && empty($session['secret'])) { +// $response->addHeader('X-Debug-Fallback', 'true'); +// $fallback = $request->getHeader('X-Fallback-Cookies', ''); +// $fallback = \json_decode($fallback, true); +// $session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : '')); +// } -$session = Auth::decodeSession( - $request->getCookie(Auth::$cookieName, // Get sessions - $request->getCookie(Auth::$cookieName.'_legacy', // Get fallback session from old clients (no SameSite support) - $request->getHeader('X-Appwrite-Key', '')))); // Get API Key +// Auth::$unique = $session['id']; +// Auth::$secret = $session['secret']; -// Get fallback session from clients who block 3rd-party cookies -$response->addHeader('X-Debug-Fallback', 'false'); +// $projectDB = new Database(); +// $projectDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)); +// $projectDB->setNamespace('app_'.$project->getId()); +// $projectDB->setMocks(Config::getParam('collections', [])); -if(empty($session['id']) && empty($session['secret'])) { - $response->addHeader('X-Debug-Fallback', 'true'); - $fallback = $request->getHeader('X-Fallback-Cookies', ''); - $fallback = \json_decode($fallback, true); - $session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : '')); -} +// if (APP_MODE_ADMIN !== $mode) { +// $user = $projectDB->getDocument(Auth::$unique); +// } +// else { +// $user = $consoleDB->getDocument(Auth::$unique); -Auth::$unique = $session['id']; -Auth::$secret = $session['secret']; +// $user +// ->setAttribute('$id', 'admin-'.$user->getAttribute('$id')) +// ; +// } -$projectDB = new Database(); -$projectDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)); -$projectDB->setNamespace('app_'.$project->getId()); -$projectDB->setMocks(Config::getParam('collections', [])); +// if (empty($user->getId()) // Check a document has been found in the DB +// || Database::SYSTEM_COLLECTION_USERS !== $user->getCollection() // Validate returned document is really a user document +// || !Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_LOGIN, Auth::$secret)) { // Validate user has valid login token +// $user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]); +// } -if (APP_MODE_ADMIN !== $mode) { - $user = $projectDB->getDocument(Auth::$unique); -} -else { - $user = $consoleDB->getDocument(Auth::$unique); +// if (APP_MODE_ADMIN === $mode) { +// if (!empty($user->search('teamId', $project->getAttribute('teamId'), $user->getAttribute('memberships')))) { +// Authorization::disable(); +// } else { +// $user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]); +// } +// } - $user - ->setAttribute('$id', 'admin-'.$user->getAttribute('$id')) - ; -} - -if (empty($user->getId()) // Check a document has been found in the DB - || Database::SYSTEM_COLLECTION_USERS !== $user->getCollection() // Validate returned document is really a user document - || !Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_LOGIN, Auth::$secret)) { // Validate user has valid login token - $user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]); -} - -if (APP_MODE_ADMIN === $mode) { - if (!empty($user->search('teamId', $project->getAttribute('teamId'), $user->getAttribute('memberships')))) { - Authorization::disable(); - } else { - $user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]); - } -} - -// Set project mail -$register->get('smtp') - ->setFrom( - App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), - ($project->getId() === 'console') - ? \urldecode(App::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server')) - : \sprintf(Locale::getText('account.emails.team'), $project->getAttribute('name') - ) - ); - -/* - * Configuration files - */ -$utopia = new App('Asia/Tel_Aviv'); -$webhook = new Event('v1-webhooks', 'WebhooksV1'); -$audit = new Event('v1-audits', 'AuditsV1'); -$usage = new Event('v1-usage', 'UsageV1'); -$mail = new Event('v1-mails', 'MailsV1'); -$deletes = new Event('v1-deletes', 'DeletesV1'); +// // Set project mail +// $register->get('smtp') +// ->setFrom( +// App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), +// ($project->getId() === 'console') +// ? \urldecode(App::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server')) +// : \sprintf(Locale::getText('account.emails.team'), $project->getAttribute('name') +// ) +// ); /** * Get All verified client URLs for both console and current projects * + Filter for duplicated entries */ -$clientsConsole = \array_map(function ($node) { - return $node['hostname']; - }, \array_filter($console->getAttribute('platforms', []), function ($node) { - if (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname'])) { - return true; - } +// $clientsConsole = \array_map(function ($node) { +// return $node['hostname']; +// }, \array_filter($console->getAttribute('platforms', []), function ($node) { +// if (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname'])) { +// return true; +// } - return false; - })); +// return false; +// })); -$clients = \array_unique(\array_merge($clientsConsole, \array_map(function ($node) { - return $node['hostname']; - }, \array_filter($project->getAttribute('platforms', []), function ($node) { - if (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname'])) { - return true; - } +// $clients = \array_unique(\array_merge($clientsConsole, \array_map(function ($node) { +// return $node['hostname']; +// }, \array_filter($project->getAttribute('platforms', []), function ($node) { +// if (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname'])) { +// return true; +// } - return false; - })))); +// return false; +// })))); -App::init(function () use ($utopia, $request, $response, &$user, $project, $console, $webhook, $audit, $usage, $clients) { +App::init(function ($utopia, $request, $response, $user, $project, $console, $webhooks, $audits, $usage, $clients, $locale) { + /** @var $locale Utopia\Locale\Locale */ + $localeParam = $request->getParam('locale', $request->getHeader('X-Appwrite-Locale', '')); + + if (\in_array($localeParam, Config::getParam('locales'))) { + $locale->setDefault($localeParam); + }; + $route = $utopia->match($request); if(!empty($route->getLabel('sdk.platform', [])) && empty($project->getId()) && ($route->getLabel('scope', '') !== 'public')) { @@ -312,13 +298,13 @@ App::init(function () use ($utopia, $request, $response, &$user, $project, $cons /* * Background Jobs */ - $webhook + $webhooks ->setParam('projectId', $project->getId()) ->setParam('event', $route->getLabel('webhook', '')) ->setParam('payload', []) ; - $audit + $audits ->setParam('projectId', $project->getId()) ->setParam('userId', $user->getId()) ->setParam('event', '') @@ -336,10 +322,9 @@ App::init(function () use ($utopia, $request, $response, &$user, $project, $cons ->setParam('response', 0) ->setParam('storage', 0) ; -}); - -App::shutdown(function () use ($response, $request, $webhook, $audit, $usage, $deletes, $mode, $project, $utopia) { +}, ['utopia', 'request', 'response', 'user', 'project', 'console', 'webhook', 'audit', 'usage', 'clients', 'locale']); +App::shutdown(function ($utopia, $response, $request, $webhook, $audit, $usage, $deletes, $mode, $project) { /* * Trigger events for background workers */ @@ -366,9 +351,9 @@ App::shutdown(function () use ($response, $request, $webhook, $audit, $usage, $d ->trigger() ; } -}); +}, ['utopia', 'response', 'request', 'webhook', 'audit', 'usage', 'deletes', 'mode', 'project']); -App::options(function () use ($request, $response) { +App::options(function ($request, $response) { $origin = $request->getServer('HTTP_ORIGIN'); $response @@ -378,9 +363,11 @@ App::options(function () use ($request, $response) { ->addHeader('Access-Control-Allow-Origin', $origin) ->addHeader('Access-Control-Allow-Credentials', 'true') ->send(); -}); +}, ['request', 'response']); + +App::error(function ($error, $utopia, $request, $response, $project) { + /** @var Exception $error */ -App::error(function ($error /* @var $error Exception */) use ($request, $response, $utopia, $project) { $version = Config::getParam('version'); switch ($error->getCode()) { @@ -450,91 +437,85 @@ App::error(function ($error /* @var $error Exception */) use ($request, $respons $response ->json($output) ; -}); +}, ['error', 'utopia', 'request', 'response', 'project']); App::get('/manifest.json') ->desc('Progressive app manifest file') ->label('scope', 'public') ->label('docs', false) - ->action( - function () use ($response) { - $response->json([ - 'name' => APP_NAME, - 'short_name' => APP_NAME, - 'start_url' => '.', - 'url' => 'https://appwrite.io/', - 'display' => 'standalone', - 'background_color' => '#fff', - 'theme_color' => '#f02e65', - 'description' => 'End to end backend server for frontend and mobile apps. 👩‍💻👨‍💻', - 'icons' => [ - [ - 'src' => 'images/favicon.png', - 'sizes' => '256x256', - 'type' => 'image/png', - ], + ->action(function ($response) { + /** @var Utopia\Response $response */ + + $response->json([ + 'name' => APP_NAME, + 'short_name' => APP_NAME, + 'start_url' => '.', + 'url' => 'https://appwrite.io/', + 'display' => 'standalone', + 'background_color' => '#fff', + 'theme_color' => '#f02e65', + 'description' => 'End to end backend server for frontend and mobile apps. 👩‍💻👨‍💻', + 'icons' => [ + [ + 'src' => 'images/favicon.png', + 'sizes' => '256x256', + 'type' => 'image/png', ], - ]); - } - ); + ], + ]); + }, ['response']); App::get('/robots.txt') ->desc('Robots.txt File') ->label('scope', 'public') ->label('docs', false) - ->action( - function () use ($response) { - $template = new View(__DIR__.'/views/general/robots.phtml'); - $response->text($template->render(false)); - } - ); + ->action(function ($response) { + $template = new View(__DIR__.'/views/general/robots.phtml'); + $response->text($template->render(false)); + }, ['response']); App::get('/humans.txt') ->desc('Humans.txt File') ->label('scope', 'public') ->label('docs', false) - ->action( - function () use ($response) { - $template = new View(__DIR__.'/views/general/humans.phtml'); - $response->text($template->render(false)); - } - ); + ->action(function ($response) { + $template = new View(__DIR__.'/views/general/humans.phtml'); + $response->text($template->render(false)); + }, ['response']); App::get('/.well-known/acme-challenge') ->desc('SSL Verification') ->label('scope', 'public') ->label('docs', false) - ->action( - function () use ($request, $response) { - $base = \realpath(APP_STORAGE_CERTIFICATES); - $path = \str_replace('/.well-known/acme-challenge/', '', $request->getParam('q')); - $absolute = \realpath($base.'/.well-known/acme-challenge/'.$path); + ->action(function ($request, $response) { + $base = \realpath(APP_STORAGE_CERTIFICATES); + $path = \str_replace('/.well-known/acme-challenge/', '', $request->getParam('q')); + $absolute = \realpath($base.'/.well-known/acme-challenge/'.$path); - if(!$base) { - throw new Exception('Storage error', 500); - } - - if(!$absolute) { - throw new Exception('Unknown path', 404); - } - - if(!\substr($absolute, 0, \strlen($base)) === $base) { - throw new Exception('Invalid path', 401); - } - - if(!\file_exists($absolute)) { - throw new Exception('Unknown path', 404); - } - - $content = @\file_get_contents($absolute); - - if(!$content) { - throw new Exception('Failed to get contents', 500); - } - - $response->text($content); + if(!$base) { + throw new Exception('Storage error', 500); } - ); + + if(!$absolute) { + throw new Exception('Unknown path', 404); + } + + if(!\substr($absolute, 0, \strlen($base)) === $base) { + throw new Exception('Invalid path', 401); + } + + if(!\file_exists($absolute)) { + throw new Exception('Unknown path', 404); + } + + $content = @\file_get_contents($absolute); + + if(!$content) { + throw new Exception('Failed to get contents', 500); + } + + $response->text($content); + }, ['request', 'response']); include_once __DIR__ . '/controllers/shared/api.php'; include_once __DIR__ . '/controllers/shared/web.php'; @@ -543,9 +524,30 @@ foreach(Config::getParam('services', []) as $service) { include_once $service['controller']; } -App::setResource('utopia', function() use ($utopia) {return $utopia;}); -App::setResource('request', function() use ($request) {return $request;}); -App::setResource('response', function() use ($response) {return $response;}); -App::setResource('register', function() use ($register) {return $register;}); +// Runtime Execution -$utopia->run($request, $response); \ No newline at end of file +App::setResource('register', function() use ($register) { return $register; }); +App::setResource('layout', function($locale) { + $layout = new View(__DIR__.'/views/layouts/default.phtml'); + $layout->setParam('locale', $locale); + return $layout; }, ['locale']); +App::setResource('locale', function($request) { return new Locale('en'); }, ['request']); + +// Queues +App::setResource('webhook', function($register) { return $register->get('queue-webhook'); }, ['register']); +App::setResource('audit', function($register) { return $register->get('queue-audit'); }, ['register']); +App::setResource('usage', function($register) { return $register->get('queue-usage'); }, ['register']); +App::setResource('mail', function($register) { return $register->get('queue-mails'); }, ['register']); +App::setResource('deletes', function($register) { return $register->get('queue-deletes'); }, ['register']); + +// Test Mock +App::setResource('clients', function() { return []; }); +App::setResource('user', function() { return new Document([]); }); +App::setResource('project', function() { return new Document([]); }); +App::setResource('console', function() { return new Document([]); }); +App::setResource('consoleDB', function() { return new Database(); }); +App::setResource('projectDB', function() { return new Database([]); }); +App::setResource('mode', function() { return false; }); + +$app = new App('Asia/Tel_Aviv'); +$app->run(new Request(), new Response()); \ No newline at end of file diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 5887cb46f9..3931599f95 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1,8 +1,5 @@ desc('Create Account') @@ -59,92 +55,97 @@ App::post('/v1/account') ->param('email', '', function () { return new Email(); }, 'User email.') ->param('password', '', function () { return new Password(); }, 'User password. Must be between 6 to 32 chars.') ->param('name', '', function () { return new Text(100); }, 'User name.', true) - ->action( - function ($email, $password, $name) use ($request, $response, $audit, $projectDB, $project, $webhook, $oauth2Keys) { - if ('console' === $project->getId()) { - $whitlistEmails = $project->getAttribute('authWhitelistEmails'); - $whitlistIPs = $project->getAttribute('authWhitelistIPs'); - $whitlistDomains = $project->getAttribute('authWhitelistDomains'); + ->action(function ($email, $password, $name, $request, $response, $project, $projectDB, $webhook, $audit) use ($oauth2Keys) { + /** @var Utopia\Request $request */ + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $project */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $webhook */ + /** @var Appwrite\Event\Event $audit */ - if (!empty($whitlistEmails) && !\in_array($email, $whitlistEmails)) { - throw new Exception('Console registration is restricted to specific emails. Contact your administrator for more information.', 401); - } + if ('console' === $project->getId()) { + $whitlistEmails = $project->getAttribute('authWhitelistEmails'); + $whitlistIPs = $project->getAttribute('authWhitelistIPs'); + $whitlistDomains = $project->getAttribute('authWhitelistDomains'); - if (!empty($whitlistIPs) && !\in_array($request->getIP(), $whitlistIPs)) { - throw new Exception('Console registration is restricted to specific IPs. Contact your administrator for more information.', 401); - } - - if (!empty($whitlistDomains) && !\in_array(\substr(\strrchr($email, '@'), 1), $whitlistDomains)) { - throw new Exception('Console registration is restricted to specific domains. Contact your administrator for more information.', 401); - } + if (!empty($whitlistEmails) && !\in_array($email, $whitlistEmails)) { + throw new Exception('Console registration is restricted to specific emails. Contact your administrator for more information.', 401); } - $profile = $projectDB->getCollectionFirst([ // Get user by email address - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'email='.$email, - ], - ]); - - if (!empty($profile)) { - throw new Exception('Account already exists', 409); + if (!empty($whitlistIPs) && !\in_array($request->getIP(), $whitlistIPs)) { + throw new Exception('Console registration is restricted to specific IPs. Contact your administrator for more information.', 401); } - Authorization::disable(); - - try { - $user = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_USERS, - '$permissions' => [ - 'read' => ['*'], - 'write' => ['user:{self}'], - ], - 'email' => $email, - 'emailVerification' => false, - 'status' => Auth::USER_STATUS_UNACTIVATED, - 'password' => Auth::passwordHash($password), - 'password-update' => \time(), - 'registration' => \time(), - 'reset' => false, - 'name' => $name, - ], ['email' => $email]); - } catch (Duplicate $th) { - throw new Exception('Account already exists', 409); + if (!empty($whitlistDomains) && !\in_array(\substr(\strrchr($email, '@'), 1), $whitlistDomains)) { + throw new Exception('Console registration is restricted to specific domains. Contact your administrator for more information.', 401); } - - Authorization::enable(); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } - - $webhook - ->setParam('payload', [ - 'name' => $name, - 'email' => $email, - ]) - ; - - $audit - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.create') - ->setParam('resource', 'users/'.$user->getId()) - ; - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json(\array_merge($user->getArrayCopy(\array_merge( - [ - '$id', - 'email', - 'registration', - 'name', - ], - $oauth2Keys - )), ['roles' => Authorization::getRoles()])); } - ); + + $profile = $projectDB->getCollectionFirst([ // Get user by email address + 'limit' => 1, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_USERS, + 'email='.$email, + ], + ]); + + if (!empty($profile)) { + throw new Exception('Account already exists', 409); + } + + Authorization::disable(); + + try { + $user = $projectDB->createDocument([ + '$collection' => Database::SYSTEM_COLLECTION_USERS, + '$permissions' => [ + 'read' => ['*'], + 'write' => ['user:{self}'], + ], + 'email' => $email, + 'emailVerification' => false, + 'status' => Auth::USER_STATUS_UNACTIVATED, + 'password' => Auth::passwordHash($password), + 'password-update' => \time(), + 'registration' => \time(), + 'reset' => false, + 'name' => $name, + ], ['email' => $email]); + } catch (Duplicate $th) { + throw new Exception('Account already exists', 409); + } + + Authorization::enable(); + + if (false === $user) { + throw new Exception('Failed saving user to DB', 500); + } + + $webhook + ->setParam('payload', [ + 'name' => $name, + 'email' => $email, + ]) + ; + + $audit + ->setParam('userId', $user->getId()) + ->setParam('event', 'account.create') + ->setParam('resource', 'users/'.$user->getId()) + ; + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->json(\array_merge($user->getArrayCopy(\array_merge( + [ + '$id', + 'email', + 'registration', + 'name', + ], + $oauth2Keys + )), ['roles' => Authorization::getRoles()])); + }, ['request', 'response', 'project', 'projectDB', 'webhook', 'audit']); App::post('/v1/account/sessions') ->desc('Create Account Session') @@ -159,82 +160,86 @@ App::post('/v1/account/sessions') ->label('abuse-key', 'url:{url},email:{param-email}') ->param('email', '', function () { return new Email(); }, 'User email.') ->param('password', '', function () { return new Password(); }, 'User password. Must be between 6 to 32 chars.') - ->action( - function ($email, $password) use ($response, $request, $projectDB, $audit, $webhook) { - $protocol = Config::getParam('protocol'); - $profile = $projectDB->getCollectionFirst([ // Get user by email address - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'email='.$email, - ], - ]); + ->action(function ($email, $password, $request, $response, $projectDB, $webhook, $audit) { + /** @var Utopia\Request $request */ + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $webhook */ + /** @var Appwrite\Event\Event $audit */ - if (false == $profile || !Auth::passwordVerify($password, $profile->getAttribute('password'))) { - $audit - //->setParam('userId', $profile->getId()) - ->setParam('event', 'account.sesssions.failed') - ->setParam('resource', 'users/'.($profile ? $profile->getId() : '')) - ; - - throw new Exception('Invalid credentials', 401); // Wrong password or username - } - - $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; - $secret = Auth::tokenGenerator(); - $session = new Document([ - '$collection' => Database::SYSTEM_COLLECTION_TOKENS, - '$permissions' => ['read' => ['user:'.$profile->getId()], 'write' => ['user:'.$profile->getId()]], - 'type' => Auth::TOKEN_TYPE_LOGIN, - 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak - 'expire' => $expiry, - 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), - 'ip' => $request->getIP(), - ]); - - Authorization::setRole('user:'.$profile->getId()); - - $session = $projectDB->createDocument($session->getArrayCopy()); - - if (false === $session) { - throw new Exception('Failed saving session to DB', 500); - } - - $profile->setAttribute('tokens', $session, Document::SET_TYPE_APPEND); - - $profile = $projectDB->updateDocument($profile->getArrayCopy()); - - if (false === $profile) { - throw new Exception('Failed saving user to DB', 500); - } - - $webhook - ->setParam('payload', [ - 'name' => $profile->getAttribute('name', ''), - 'email' => $profile->getAttribute('email', ''), - ]) - ; + $protocol = Config::getParam('protocol'); + $profile = $projectDB->getCollectionFirst([ // Get user by email address + 'limit' => 1, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_USERS, + 'email='.$email, + ], + ]); + if (false == $profile || !Auth::passwordVerify($password, $profile->getAttribute('password'))) { $audit - ->setParam('userId', $profile->getId()) - ->setParam('event', 'account.sessions.create') - ->setParam('resource', 'users/'.$profile->getId()) + //->setParam('userId', $profile->getId()) + ->setParam('event', 'account.sesssions.failed') + ->setParam('resource', 'users/'.($profile ? $profile->getId() : '')) ; - if (!Config::getParam('domainVerification')) { - $response - ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($profile->getId(), $secret)])) - ; - } - + throw new Exception('Invalid credentials', 401); // Wrong password or username + } + + $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; + $secret = Auth::tokenGenerator(); + $session = new Document([ + '$collection' => Database::SYSTEM_COLLECTION_TOKENS, + '$permissions' => ['read' => ['user:'.$profile->getId()], 'write' => ['user:'.$profile->getId()]], + 'type' => Auth::TOKEN_TYPE_LOGIN, + 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak + 'expire' => $expiry, + 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), + 'ip' => $request->getIP(), + ]); + + Authorization::setRole('user:'.$profile->getId()); + + $session = $projectDB->createDocument($session->getArrayCopy()); + + if (false === $session) { + throw new Exception('Failed saving session to DB', 500); + } + + $profile->setAttribute('tokens', $session, Document::SET_TYPE_APPEND); + + $profile = $projectDB->updateDocument($profile->getArrayCopy()); + + if (false === $profile) { + throw new Exception('Failed saving user to DB', 500); + } + + $webhook + ->setParam('payload', [ + 'name' => $profile->getAttribute('name', ''), + 'email' => $profile->getAttribute('email', ''), + ]) + ; + + $audit + ->setParam('userId', $profile->getId()) + ->setParam('event', 'account.sessions.create') + ->setParam('resource', 'users/'.$profile->getId()) + ; + + if (!Config::getParam('domainVerification')) { $response - ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($profile->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, Auth::encodeSession($profile->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($session->getArrayCopy(['$id', 'type', 'expire'])) + ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($profile->getId(), $secret)])) ; } - ); + + $response + ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($profile->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, Auth::encodeSession($profile->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->json($session->getArrayCopy(['$id', 'type', 'expire'])) + ; + }, ['request', 'response', 'projectDB', 'webhook', 'audit']); App::get('/v1/account/sessions/oauth2/:provider') ->desc('Create Account Session with OAuth2') @@ -251,41 +256,43 @@ App::get('/v1/account/sessions/oauth2/:provider') ->label('abuse-limit', 50) ->label('abuse-key', 'ip:{ip}') ->param('provider', '', function () { return new WhiteList(\array_keys(Config::getParam('providers'))); }, 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('providers'), function($node) {return (!$node['mock']);}))).'.') - ->param('success', $oauthDefaultSuccess, function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true) - ->param('failure', $oauthDefaultFailure, function () use ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true) + ->param('success', $oauthDefaultSuccess, function ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) + ->param('failure', $oauthDefaultFailure, function ($clients) { return new Host($clients); }, 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) ->param('scopes', [], function () { return new ArrayList(new Text(128)); }, 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes.', true) - ->action( - function ($provider, $success, $failure, $scopes) use ($response, $request, $project) { - $protocol = Config::getParam('protocol'); - $callback = $protocol.'://'.$request->getServer('HTTP_HOST').'/v1/account/sessions/oauth2/callback/'.$provider.'/'.$project->getId(); - $appId = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Appid', ''); - $appSecret = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}'); + ->action(function ($provider, $success, $failure, $scopes, $request, $response, $project) { + /** @var Utopia\Request $request */ + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $project */ - $appSecret = \json_decode($appSecret, true); + $protocol = Config::getParam('protocol'); + $callback = $protocol.'://'.$request->getServer('HTTP_HOST').'/v1/account/sessions/oauth2/callback/'.$provider.'/'.$project->getId(); + $appId = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Appid', ''); + $appSecret = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}'); - if (!empty($appSecret) && isset($appSecret['version'])) { - $key = App::getEnv('_APP_OPENSSL_KEY_V'.$appSecret['version']); - $appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, \hex2bin($appSecret['iv']), \hex2bin($appSecret['tag'])); - } + $appSecret = \json_decode($appSecret, true); - if (empty($appId) || empty($appSecret)) { - throw new Exception('This provider is disabled. Please configure the provider app ID and app secret key from your '.APP_NAME.' console to continue.', 412); - } - - $classname = 'Appwrite\\Auth\\OAuth2\\'.\ucfirst($provider); - - if (!\class_exists($classname)) { - throw new Exception('Provider is not supported', 501); - } - - $oauth2 = new $classname($appId, $appSecret, $callback, ['success' => $success, 'failure' => $failure], $scopes); - - $response - ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') - ->addHeader('Pragma', 'no-cache') - ->redirect($oauth2->getLoginURL()); + if (!empty($appSecret) && isset($appSecret['version'])) { + $key = App::getEnv('_APP_OPENSSL_KEY_V'.$appSecret['version']); + $appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, \hex2bin($appSecret['iv']), \hex2bin($appSecret['tag'])); } - ); + + if (empty($appId) || empty($appSecret)) { + throw new Exception('This provider is disabled. Please configure the provider app ID and app secret key from your '.APP_NAME.' console to continue.', 412); + } + + $classname = 'Appwrite\\Auth\\OAuth2\\'.\ucfirst($provider); + + if (!\class_exists($classname)) { + throw new Exception('Provider is not supported', 501); + } + + $oauth2 = new $classname($appId, $appSecret, $callback, ['success' => $success, 'failure' => $failure], $scopes); + + $response + ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') + ->addHeader('Pragma', 'no-cache') + ->redirect($oauth2->getLoginURL()); + }, ['request', 'response', 'project']); App::get('/v1/account/sessions/oauth2/callback/:provider/:projectId') ->desc('OAuth2 Callback') @@ -297,18 +304,16 @@ App::get('/v1/account/sessions/oauth2/callback/:provider/:projectId') ->param('provider', '', function () { return new WhiteList(\array_keys(Config::getParam('providers'))); }, 'OAuth2 provider.') ->param('code', '', function () { return new Text(1024); }, 'OAuth2 code.') ->param('state', '', function () { return new Text(2048); }, 'Login state params.', true) - ->action( - function ($projectId, $provider, $code, $state) use ($response) { - $domain = Config::getParam('domain'); - $protocol = Config::getParam('protocol'); - - $response - ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') - ->addHeader('Pragma', 'no-cache') - ->redirect($protocol.'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?' - .\http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state])); - } - ); + ->action(function ($projectId, $provider, $code, $state, $response) { + $domain = Config::getParam('domain'); + $protocol = Config::getParam('protocol'); + + $response + ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') + ->addHeader('Pragma', 'no-cache') + ->redirect($protocol.'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?' + .\http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state])); + }, ['response']); App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId') ->desc('OAuth2 Callback') @@ -321,18 +326,16 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId') ->param('provider', '', function () { return new WhiteList(\array_keys(Config::getParam('providers'))); }, 'OAuth2 provider.') ->param('code', '', function () { return new Text(1024); }, 'OAuth2 code.') ->param('state', '', function () { return new Text(2048); }, 'Login state params.', true) - ->action( - function ($projectId, $provider, $code, $state) use ($response) { - $domain = Config::getParam('domain'); - $protocol = Config::getParam('protocol'); - - $response - ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') - ->addHeader('Pragma', 'no-cache') - ->redirect($protocol.'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?' - .\http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state])); - } - ); + ->action(function ($projectId, $provider, $code, $state, $response) { + $domain = Config::getParam('domain'); + $protocol = Config::getParam('protocol'); + + $response + ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') + ->addHeader('Pragma', 'no-cache') + ->redirect($protocol.'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?' + .\http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state])); + }, ['response']); App::get('/v1/account/sessions/oauth2/:provider/redirect') ->desc('OAuth2 Redirect') @@ -346,187 +349,192 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->param('provider', '', function () { return new WhiteList(\array_keys(Config::getParam('providers'))); }, 'OAuth2 provider.') ->param('code', '', function () { return new Text(1024); }, 'OAuth2 code.') ->param('state', '', function () { return new Text(2048); }, 'OAuth2 state params.', true) - ->action( - function ($provider, $code, $state) use ($response, $request, $user, $projectDB, $project, $audit, $oauthDefaultSuccess) { - $protocol = Config::getParam('protocol'); - $callback = $protocol.'://'.$request->getServer('HTTP_HOST').'/v1/account/sessions/oauth2/callback/'.$provider.'/'.$project->getId(); - $defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => '']; - $validateURL = new URL(); + ->action(function ($provider, $code, $state, $request, $response, $project, $user, $projectDB, $audit) use ($oauthDefaultSuccess) { + /** @var Utopia\Request $request */ + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $project */ + /** @var Appwrite\Database\Document $user */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $audit */ + + $protocol = Config::getParam('protocol'); + $callback = $protocol.'://'.$request->getServer('HTTP_HOST').'/v1/account/sessions/oauth2/callback/'.$provider.'/'.$project->getId(); + $defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => '']; + $validateURL = new URL(); - $appId = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Appid', ''); - $appSecret = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}'); + $appId = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Appid', ''); + $appSecret = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}'); - $appSecret = \json_decode($appSecret, true); + $appSecret = \json_decode($appSecret, true); - if (!empty($appSecret) && isset($appSecret['version'])) { - $key = App::getEnv('_APP_OPENSSL_KEY_V'.$appSecret['version']); - $appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, \hex2bin($appSecret['iv']), \hex2bin($appSecret['tag'])); + if (!empty($appSecret) && isset($appSecret['version'])) { + $key = App::getEnv('_APP_OPENSSL_KEY_V'.$appSecret['version']); + $appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, \hex2bin($appSecret['iv']), \hex2bin($appSecret['tag'])); + } + + $classname = 'Appwrite\\Auth\\OAuth2\\'.\ucfirst($provider); + + if (!\class_exists($classname)) { + throw new Exception('Provider is not supported', 501); + } + + $oauth2 = new $classname($appId, $appSecret, $callback); + + if (!empty($state)) { + try { + $state = \array_merge($defaultState, $oauth2->parseState($state)); + } catch (\Exception $exception) { + throw new Exception('Failed to parse login state params as passed from OAuth2 provider'); + } + } else { + $state = $defaultState; + } + + if (!$validateURL->isValid($state['success'])) { + throw new Exception('Invalid redirect URL for success login', 400); + } + + if (!empty($state['failure']) && !$validateURL->isValid($state['failure'])) { + throw new Exception('Invalid redirect URL for failure login', 400); + } + + $state['failure'] = null; + $accessToken = $oauth2->getAccessToken($code); + + if (empty($accessToken)) { + if (!empty($state['failure'])) { + $response->redirect($state['failure'], 301, 0); } - $classname = 'Appwrite\\Auth\\OAuth2\\'.\ucfirst($provider); + throw new Exception('Failed to obtain access token'); + } - if (!\class_exists($classname)) { - throw new Exception('Provider is not supported', 501); + $oauth2ID = $oauth2->getUserID($accessToken); + + if (empty($oauth2ID)) { + if (!empty($state['failure'])) { + $response->redirect($state['failure'], 301, 0); } - $oauth2 = new $classname($appId, $appSecret, $callback); + throw new Exception('Missing ID from OAuth2 provider', 400); + } - if (!empty($state)) { - try { - $state = \array_merge($defaultState, $oauth2->parseState($state)); - } catch (\Exception $exception) { - throw new Exception('Failed to parse login state params as passed from OAuth2 provider'); - } - } else { - $state = $defaultState; - } + $current = Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_LOGIN, Auth::$secret); - if (!$validateURL->isValid($state['success'])) { - throw new Exception('Invalid redirect URL for success login', 400); - } + if ($current) { + $projectDB->deleteDocument($current); //throw new Exception('User already logged in', 401); + } - if (!empty($state['failure']) && !$validateURL->isValid($state['failure'])) { - throw new Exception('Invalid redirect URL for failure login', 400); - } - - $state['failure'] = null; - $accessToken = $oauth2->getAccessToken($code); + $user = (empty($user->getId())) ? $projectDB->getCollectionFirst([ // Get user by provider id + 'limit' => 1, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_USERS, + 'oauth2'.\ucfirst($provider).'='.$oauth2ID, + ], + ]) : $user; - if (empty($accessToken)) { - if (!empty($state['failure'])) { - $response->redirect($state['failure'], 301, 0); - } + if (empty($user)) { // No user logged in or with OAuth2 provider ID, create new one or connect with account with same email + $name = $oauth2->getUserName($accessToken); + $email = $oauth2->getUserEmail($accessToken); - throw new Exception('Failed to obtain access token'); - } - - $oauth2ID = $oauth2->getUserID($accessToken); - - if (empty($oauth2ID)) { - if (!empty($state['failure'])) { - $response->redirect($state['failure'], 301, 0); - } - - throw new Exception('Missing ID from OAuth2 provider', 400); - } - - $current = Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_LOGIN, Auth::$secret); - - if ($current) { - $projectDB->deleteDocument($current); //throw new Exception('User already logged in', 401); - } - - $user = (empty($user->getId())) ? $projectDB->getCollectionFirst([ // Get user by provider id + $user = $projectDB->getCollectionFirst([ // Get user by provider email address 'limit' => 1, 'filters' => [ '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'oauth2'.\ucfirst($provider).'='.$oauth2ID, + 'email='.$email, ], - ]) : $user; - - if (empty($user)) { // No user logged in or with OAuth2 provider ID, create new one or connect with account with same email - $name = $oauth2->getUserName($accessToken); - $email = $oauth2->getUserEmail($accessToken); - - $user = $projectDB->getCollectionFirst([ // Get user by provider email address - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'email='.$email, - ], - ]); - - if (!$user || empty($user->getId())) { // Last option -> create user alone, generate random password - Authorization::disable(); - - try { - $user = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_USERS, - '$permissions' => ['read' => ['*'], 'write' => ['user:{self}']], - 'email' => $email, - 'emailVerification' => true, - 'status' => Auth::USER_STATUS_ACTIVATED, // Email should already be authenticated by OAuth2 provider - 'password' => Auth::passwordHash(Auth::passwordGenerator()), - 'password-update' => \time(), - 'registration' => \time(), - 'reset' => false, - 'name' => $name, - ], ['email' => $email]); - } catch (Duplicate $th) { - throw new Exception('Account already exists', 409); - } - - Authorization::enable(); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } - } - } - - // Create session token, verify user account and update OAuth2 ID and Access Token - - $secret = Auth::tokenGenerator(); - $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; - $session = new Document([ - '$collection' => Database::SYSTEM_COLLECTION_TOKENS, - '$permissions' => ['read' => ['user:'.$user['$id']], 'write' => ['user:'.$user['$id']]], - 'type' => Auth::TOKEN_TYPE_LOGIN, - 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak - 'expire' => $expiry, - 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), - 'ip' => $request->getIP(), ]); - $user - ->setAttribute('oauth2'.\ucfirst($provider), $oauth2ID) - ->setAttribute('oauth2'.\ucfirst($provider).'AccessToken', $accessToken) - ->setAttribute('status', Auth::USER_STATUS_ACTIVATED) - ->setAttribute('tokens', $session, Document::SET_TYPE_APPEND) - ; + if (!$user || empty($user->getId())) { // Last option -> create user alone, generate random password + Authorization::disable(); - Authorization::setRole('user:'.$user->getId()); + try { + $user = $projectDB->createDocument([ + '$collection' => Database::SYSTEM_COLLECTION_USERS, + '$permissions' => ['read' => ['*'], 'write' => ['user:{self}']], + 'email' => $email, + 'emailVerification' => true, + 'status' => Auth::USER_STATUS_ACTIVATED, // Email should already be authenticated by OAuth2 provider + 'password' => Auth::passwordHash(Auth::passwordGenerator()), + 'password-update' => \time(), + 'registration' => \time(), + 'reset' => false, + 'name' => $name, + ], ['email' => $email]); + } catch (Duplicate $th) { + throw new Exception('Account already exists', 409); + } - $user = $projectDB->updateDocument($user->getArrayCopy()); + Authorization::enable(); - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); + if (false === $user) { + throw new Exception('Failed saving user to DB', 500); + } } + } - $audit - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.sessions.create') - ->setParam('resource', 'users/'.$user->getId()) - ->setParam('data', ['provider' => $provider]) - ; + // Create session token, verify user account and update OAuth2 ID and Access Token - if (!Config::getParam('domainVerification')) { - $response - ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])) - ; - } - - // Add keys for non-web platforms - TODO - add verification phase to aviod session sniffing - if (parse_url($state['success'], PHP_URL_PATH) === $oauthDefaultSuccess) { - $state['success'] = URLParser::parse($state['success']); - $query = URLParser::parseQuery($state['success']['query']); - $query['project'] = $project->getId(); - $query['domain'] = COOKIE_DOMAIN; - $query['key'] = Auth::$cookieName; - $query['secret'] = Auth::encodeSession($user->getId(), $secret); - $state['success']['query'] = URLParser::unparseQuery($query); - $state['success'] = URLParser::unparse($state['success']); - } + $secret = Auth::tokenGenerator(); + $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; + $session = new Document([ + '$collection' => Database::SYSTEM_COLLECTION_TOKENS, + '$permissions' => ['read' => ['user:'.$user['$id']], 'write' => ['user:'.$user['$id']]], + 'type' => Auth::TOKEN_TYPE_LOGIN, + 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak + 'expire' => $expiry, + 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), + 'ip' => $request->getIP(), + ]); + $user + ->setAttribute('oauth2'.\ucfirst($provider), $oauth2ID) + ->setAttribute('oauth2'.\ucfirst($provider).'AccessToken', $accessToken) + ->setAttribute('status', Auth::USER_STATUS_ACTIVATED) + ->setAttribute('tokens', $session, Document::SET_TYPE_APPEND) + ; + + Authorization::setRole('user:'.$user->getId()); + + $user = $projectDB->updateDocument($user->getArrayCopy()); + + if (false === $user) { + throw new Exception('Failed saving user to DB', 500); + } + + $audit + ->setParam('userId', $user->getId()) + ->setParam('event', 'account.sessions.create') + ->setParam('resource', 'users/'.$user->getId()) + ->setParam('data', ['provider' => $provider]) + ; + + if (!Config::getParam('domainVerification')) { $response - ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') - ->addHeader('Pragma', 'no-cache') - ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) - ->redirect($state['success']) + ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])) ; } - ); + + // Add keys for non-web platforms - TODO - add verification phase to aviod session sniffing + if (parse_url($state['success'], PHP_URL_PATH) === $oauthDefaultSuccess) { + $state['success'] = URLParser::parse($state['success']); + $query = URLParser::parseQuery($state['success']['query']); + $query['project'] = $project->getId(); + $query['domain'] = COOKIE_DOMAIN; + $query['key'] = Auth::$cookieName; + $query['secret'] = Auth::encodeSession($user->getId(), $secret); + $state['success']['query'] = URLParser::unparseQuery($query); + $state['success'] = URLParser::unparse($state['success']); + } + + $response + ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') + ->addHeader('Pragma', 'no-cache') + ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) + ->redirect($state['success']) + ; + }, ['request', 'response', 'project', 'user', 'projectDB', 'audit']); App::get('/v1/account') ->desc('Get Account') @@ -537,21 +545,21 @@ App::get('/v1/account') ->label('sdk.method', 'get') ->label('sdk.description', '/docs/references/account/get.md') ->label('sdk.response', ['200' => 'user']) - ->inject('response') - ->action( - function ($response) use (&$user, $oauth2Keys) { - $response->json(\array_merge($user->getArrayCopy(\array_merge( - [ - '$id', - 'email', - 'emailVerification', - 'registration', - 'name', - ], - $oauth2Keys - )), ['roles' => Authorization::getRoles()])); - } - ); + ->action(function ($response, $user) use ($oauth2Keys) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $user */ + + $response->json(\array_merge($user->getArrayCopy(\array_merge( + [ + '$id', + 'email', + 'emailVerification', + 'registration', + 'name', + ], + $oauth2Keys + )), ['roles' => Authorization::getRoles()])); + }, ['response', ['user']]); App::get('/v1/account/prefs') ->desc('Get Account Preferences') @@ -561,20 +569,21 @@ App::get('/v1/account/prefs') ->label('sdk.namespace', 'account') ->label('sdk.method', 'getPrefs') ->label('sdk.description', '/docs/references/account/get-prefs.md') - ->action( - function () use ($response, $user) { - $prefs = $user->getAttribute('prefs', '{}'); + ->action(function ($response, $user) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $user */ - try { - $prefs = \json_decode($prefs, true); - $prefs = ($prefs) ? $prefs : []; - } catch (\Exception $error) { - throw new Exception('Failed to parse prefs', 500); - } + $prefs = $user->getAttribute('prefs', '{}'); - $response->json($prefs); + try { + $prefs = \json_decode($prefs, true); + $prefs = ($prefs) ? $prefs : []; + } catch (\Exception $error) { + throw new Exception('Failed to parse prefs', 500); } - ); + + $response->json($prefs); + }, ['response', 'user']); App::get('/v1/account/sessions') ->desc('Get Account Sessions') @@ -584,56 +593,58 @@ App::get('/v1/account/sessions') ->label('sdk.namespace', 'account') ->label('sdk.method', 'getSessions') ->label('sdk.description', '/docs/references/account/get-sessions.md') - ->action( - function () use ($response, $user) { - $tokens = $user->getAttribute('tokens', []); - $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb'); - $sessions = []; - $current = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_LOGIN, Auth::$secret); - $index = 0; - $countries = Locale::getText('countries'); + ->action(function ($response, $user, $locale) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $user */ + /** @var Utopia\Locale\Locale $locale */ - foreach ($tokens as $token) { /* @var $token Document */ - if (Auth::TOKEN_TYPE_LOGIN != $token->getAttribute('type')) { - continue; - } + $tokens = $user->getAttribute('tokens', []); + $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb'); + $sessions = []; + $current = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_LOGIN, Auth::$secret); + $index = 0; + $countries = $locale->getText('countries'); - $userAgent = (!empty($token->getAttribute('userAgent'))) ? $token->getAttribute('userAgent') : 'UNKNOWN'; - - $dd = new DeviceDetector($userAgent); - - // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then) - // $dd->skipBotDetection(); - - $dd->parse(); - - $sessions[$index] = [ - '$id' => $token->getId(), - 'OS' => $dd->getOs(), - 'client' => $dd->getClient(), - 'device' => $dd->getDevice(), - 'brand' => $dd->getBrand(), - 'model' => $dd->getModel(), - 'ip' => $token->getAttribute('ip', ''), - 'geo' => [], - 'current' => ($current == $token->getId()) ? true : false, - ]; - - try { - $record = $reader->country($token->getAttribute('ip', '')); - $sessions[$index]['geo']['isoCode'] = \strtolower($record->country->isoCode); - $sessions[$index]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown'); - } catch (\Exception $e) { - $sessions[$index]['geo']['isoCode'] = '--'; - $sessions[$index]['geo']['country'] = Locale::getText('locale.country.unknown'); - } - - ++$index; + foreach ($tokens as $token) { /* @var $token Document */ + if (Auth::TOKEN_TYPE_LOGIN != $token->getAttribute('type')) { + continue; } - $response->json($sessions); + $userAgent = (!empty($token->getAttribute('userAgent'))) ? $token->getAttribute('userAgent') : 'UNKNOWN'; + + $dd = new DeviceDetector($userAgent); + + // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then) + // $dd->skipBotDetection(); + + $dd->parse(); + + $sessions[$index] = [ + '$id' => $token->getId(), + 'OS' => $dd->getOs(), + 'client' => $dd->getClient(), + 'device' => $dd->getDevice(), + 'brand' => $dd->getBrand(), + 'model' => $dd->getModel(), + 'ip' => $token->getAttribute('ip', ''), + 'geo' => [], + 'current' => ($current == $token->getId()) ? true : false, + ]; + + try { + $record = $reader->country($token->getAttribute('ip', '')); + $sessions[$index]['geo']['isoCode'] = \strtolower($record->country->isoCode); + $sessions[$index]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : $locale->getText('locale.country.unknown'); + } catch (\Exception $e) { + $sessions[$index]['geo']['isoCode'] = '--'; + $sessions[$index]['geo']['country'] = $locale->getText('locale.country.unknown'); + } + + ++$index; } - ); + + $response->json($sessions); + }, ['response', 'user', 'locale']); App::get('/v1/account/logs') ->desc('Get Account Logs') @@ -643,70 +654,73 @@ App::get('/v1/account/logs') ->label('sdk.namespace', 'account') ->label('sdk.method', 'getLogs') ->label('sdk.description', '/docs/references/account/get-logs.md') - ->action( - function () use ($response, $register, $project, $user) { - $adapter = new AuditAdapter($register->get('db')); - $adapter->setNamespace('app_'.$project->getId()); + ->action(function ($response, $register, $project, $user) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $project */ + /** @var Appwrite\Database\Document $user */ + /** @var Utopia\Locale\Locale $locale */ - $audit = new Audit($adapter); - $countries = Locale::getText('countries'); + $adapter = new AuditAdapter($register->get('db')); + $adapter->setNamespace('app_'.$project->getId()); - $logs = $audit->getLogsByUserAndActions($user->getId(), [ - 'account.create', - 'account.delete', - 'account.update.name', - 'account.update.email', - 'account.update.password', - 'account.update.prefs', - 'account.sessions.create', - 'account.sessions.delete', - 'account.recovery.create', - 'account.recovery.update', - 'account.verification.create', - 'account.verification.update', - 'teams.membership.create', - 'teams.membership.update', - 'teams.membership.delete', - ]); + $audit = new Audit($adapter); + $countries = $locale->getText('countries'); - $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb'); - $output = []; + $logs = $audit->getLogsByUserAndActions($user->getId(), [ + 'account.create', + 'account.delete', + 'account.update.name', + 'account.update.email', + 'account.update.password', + 'account.update.prefs', + 'account.sessions.create', + 'account.sessions.delete', + 'account.recovery.create', + 'account.recovery.update', + 'account.verification.create', + 'account.verification.update', + 'teams.membership.create', + 'teams.membership.update', + 'teams.membership.delete', + ]); - foreach ($logs as $i => &$log) { - $log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN'; + $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb'); + $output = []; - $dd = new DeviceDetector($log['userAgent']); + foreach ($logs as $i => &$log) { + $log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN'; - $dd->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then) + $dd = new DeviceDetector($log['userAgent']); - $dd->parse(); + $dd->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then) - $output[$i] = [ - 'event' => $log['event'], - 'ip' => $log['ip'], - 'time' => \strtotime($log['time']), - 'OS' => $dd->getOs(), - 'client' => $dd->getClient(), - 'device' => $dd->getDevice(), - 'brand' => $dd->getBrand(), - 'model' => $dd->getModel(), - 'geo' => [], - ]; + $dd->parse(); - try { - $record = $reader->country($log['ip']); - $output[$i]['geo']['isoCode'] = \strtolower($record->country->isoCode); - $output[$i]['geo']['country'] = $record->country->name; - $output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown'); - } catch (\Exception $e) { - $output[$i]['geo']['isoCode'] = '--'; - $output[$i]['geo']['country'] = Locale::getText('locale.country.unknown'); - } + $output[$i] = [ + 'event' => $log['event'], + 'ip' => $log['ip'], + 'time' => \strtotime($log['time']), + 'OS' => $dd->getOs(), + 'client' => $dd->getClient(), + 'device' => $dd->getDevice(), + 'brand' => $dd->getBrand(), + 'model' => $dd->getModel(), + 'geo' => [], + ]; + + try { + $record = $reader->country($log['ip']); + $output[$i]['geo']['isoCode'] = \strtolower($record->country->isoCode); + $output[$i]['geo']['country'] = $record->country->name; + $output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : $locale->getText('locale.country.unknown'); + } catch (\Exception $e) { + $output[$i]['geo']['isoCode'] = '--'; + $output[$i]['geo']['country'] = $locale->getText('locale.country.unknown'); } - - $response->json($output); } - ); + + $response->json($output); + }, ['response', 'register', 'project', 'user']); App::patch('/v1/account/name') ->desc('Update Account Name') @@ -718,33 +732,36 @@ App::patch('/v1/account/name') ->label('sdk.method', 'updateName') ->label('sdk.description', '/docs/references/account/update-name.md') ->param('name', '', function () { return new Text(100); }, 'User name.') - ->action( - function ($name) use ($response, $user, $projectDB, $audit, $oauth2Keys) { - $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ - 'name' => $name, - ])); + ->action(function ($name, $response, $user, $projectDB, $audit) use ($oauth2Keys) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $user */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $audit */ - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } + $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ + 'name' => $name, + ])); - $audit - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.update.name') - ->setParam('resource', 'users/'.$user->getId()) - ; - - $response->json(\array_merge($user->getArrayCopy(\array_merge( - [ - '$id', - 'email', - 'registration', - 'name', - ], - $oauth2Keys - )), ['roles' => Authorization::getRoles()])); + if (false === $user) { + throw new Exception('Failed saving user to DB', 500); } - ); + + $audit + ->setParam('userId', $user->getId()) + ->setParam('event', 'account.update.name') + ->setParam('resource', 'users/'.$user->getId()) + ; + + $response->json(\array_merge($user->getArrayCopy(\array_merge( + [ + '$id', + 'email', + 'registration', + 'name', + ], + $oauth2Keys + )), ['roles' => Authorization::getRoles()])); + }, ['response', 'user', 'projectDB', 'audit']); App::patch('/v1/account/password') ->desc('Update Account Password') @@ -757,37 +774,40 @@ App::patch('/v1/account/password') ->label('sdk.description', '/docs/references/account/update-password.md') ->param('password', '', function () { return new Password(); }, 'New user password. Must be between 6 to 32 chars.') ->param('oldPassword', '', function () { return new Password(); }, 'Old user password. Must be between 6 to 32 chars.') - ->action( - function ($password, $oldPassword) use ($response, $user, $projectDB, $audit, $oauth2Keys) { - if (!Auth::passwordVerify($oldPassword, $user->getAttribute('password'))) { // Double check user password - throw new Exception('Invalid credentials', 401); - } + ->action(function ($password, $oldPassword, $response, $user, $projectDB, $audit) use ($oauth2Keys) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $user */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $audit */ - $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ - 'password' => Auth::passwordHash($password), - ])); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } - - $audit - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.update.password') - ->setParam('resource', 'users/'.$user->getId()) - ; - - $response->json(\array_merge($user->getArrayCopy(\array_merge( - [ - '$id', - 'email', - 'registration', - 'name', - ], - $oauth2Keys - )), ['roles' => Authorization::getRoles()])); + if (!Auth::passwordVerify($oldPassword, $user->getAttribute('password'))) { // Double check user password + throw new Exception('Invalid credentials', 401); } - ); + + $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ + 'password' => Auth::passwordHash($password), + ])); + + if (false === $user) { + throw new Exception('Failed saving user to DB', 500); + } + + $audit + ->setParam('userId', $user->getId()) + ->setParam('event', 'account.update.password') + ->setParam('resource', 'users/'.$user->getId()) + ; + + $response->json(\array_merge($user->getArrayCopy(\array_merge( + [ + '$id', + 'email', + 'registration', + 'name', + ], + $oauth2Keys + )), ['roles' => Authorization::getRoles()])); + }, ['response', 'user', 'projectDB', 'audit']); App::patch('/v1/account/email') ->desc('Update Account Email') @@ -800,52 +820,55 @@ App::patch('/v1/account/email') ->label('sdk.description', '/docs/references/account/update-email.md') ->param('email', '', function () { return new Email(); }, 'User email.') ->param('password', '', function () { return new Password(); }, 'User password. Must be between 6 to 32 chars.') - ->action( - function ($email, $password) use ($response, $user, $projectDB, $audit, $oauth2Keys) { - if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password - throw new Exception('Invalid credentials', 401); - } + ->action(function ($email, $password, $response, $user, $projectDB, $audit) use ($oauth2Keys) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $user */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $audit */ - $profile = $projectDB->getCollectionFirst([ // Get user by email address - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'email='.$email, - ], - ]); - - if (!empty($profile)) { - throw new Exception('User already registered', 400); - } - - // TODO after this user needs to confirm mail again - - $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ - 'email' => $email, - 'emailVerification' => false, - ])); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } - - $audit - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.update.email') - ->setParam('resource', 'users/'.$user->getId()) - ; - - $response->json(\array_merge($user->getArrayCopy(\array_merge( - [ - '$id', - 'email', - 'registration', - 'name', - ], - $oauth2Keys - )), ['roles' => Authorization::getRoles()])); + if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password + throw new Exception('Invalid credentials', 401); } - ); + + $profile = $projectDB->getCollectionFirst([ // Get user by email address + 'limit' => 1, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_USERS, + 'email='.$email, + ], + ]); + + if (!empty($profile)) { + throw new Exception('User already registered', 400); + } + + // TODO after this user needs to confirm mail again + + $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ + 'email' => $email, + 'emailVerification' => false, + ])); + + if (false === $user) { + throw new Exception('Failed saving user to DB', 500); + } + + $audit + ->setParam('userId', $user->getId()) + ->setParam('event', 'account.update.email') + ->setParam('resource', 'users/'.$user->getId()) + ; + + $response->json(\array_merge($user->getArrayCopy(\array_merge( + [ + '$id', + 'email', + 'registration', + 'name', + ], + $oauth2Keys + )), ['roles' => Authorization::getRoles()])); + }, ['response', 'user', 'projectDB', 'audit']); App::patch('/v1/account/prefs') ->desc('Update Account Preferences') @@ -857,36 +880,39 @@ App::patch('/v1/account/prefs') ->label('sdk.method', 'updatePrefs') ->param('prefs', '', function () { return new Assoc();}, 'Prefs key-value JSON object.') ->label('sdk.description', '/docs/references/account/update-prefs.md') - ->action( - function ($prefs) use ($response, $user, $projectDB, $audit) { - $old = \json_decode($user->getAttribute('prefs', '{}'), true); - $old = ($old) ? $old : []; + ->action(function ($prefs, $response, $user, $projectDB, $audit) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $user */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $audit */ - $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ - 'prefs' => \json_encode(\array_merge($old, $prefs)), - ])); + $old = \json_decode($user->getAttribute('prefs', '{}'), true); + $old = ($old) ? $old : []; - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } + $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ + 'prefs' => \json_encode(\array_merge($old, $prefs)), + ])); - $audit - ->setParam('event', 'account.update.prefs') - ->setParam('resource', 'users/'.$user->getId()) - ; - - $prefs = $user->getAttribute('prefs', '{}'); - - try { - $prefs = \json_decode($prefs, true); - $prefs = ($prefs) ? $prefs : []; - } catch (\Exception $error) { - throw new Exception('Failed to parse prefs', 500); - } - - $response->json($prefs); + if (false === $user) { + throw new Exception('Failed saving user to DB', 500); } - ); + + $audit + ->setParam('event', 'account.update.prefs') + ->setParam('resource', 'users/'.$user->getId()) + ; + + $prefs = $user->getAttribute('prefs', '{}'); + + try { + $prefs = \json_decode($prefs, true); + $prefs = ($prefs) ? $prefs : []; + } catch (\Exception $error) { + throw new Exception('Failed to parse prefs', 500); + } + + $response->json($prefs); + }, ['response', 'user', 'projectDB', 'audit']); App::delete('/v1/account') ->desc('Delete Account') @@ -897,52 +923,56 @@ App::delete('/v1/account') ->label('sdk.namespace', 'account') ->label('sdk.method', 'delete') ->label('sdk.description', '/docs/references/account/delete.md') - ->action( - function () use ($response, $user, $projectDB, $audit, $webhook) { - $protocol = Config::getParam('protocol'); - $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ - 'status' => Auth::USER_STATUS_BLOCKED, - ])); + ->action(function ($response, $user, $projectDB, $audit, $webhook) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $user */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $audit */ + /** @var Appwrite\Event\Event $webhook */ - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } + $protocol = Config::getParam('protocol'); + $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ + 'status' => Auth::USER_STATUS_BLOCKED, + ])); - //TODO delete all tokens or only current session? - //TODO delete all user data according to GDPR. Make sure everything is backed up and backups are deleted later - /* - * Data to delete - * * Tokens - * * Memberships - */ + if (false === $user) { + throw new Exception('Failed saving user to DB', 500); + } - $audit - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.delete') - ->setParam('resource', 'users/'.$user->getId()) - ->setParam('data', $user->getArrayCopy()) - ; + //TODO delete all tokens or only current session? + //TODO delete all user data according to GDPR. Make sure everything is backed up and backups are deleted later + /* + * Data to delete + * * Tokens + * * Memberships + */ - $webhook - ->setParam('payload', [ - 'name' => $user->getAttribute('name', ''), - 'email' => $user->getAttribute('email', ''), - ]) - ; + $audit + ->setParam('userId', $user->getId()) + ->setParam('event', 'account.delete') + ->setParam('resource', 'users/'.$user->getId()) + ->setParam('data', $user->getArrayCopy()) + ; - if (!Config::getParam('domainVerification')) { - $response - ->addHeader('X-Fallback-Cookies', \json_encode([])) - ; - } + $webhook + ->setParam('payload', [ + 'name' => $user->getAttribute('name', ''), + 'email' => $user->getAttribute('email', ''), + ]) + ; + if (!Config::getParam('domainVerification')) { $response - ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) - ->noContent() + ->addHeader('X-Fallback-Cookies', \json_encode([])) ; } - ); + + $response + ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) + ->noContent() + ; + }, ['response', 'user', 'projectDB', 'audit', 'webhook']); App::delete('/v1/account/sessions/:sessionId') ->desc('Delete Account Session') @@ -955,71 +985,22 @@ App::delete('/v1/account/sessions/:sessionId') ->label('sdk.description', '/docs/references/account/delete-session.md') ->label('abuse-limit', 100) ->param('sessionId', null, function () { return new UID(); }, 'Session unique ID. Use the string \'current\' to delete the current device session.') - ->action( - function ($sessionId) use ($response, $user, $projectDB, $webhook, $audit) { - $protocol = Config::getParam('protocol'); - $sessionId = ($sessionId === 'current') - ? Auth::tokenVerify($user->getAttribute('tokens'), Auth::TOKEN_TYPE_LOGIN, Auth::$secret) - : $sessionId; - - $tokens = $user->getAttribute('tokens', []); + ->action(function ($sessionId, $response, $user, $projectDB, $audit, $webhook) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $user */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $audit */ + /** @var Appwrite\Event\Event $webhook */ - foreach ($tokens as $token) { /* @var $token Document */ - if (($sessionId == $token->getId()) && Auth::TOKEN_TYPE_LOGIN == $token->getAttribute('type')) { - if (!$projectDB->deleteDocument($token->getId())) { - throw new Exception('Failed to remove token from DB', 500); - } + $protocol = Config::getParam('protocol'); + $sessionId = ($sessionId === 'current') + ? Auth::tokenVerify($user->getAttribute('tokens'), Auth::TOKEN_TYPE_LOGIN, Auth::$secret) + : $sessionId; + + $tokens = $user->getAttribute('tokens', []); - $audit - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.sessions.delete') - ->setParam('resource', '/user/'.$user->getId()) - ; - - $webhook - ->setParam('payload', [ - 'name' => $user->getAttribute('name', ''), - 'email' => $user->getAttribute('email', ''), - ]) - ; - - if (!Config::getParam('domainVerification')) { - $response - ->addHeader('X-Fallback-Cookies', \json_encode([])) - ; - } - - if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too - $response - ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) - ; - } - - return $response->noContent(); - } - } - - throw new Exception('Session not found', 404); - } - ); - -App::delete('/v1/account/sessions') - ->desc('Delete All Account Sessions') - ->groups(['api', 'account']) - ->label('scope', 'account') - ->label('webhook', 'account.sessions.delete') - ->label('sdk.platform', [APP_PLATFORM_CLIENT]) - ->label('sdk.namespace', 'account') - ->label('sdk.method', 'deleteSessions') - ->label('sdk.description', '/docs/references/account/delete-sessions.md') - ->label('abuse-limit', 100) - ->action( - function () use ($response, $user, $projectDB, $audit, $webhook) { - $protocol = Config::getParam('protocol'); - $tokens = $user->getAttribute('tokens', []); - - foreach ($tokens as $token) { /* @var $token Document */ + foreach ($tokens as $token) { /* @var $token Document */ + if (($sessionId == $token->getId()) && Auth::TOKEN_TYPE_LOGIN == $token->getAttribute('type')) { if (!$projectDB->deleteDocument($token->getId())) { throw new Exception('Failed to remove token from DB', 500); } @@ -1049,11 +1030,68 @@ App::delete('/v1/account/sessions') ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) ; } + + return $response->noContent(); + } + } + + throw new Exception('Session not found', 404); + }, ['response', 'user', 'projectDB', 'audit', 'webhook']); + +App::delete('/v1/account/sessions') + ->desc('Delete All Account Sessions') + ->groups(['api', 'account']) + ->label('scope', 'account') + ->label('webhook', 'account.sessions.delete') + ->label('sdk.platform', [APP_PLATFORM_CLIENT]) + ->label('sdk.namespace', 'account') + ->label('sdk.method', 'deleteSessions') + ->label('sdk.description', '/docs/references/account/delete-sessions.md') + ->label('abuse-limit', 100) + ->action(function ($response, $user, $projectDB, $audit, $webhook) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $user */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $audit */ + /** @var Appwrite\Event\Event $webhook */ + + $protocol = Config::getParam('protocol'); + $tokens = $user->getAttribute('tokens', []); + + foreach ($tokens as $token) { /* @var $token Document */ + if (!$projectDB->deleteDocument($token->getId())) { + throw new Exception('Failed to remove token from DB', 500); } - $response->noContent(); + $audit + ->setParam('userId', $user->getId()) + ->setParam('event', 'account.sessions.delete') + ->setParam('resource', '/user/'.$user->getId()) + ; + + $webhook + ->setParam('payload', [ + 'name' => $user->getAttribute('name', ''), + 'email' => $user->getAttribute('email', ''), + ]) + ; + + if (!Config::getParam('domainVerification')) { + $response + ->addHeader('X-Fallback-Cookies', \json_encode([])) + ; + } + + if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too + $response + ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) + ; + } } - ); + + $response->noContent(); + }, ['response', 'user', 'projectDB', 'audit', 'webhook']); App::post('/v1/account/recovery') ->desc('Create Password Recovery') @@ -1066,93 +1104,99 @@ App::post('/v1/account/recovery') ->label('abuse-limit', 10) ->label('abuse-key', 'url:{url},email:{param-email}') ->param('email', '', function () { return new Email(); }, 'User email.') - ->param('url', '', function () use ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.') - ->action( - function ($email, $url) use ($request, $response, $projectDB, $mail, $audit, $project) { - $profile = $projectDB->getCollectionFirst([ // Get user by email address - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'email='.$email, - ], - ]); + ->param('url', '', function ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients']) + ->action(function ($email, $url, $request, $response, $projectDB, $project, $locale, $mail, $audit) { + /** @var Utopia\Request $request */ + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Database\Document $project */ + /** @var Utopia\Locale\Locale $locale */ + /** @var Appwrite\Event\Event $mail */ + /** @var Appwrite\Event\Event $audit */ - if (empty($profile)) { - throw new Exception('User not found', 404); // TODO maybe hide this - } + $profile = $projectDB->getCollectionFirst([ // Get user by email address + 'limit' => 1, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_USERS, + 'email='.$email, + ], + ]); - $secret = Auth::tokenGenerator(); - $recovery = new Document([ - '$collection' => Database::SYSTEM_COLLECTION_TOKENS, - '$permissions' => ['read' => ['user:'.$profile->getId()], 'write' => ['user:'.$profile->getId()]], - 'type' => Auth::TOKEN_TYPE_RECOVERY, - 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak - 'expire' => \time() + Auth::TOKEN_EXPIRATION_RECOVERY, - 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), - 'ip' => $request->getIP(), - ]); - - Authorization::setRole('user:'.$profile->getId()); - - $recovery = $projectDB->createDocument($recovery->getArrayCopy()); - - if (false === $recovery) { - throw new Exception('Failed saving recovery to DB', 500); - } - - $profile->setAttribute('tokens', $recovery, Document::SET_TYPE_APPEND); - - $profile = $projectDB->updateDocument($profile->getArrayCopy()); - - if (false === $profile) { - throw new Exception('Failed to save user to DB', 500); - } - - $url = Template::parseURL($url); - $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret]); - $url = Template::unParseURL($url); - - $body = new Template(__DIR__.'/../../config/locales/templates/_base.tpl'); - $content = new Template(__DIR__.'/../../config/locales/templates/'.Locale::getText('account.emails.recovery.body')); - $cta = new Template(__DIR__.'/../../config/locales/templates/_cta.tpl'); - - $body - ->setParam('{{content}}', $content->render()) - ->setParam('{{cta}}', $cta->render()) - ->setParam('{{title}}', Locale::getText('account.emails.recovery.title')) - ->setParam('{{direction}}', Locale::getText('settings.direction')) - ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]'])) - ->setParam('{{name}}', $profile->getAttribute('name')) - ->setParam('{{redirect}}', $url) - ->setParam('{{bg-body}}', '#f6f6f6') - ->setParam('{{bg-content}}', '#ffffff') - ->setParam('{{bg-cta}}', '#3498db') - ->setParam('{{bg-cta-hover}}', '#34495e') - ->setParam('{{text-content}}', '#000000') - ->setParam('{{text-cta}}', '#ffffff') - ; - - $mail - ->setParam('event', 'account.recovery.create') - ->setParam('recipient', $profile->getAttribute('email', '')) - ->setParam('name', $profile->getAttribute('name', '')) - ->setParam('subject', Locale::getText('account.emails.recovery.title')) - ->setParam('body', $body->render()) - ->trigger(); - ; - - $audit - ->setParam('userId', $profile->getId()) - ->setParam('event', 'account.recovery.create') - ->setParam('resource', 'users/'.$profile->getId()) - ; - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($recovery->getArrayCopy(['$id', 'type', 'expire'])) - ; + if (empty($profile)) { + throw new Exception('User not found', 404); // TODO maybe hide this } - ); + + $secret = Auth::tokenGenerator(); + $recovery = new Document([ + '$collection' => Database::SYSTEM_COLLECTION_TOKENS, + '$permissions' => ['read' => ['user:'.$profile->getId()], 'write' => ['user:'.$profile->getId()]], + 'type' => Auth::TOKEN_TYPE_RECOVERY, + 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak + 'expire' => \time() + Auth::TOKEN_EXPIRATION_RECOVERY, + 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), + 'ip' => $request->getIP(), + ]); + + Authorization::setRole('user:'.$profile->getId()); + + $recovery = $projectDB->createDocument($recovery->getArrayCopy()); + + if (false === $recovery) { + throw new Exception('Failed saving recovery to DB', 500); + } + + $profile->setAttribute('tokens', $recovery, Document::SET_TYPE_APPEND); + + $profile = $projectDB->updateDocument($profile->getArrayCopy()); + + if (false === $profile) { + throw new Exception('Failed to save user to DB', 500); + } + + $url = Template::parseURL($url); + $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret]); + $url = Template::unParseURL($url); + + $body = new Template(__DIR__.'/../../config/locales/templates/_base.tpl'); + $content = new Template(__DIR__.'/../../config/locales/templates/'.$locale->getText('account.emails.recovery.body')); + $cta = new Template(__DIR__.'/../../config/locales/templates/_cta.tpl'); + + $body + ->setParam('{{content}}', $content->render()) + ->setParam('{{cta}}', $cta->render()) + ->setParam('{{title}}', $locale->getText('account.emails.recovery.title')) + ->setParam('{{direction}}', $locale->getText('settings.direction')) + ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]'])) + ->setParam('{{name}}', $profile->getAttribute('name')) + ->setParam('{{redirect}}', $url) + ->setParam('{{bg-body}}', '#f6f6f6') + ->setParam('{{bg-content}}', '#ffffff') + ->setParam('{{bg-cta}}', '#3498db') + ->setParam('{{bg-cta-hover}}', '#34495e') + ->setParam('{{text-content}}', '#000000') + ->setParam('{{text-cta}}', '#ffffff') + ; + + $mail + ->setParam('event', 'account.recovery.create') + ->setParam('recipient', $profile->getAttribute('email', '')) + ->setParam('name', $profile->getAttribute('name', '')) + ->setParam('subject', $locale->getText('account.emails.recovery.title')) + ->setParam('body', $body->render()) + ->trigger(); + ; + + $audit + ->setParam('userId', $profile->getId()) + ->setParam('event', 'account.recovery.create') + ->setParam('resource', 'users/'.$profile->getId()) + ; + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->json($recovery->getArrayCopy(['$id', 'type', 'expire'])) + ; + }, ['request', 'response', 'projectDB', 'project', 'locale', 'mail', 'audit']); App::put('/v1/account/recovery') ->desc('Complete Password Recovery') @@ -1168,61 +1212,63 @@ App::put('/v1/account/recovery') ->param('secret', '', function () { return new Text(256); }, 'Valid reset token.') ->param('password', '', function () { return new Password(); }, 'New password. Must be between 6 to 32 chars.') ->param('passwordAgain', '', function () {return new Password(); }, 'New password again. Must be between 6 to 32 chars.') - ->action( - function ($userId, $secret, $password, $passwordAgain) use ($response, $projectDB, $audit) { - if ($password !== $passwordAgain) { - throw new Exception('Passwords must match', 400); - } - - $profile = $projectDB->getCollectionFirst([ // Get user by email address - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - '$id='.$userId, - ], - ]); - - if (empty($profile)) { - throw new Exception('User not found', 404); // TODO maybe hide this - } - - $recovery = Auth::tokenVerify($profile->getAttribute('tokens', []), Auth::TOKEN_TYPE_RECOVERY, $secret); - - if (!$recovery) { - throw new Exception('Invalid recovery token', 401); - } - - Authorization::setRole('user:'.$profile->getId()); - - $profile = $projectDB->updateDocument(\array_merge($profile->getArrayCopy(), [ - 'password' => Auth::passwordHash($password), - 'password-update' => \time(), - 'emailVerification' => true, - ])); - - if (false === $profile) { - throw new Exception('Failed saving user to DB', 500); - } - - /** - * We act like we're updating and validating - * the recovery token but actually we don't need it anymore. - */ - if (!$projectDB->deleteDocument($recovery)) { - throw new Exception('Failed to remove recovery from DB', 500); - } - - $audit - ->setParam('userId', $profile->getId()) - ->setParam('event', 'account.recovery.update') - ->setParam('resource', 'users/'.$profile->getId()) - ; - - $recovery = $profile->search('$id', $recovery, $profile->getAttribute('tokens', [])); - - $response->json($recovery->getArrayCopy(['$id', 'type', 'expire'])); + ->action(function ($userId, $secret, $password, $passwordAgain, $response, $projectDB, $audit) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $audit */ + + if ($password !== $passwordAgain) { + throw new Exception('Passwords must match', 400); } - ); + + $profile = $projectDB->getCollectionFirst([ // Get user by email address + 'limit' => 1, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_USERS, + '$id='.$userId, + ], + ]); + + if (empty($profile)) { + throw new Exception('User not found', 404); // TODO maybe hide this + } + + $recovery = Auth::tokenVerify($profile->getAttribute('tokens', []), Auth::TOKEN_TYPE_RECOVERY, $secret); + + if (!$recovery) { + throw new Exception('Invalid recovery token', 401); + } + + Authorization::setRole('user:'.$profile->getId()); + + $profile = $projectDB->updateDocument(\array_merge($profile->getArrayCopy(), [ + 'password' => Auth::passwordHash($password), + 'password-update' => \time(), + 'emailVerification' => true, + ])); + + if (false === $profile) { + throw new Exception('Failed saving user to DB', 500); + } + + /** + * We act like we're updating and validating + * the recovery token but actually we don't need it anymore. + */ + if (!$projectDB->deleteDocument($recovery)) { + throw new Exception('Failed to remove recovery from DB', 500); + } + + $audit + ->setParam('userId', $profile->getId()) + ->setParam('event', 'account.recovery.update') + ->setParam('resource', 'users/'.$profile->getId()) + ; + + $recovery = $profile->search('$id', $recovery, $profile->getAttribute('tokens', [])); + + $response->json($recovery->getArrayCopy(['$id', 'type', 'expire'])); + }, ['response', 'projectDB', 'audit']); App::post('/v1/account/verification') ->desc('Create Email Verification') @@ -1234,82 +1280,89 @@ App::post('/v1/account/verification') ->label('sdk.description', '/docs/references/account/create-verification.md') ->label('abuse-limit', 10) ->label('abuse-key', 'url:{url},email:{param-email}') - ->param('url', '', function () use ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.') // TODO add built-in confirm page - ->action( - function ($url) use ($request, $response, $mail, $user, $project, $projectDB, $audit) { - $verificationSecret = Auth::tokenGenerator(); + ->param('url', '', function ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the verification email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients']) // TODO add built-in confirm page + ->action(function ($url, $request, $response, $project, $user, $projectDB, $locale, $audit, $mail) { + /** @var Utopia\Request $request */ + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $project */ + /** @var Appwrite\Database\Document $user */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Locale\Locale $locale */ + /** @var Appwrite\Event\Event $audit */ + /** @var Appwrite\Event\Event $mail */ + + $verificationSecret = Auth::tokenGenerator(); + + $verification = new Document([ + '$collection' => Database::SYSTEM_COLLECTION_TOKENS, + '$permissions' => ['read' => ['user:'.$user->getId()], 'write' => ['user:'.$user->getId()]], + 'type' => Auth::TOKEN_TYPE_VERIFICATION, + 'secret' => Auth::hash($verificationSecret), // On way hash encryption to protect DB leak + 'expire' => \time() + Auth::TOKEN_EXPIRATION_CONFIRM, + 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), + 'ip' => $request->getIP(), + ]); - $verification = new Document([ - '$collection' => Database::SYSTEM_COLLECTION_TOKENS, - '$permissions' => ['read' => ['user:'.$user->getId()], 'write' => ['user:'.$user->getId()]], - 'type' => Auth::TOKEN_TYPE_VERIFICATION, - 'secret' => Auth::hash($verificationSecret), // On way hash encryption to protect DB leak - 'expire' => \time() + Auth::TOKEN_EXPIRATION_CONFIRM, - 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), - 'ip' => $request->getIP(), - ]); - - Authorization::setRole('user:'.$user->getId()); + Authorization::setRole('user:'.$user->getId()); - $verification = $projectDB->createDocument($verification->getArrayCopy()); + $verification = $projectDB->createDocument($verification->getArrayCopy()); - if (false === $verification) { - throw new Exception('Failed saving verification to DB', 500); - } - - $user->setAttribute('tokens', $verification, Document::SET_TYPE_APPEND); - - $user = $projectDB->updateDocument($user->getArrayCopy()); - - if (false === $user) { - throw new Exception('Failed to save user to DB', 500); - } - - $url = Template::parseURL($url); - $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $verificationSecret]); - $url = Template::unParseURL($url); - - $body = new Template(__DIR__.'/../../config/locales/templates/_base.tpl'); - $content = new Template(__DIR__.'/../../config/locales/templates/'.Locale::getText('account.emails.verification.body')); - $cta = new Template(__DIR__.'/../../config/locales/templates/_cta.tpl'); - - $body - ->setParam('{{content}}', $content->render()) - ->setParam('{{cta}}', $cta->render()) - ->setParam('{{title}}', Locale::getText('account.emails.verification.title')) - ->setParam('{{direction}}', Locale::getText('settings.direction')) - ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]'])) - ->setParam('{{name}}', $user->getAttribute('name')) - ->setParam('{{redirect}}', $url) - ->setParam('{{bg-body}}', '#f6f6f6') - ->setParam('{{bg-content}}', '#ffffff') - ->setParam('{{bg-cta}}', '#3498db') - ->setParam('{{bg-cta-hover}}', '#34495e') - ->setParam('{{text-content}}', '#000000') - ->setParam('{{text-cta}}', '#ffffff') - ; - - $mail - ->setParam('event', 'account.verification.create') - ->setParam('recipient', $user->getAttribute('email')) - ->setParam('name', $user->getAttribute('name')) - ->setParam('subject', Locale::getText('account.emails.verification.title')) - ->setParam('body', $body->render()) - ->trigger() - ; - - $audit - ->setParam('userId', $user->getId()) - ->setParam('event', 'account.verification.create') - ->setParam('resource', 'users/'.$user->getId()) - ; - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($verification->getArrayCopy(['$id', 'type', 'expire'])) - ; + if (false === $verification) { + throw new Exception('Failed saving verification to DB', 500); } - ); + + $user->setAttribute('tokens', $verification, Document::SET_TYPE_APPEND); + + $user = $projectDB->updateDocument($user->getArrayCopy()); + + if (false === $user) { + throw new Exception('Failed to save user to DB', 500); + } + + $url = Template::parseURL($url); + $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $verificationSecret]); + $url = Template::unParseURL($url); + + $body = new Template(__DIR__.'/../../config/locales/templates/_base.tpl'); + $content = new Template(__DIR__.'/../../config/locales/templates/'.$locale->getText('account.emails.verification.body')); + $cta = new Template(__DIR__.'/../../config/locales/templates/_cta.tpl'); + + $body + ->setParam('{{content}}', $content->render()) + ->setParam('{{cta}}', $cta->render()) + ->setParam('{{title}}', $locale->getText('account.emails.verification.title')) + ->setParam('{{direction}}', $locale->getText('settings.direction')) + ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]'])) + ->setParam('{{name}}', $user->getAttribute('name')) + ->setParam('{{redirect}}', $url) + ->setParam('{{bg-body}}', '#f6f6f6') + ->setParam('{{bg-content}}', '#ffffff') + ->setParam('{{bg-cta}}', '#3498db') + ->setParam('{{bg-cta-hover}}', '#34495e') + ->setParam('{{text-content}}', '#000000') + ->setParam('{{text-cta}}', '#ffffff') + ; + + $mail + ->setParam('event', 'account.verification.create') + ->setParam('recipient', $user->getAttribute('email')) + ->setParam('name', $user->getAttribute('name')) + ->setParam('subject', $locale->getText('account.emails.verification.title')) + ->setParam('body', $body->render()) + ->trigger() + ; + + $audit + ->setParam('userId', $user->getId()) + ->setParam('event', 'account.verification.create') + ->setParam('resource', 'users/'.$user->getId()) + ; + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->json($verification->getArrayCopy(['$id', 'type', 'expire'])) + ; + }, ['request', 'response', 'project', 'user', 'projectDB', 'locale', 'audit', 'mail']); App::put('/v1/account/verification') ->desc('Complete Email Verification') @@ -1323,52 +1376,55 @@ App::put('/v1/account/verification') ->label('abuse-key', 'url:{url},userId:{param-userId}') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->param('secret', '', function () { return new Text(256); }, 'Valid verification token.') - ->action( - function ($userId, $secret) use ($response, $user, $projectDB, $audit) { - $profile = $projectDB->getCollectionFirst([ // Get user by email address - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - '$id='.$userId, - ], - ]); + ->action(function ($userId, $secret, $response, $user, $projectDB, $audit) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $user */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $audit */ - if (empty($profile)) { - throw new Exception('User not found', 404); // TODO maybe hide this - } + $profile = $projectDB->getCollectionFirst([ // Get user by email address + 'limit' => 1, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_USERS, + '$id='.$userId, + ], + ]); - $verification = Auth::tokenVerify($profile->getAttribute('tokens', []), Auth::TOKEN_TYPE_VERIFICATION, $secret); - - if (!$verification) { - throw new Exception('Invalid verification token', 401); - } - - Authorization::setRole('user:'.$profile->getId()); - - $profile = $projectDB->updateDocument(\array_merge($profile->getArrayCopy(), [ - 'emailVerification' => true, - ])); - - if (false === $profile) { - throw new Exception('Failed saving user to DB', 500); - } - - /** - * We act like we're updating and validating - * the verification token but actually we don't need it anymore. - */ - if (!$projectDB->deleteDocument($verification)) { - throw new Exception('Failed to remove verification from DB', 500); - } - - $audit - ->setParam('userId', $profile->getId()) - ->setParam('event', 'account.verification.update') - ->setParam('resource', 'users/'.$user->getId()) - ; - - $verification = $profile->search('$id', $verification, $profile->getAttribute('tokens', [])); - - $response->json($verification->getArrayCopy(['$id', 'type', 'expire'])); + if (empty($profile)) { + throw new Exception('User not found', 404); // TODO maybe hide this } - ); \ No newline at end of file + + $verification = Auth::tokenVerify($profile->getAttribute('tokens', []), Auth::TOKEN_TYPE_VERIFICATION, $secret); + + if (!$verification) { + throw new Exception('Invalid verification token', 401); + } + + Authorization::setRole('user:'.$profile->getId()); + + $profile = $projectDB->updateDocument(\array_merge($profile->getArrayCopy(), [ + 'emailVerification' => true, + ])); + + if (false === $profile) { + throw new Exception('Failed saving user to DB', 500); + } + + /** + * We act like we're updating and validating + * the verification token but actually we don't need it anymore. + */ + if (!$projectDB->deleteDocument($verification)) { + throw new Exception('Failed to remove verification from DB', 500); + } + + $audit + ->setParam('userId', $profile->getId()) + ->setParam('event', 'account.verification.update') + ->setParam('resource', 'users/'.$user->getId()) + ; + + $verification = $profile->search('$id', $verification, $profile->getAttribute('tokens', [])); + + $response->json($verification->getArrayCopy(['$id', 'type', 'expire'])); + }, ['response', 'user', 'projectDB', 'audit']); \ No newline at end of file diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index 9aa07827f0..850a3a3ed2 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -1,7 +1,5 @@ desc('Get Credit Card Icon') ->groups(['api', 'avatars']) - ->param('code', '', function () { return new WhiteList(\array_keys(Config::getParam('avatar-credit-cards'))); }, 'Credit Card Code. Possible values: '.\implode(', ', \array_keys(Config::getParam('avatar-credit-cards'))).'.') - ->param('width', 100, function () { return new Range(0, 2000); }, 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true) - ->param('height', 100, function () { return new Range(0, 2000); }, 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true) - ->param('quality', 100, function () { return new Range(0, 100); }, 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true) ->label('scope', 'avatars.read') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'avatars') ->label('sdk.method', 'getCreditCard') ->label('sdk.methodType', 'location') ->label('sdk.description', '/docs/references/avatars/get-credit-card.md') - ->action(function ($code, $width, $height, $quality) use ($avatarCallback) { - return $avatarCallback('credit-cards', $code, $width, $height, $quality); - }); + ->param('code', '', function () { return new WhiteList(\array_keys(Config::getParam('avatar-credit-cards'))); }, 'Credit Card Code. Possible values: '.\implode(', ', \array_keys(Config::getParam('avatar-credit-cards'))).'.') + ->param('width', 100, function () { return new Range(0, 2000); }, 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true) + ->param('height', 100, function () { return new Range(0, 2000); }, 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true) + ->param('quality', 100, function () { return new Range(0, 100); }, 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true) + ->action(function ($code, $width, $height, $quality, $response) use ($avatarCallback) { + return $avatarCallback('credit-cards', $code, $width, $height, $quality, $response); + }, ['response']); App::get('/v1/avatars/browsers/:code') ->desc('Get Browser Icon') ->groups(['api', 'avatars']) - ->param('code', '', function () { return new WhiteList(\array_keys(Config::getParam('avatar-browsers'))); }, 'Browser Code.') - ->param('width', 100, function () { return new Range(0, 2000); }, 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true) - ->param('height', 100, function () { return new Range(0, 2000); }, 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true) - ->param('quality', 100, function () { return new Range(0, 100); }, 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true) ->label('scope', 'avatars.read') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'avatars') ->label('sdk.method', 'getBrowser') ->label('sdk.methodType', 'location') ->label('sdk.description', '/docs/references/avatars/get-browser.md') - ->action(function ($code, $width, $height, $quality) use ($avatarCallback) { - return $avatarCallback('browsers', $code, $width, $height, $quality); - }); + ->param('code', '', function () { return new WhiteList(\array_keys(Config::getParam('avatar-browsers'))); }, 'Browser Code.') + ->param('width', 100, function () { return new Range(0, 2000); }, 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true) + ->param('height', 100, function () { return new Range(0, 2000); }, 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true) + ->param('quality', 100, function () { return new Range(0, 100); }, 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true) + ->action(function ($code, $width, $height, $quality, $response) use ($avatarCallback) { + return $avatarCallback('browsers', $code, $width, $height, $quality, $response); + }, ['response']); App::get('/v1/avatars/flags/:code') ->desc('Get Country Flag') ->groups(['api', 'avatars']) - ->param('code', '', function () { return new WhiteList(\array_keys(Config::getParam('avatar-flags'))); }, 'Country Code. ISO Alpha-2 country code format.') - ->param('width', 100, function () { return new Range(0, 2000); }, 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true) - ->param('height', 100, function () { return new Range(0, 2000); }, 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true) - ->param('quality', 100, function () { return new Range(0, 100); }, 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true) ->label('scope', 'avatars.read') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'avatars') ->label('sdk.method', 'getFlag') ->label('sdk.methodType', 'location') ->label('sdk.description', '/docs/references/avatars/get-flag.md') - ->action(function ($code, $width, $height, $quality) use ($avatarCallback) { - return $avatarCallback('flags', $code, $width, $height, $quality); - }); + ->param('code', '', function () { return new WhiteList(\array_keys(Config::getParam('avatar-flags'))); }, 'Country Code. ISO Alpha-2 country code format.') + ->param('width', 100, function () { return new Range(0, 2000); }, 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true) + ->param('height', 100, function () { return new Range(0, 2000); }, 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true) + ->param('quality', 100, function () { return new Range(0, 100); }, 'Image quality. Pass an integer between 0 to 100. Defaults to 100.', true) + ->action(function ($code, $width, $height, $quality, $response) use ($avatarCallback) { + return $avatarCallback('flags', $code, $width, $height, $quality, $response); + }, ['response']); App::get('/v1/avatars/image') ->desc('Get Image from URL') ->groups(['api', 'avatars']) - ->param('url', '', function () { return new URL(); }, 'Image URL which you want to crop.') - ->param('width', 400, function () { return new Range(0, 2000); }, 'Resize preview image width, Pass an integer between 0 to 2000.', true) - ->param('height', 400, function () { return new Range(0, 2000); }, 'Resize preview image height, Pass an integer between 0 to 2000.', true) ->label('scope', 'avatars.read') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'avatars') ->label('sdk.method', 'getImage') ->label('sdk.methodType', 'location') ->label('sdk.description', '/docs/references/avatars/get-image.md') - ->action( - function ($url, $width, $height) use ($response) { - $quality = 80; - $output = 'png'; - $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache - $key = \md5('/v2/avatars/images-'.$url.'-'.$width.'/'.$height.'/'.$quality); - $type = 'png'; - $cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-0')); // Limit file number or size - $data = $cache->load($key, 60 * 60 * 24 * 7 /* 1 week */); + ->param('url', '', function () { return new URL(); }, 'Image URL which you want to crop.') + ->param('width', 400, function () { return new Range(0, 2000); }, 'Resize preview image width, Pass an integer between 0 to 2000.', true) + ->param('height', 400, function () { return new Range(0, 2000); }, 'Resize preview image height, Pass an integer between 0 to 2000.', true) + ->action(function ($url, $width, $height, $response) { + /** @var Utopia\Response $response */ - if ($data) { - $response - ->setContentType('image/png') - ->addHeader('Expires', $date) - ->addHeader('X-Appwrite-Cache', 'hit') - ->send($data, 0) - ; - } - - if (!\extension_loaded('imagick')) { - throw new Exception('Imagick extension is missing', 500); - } - - $fetch = @\file_get_contents($url, false); - - if (!$fetch) { - throw new Exception('Image not found', 404); - } - - try { - $resize = new Resize($fetch); - } catch (\Exception $exception) { - throw new Exception('Unable to parse image', 500); - } - - $resize->crop((int) $width, (int) $height); - - $output = (empty($output)) ? $type : $output; + $quality = 80; + $output = 'png'; + $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache + $key = \md5('/v2/avatars/images-'.$url.'-'.$width.'/'.$height.'/'.$quality); + $type = 'png'; + $cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-0')); // Limit file number or size + $data = $cache->load($key, 60 * 60 * 24 * 7 /* 1 week */); + if ($data) { $response ->setContentType('image/png') ->addHeader('Expires', $date) - ->addHeader('X-Appwrite-Cache', 'miss') - ->send('', null) + ->addHeader('X-Appwrite-Cache', 'hit') + ->send($data, 0) ; - - $data = $resize->output($output, $quality); - - $cache->save($key, $data); - - echo $data; - - unset($resize); } - ); + + if (!\extension_loaded('imagick')) { + throw new Exception('Imagick extension is missing', 500); + } + + $fetch = @\file_get_contents($url, false); + + if (!$fetch) { + throw new Exception('Image not found', 404); + } + + try { + $resize = new Resize($fetch); + } catch (\Exception $exception) { + throw new Exception('Unable to parse image', 500); + } + + $resize->crop((int) $width, (int) $height); + + $output = (empty($output)) ? $type : $output; + + $response + ->setContentType('image/png') + ->addHeader('Expires', $date) + ->addHeader('X-Appwrite-Cache', 'miss') + ->send('', null) + ; + + $data = $resize->output($output, $quality); + + $cache->save($key, $data); + + echo $data; + + unset($resize); + }, ['response']); App::get('/v1/avatars/favicon') ->desc('Get Favicon') ->groups(['api', 'avatars']) - ->param('url', '', function () { return new URL(); }, 'Website URL which you want to fetch the favicon from.') ->label('scope', 'avatars.read') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'avatars') ->label('sdk.method', 'getFavicon') ->label('sdk.methodType', 'location') ->label('sdk.description', '/docs/references/avatars/get-favicon.md') - ->action( - function ($url) use ($response) { - $width = 56; - $height = 56; - $quality = 80; - $output = 'png'; - $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache - $key = \md5('/v2/avatars/favicon-'.$url); - $type = 'png'; - $cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-0')); // Limit file number or size - $data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */); + ->param('url', '', function () { return new URL(); }, 'Website URL which you want to fetch the favicon from.') + ->action(function ($url, $response) { + /** @var Utopia\Response $response */ - if ($data) { - $response - ->setContentType('image/png') - ->addHeader('Expires', $date) - ->addHeader('X-Appwrite-Cache', 'hit') - ->send($data, 0) - ; - } - - if (!\extension_loaded('imagick')) { - throw new Exception('Imagick extension is missing', 500); - } - - $curl = \curl_init(); - - \curl_setopt_array($curl, [ - CURLOPT_RETURNTRANSFER => 1, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_MAXREDIRS => 3, - CURLOPT_URL => $url, - CURLOPT_USERAGENT => \sprintf(APP_USERAGENT, - Config::getParam('version'), - App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY) - ), - ]); - - $html = \curl_exec($curl); - - \curl_close($curl); - - if (!$html) { - throw new Exception('Failed to fetch remote URL', 404); - } - - $doc = new DOMDocument(); - $doc->strictErrorChecking = false; - @$doc->loadHTML($html); - - $links = $doc->getElementsByTagName('link'); - $outputHref = ''; - $outputExt = ''; - $space = 0; - - foreach ($links as $link) { /* @var $link DOMElement */ - $href = $link->getAttribute('href'); - $rel = $link->getAttribute('rel'); - $sizes = $link->getAttribute('sizes'); - $absolute = URLParse::unparse(\array_merge(\parse_url($url), \parse_url($href))); - - switch (\strtolower($rel)) { - case 'icon': - case 'shortcut icon': - //case 'apple-touch-icon': - $ext = \pathinfo(\parse_url($absolute, PHP_URL_PATH), PATHINFO_EXTENSION); - - switch ($ext) { - case 'ico': - case 'png': - case 'jpg': - case 'jpeg': - $size = \explode('x', \strtolower($sizes)); - - $sizeWidth = (isset($size[0])) ? (int) $size[0] : 0; - $sizeHeight = (isset($size[1])) ? (int) $size[1] : 0; - - if (($sizeWidth * $sizeHeight) >= $space) { - $space = $sizeWidth * $sizeHeight; - $outputHref = $absolute; - $outputExt = $ext; - } - - break; - } - - break; - } - } - - if (empty($outputHref) || empty($outputExt)) { - $default = \parse_url($url); - - $outputHref = $default['scheme'].'://'.$default['host'].'/favicon.ico'; - $outputExt = 'ico'; - } - - if ('ico' == $outputExt) { // Skip crop, Imagick isn\'t supporting icon files - $data = @\file_get_contents($outputHref, false); - - if (empty($data) || (\mb_substr($data, 0, 5) === 'save($key, $data); - - $response - ->setContentType('image/x-icon') - ->addHeader('Expires', $date) - ->addHeader('X-Appwrite-Cache', 'miss') - ->send($data, 0) - ; - } - - $fetch = @\file_get_contents($outputHref, false); - - if (!$fetch) { - throw new Exception('Icon not found', 404); - } - - $resize = new Resize($fetch); - - $resize->crop((int) $width, (int) $height); - - $output = (empty($output)) ? $type : $output; + $width = 56; + $height = 56; + $quality = 80; + $output = 'png'; + $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache + $key = \md5('/v2/avatars/favicon-'.$url); + $type = 'png'; + $cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-0')); // Limit file number or size + $data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */); + if ($data) { $response ->setContentType('image/png') ->addHeader('Expires', $date) - ->addHeader('X-Appwrite-Cache', 'miss') - ->send('', null) + ->addHeader('X-Appwrite-Cache', 'hit') + ->send($data, 0) ; + } - $data = $resize->output($output, $quality); + if (!\extension_loaded('imagick')) { + throw new Exception('Imagick extension is missing', 500); + } + + $curl = \curl_init(); + + \curl_setopt_array($curl, [ + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_MAXREDIRS => 3, + CURLOPT_URL => $url, + CURLOPT_USERAGENT => \sprintf(APP_USERAGENT, + Config::getParam('version'), + App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY) + ), + ]); + + $html = \curl_exec($curl); + + \curl_close($curl); + + if (!$html) { + throw new Exception('Failed to fetch remote URL', 404); + } + + $doc = new DOMDocument(); + $doc->strictErrorChecking = false; + @$doc->loadHTML($html); + + $links = $doc->getElementsByTagName('link'); + $outputHref = ''; + $outputExt = ''; + $space = 0; + + foreach ($links as $link) { /* @var $link DOMElement */ + $href = $link->getAttribute('href'); + $rel = $link->getAttribute('rel'); + $sizes = $link->getAttribute('sizes'); + $absolute = URLParse::unparse(\array_merge(\parse_url($url), \parse_url($href))); + + switch (\strtolower($rel)) { + case 'icon': + case 'shortcut icon': + //case 'apple-touch-icon': + $ext = \pathinfo(\parse_url($absolute, PHP_URL_PATH), PATHINFO_EXTENSION); + + switch ($ext) { + case 'ico': + case 'png': + case 'jpg': + case 'jpeg': + $size = \explode('x', \strtolower($sizes)); + + $sizeWidth = (isset($size[0])) ? (int) $size[0] : 0; + $sizeHeight = (isset($size[1])) ? (int) $size[1] : 0; + + if (($sizeWidth * $sizeHeight) >= $space) { + $space = $sizeWidth * $sizeHeight; + $outputHref = $absolute; + $outputExt = $ext; + } + + break; + } + + break; + } + } + + if (empty($outputHref) || empty($outputExt)) { + $default = \parse_url($url); + + $outputHref = $default['scheme'].'://'.$default['host'].'/favicon.ico'; + $outputExt = 'ico'; + } + + if ('ico' == $outputExt) { // Skip crop, Imagick isn\'t supporting icon files + $data = @\file_get_contents($outputHref, false); + + if (empty($data) || (\mb_substr($data, 0, 5) === 'save($key, $data); - echo $data; - - unset($resize); + $response + ->setContentType('image/x-icon') + ->addHeader('Expires', $date) + ->addHeader('X-Appwrite-Cache', 'miss') + ->send($data, 0) + ; } - ); + + $fetch = @\file_get_contents($outputHref, false); + + if (!$fetch) { + throw new Exception('Icon not found', 404); + } + + $resize = new Resize($fetch); + + $resize->crop((int) $width, (int) $height); + + $output = (empty($output)) ? $type : $output; + + $response + ->setContentType('image/png') + ->addHeader('Expires', $date) + ->addHeader('X-Appwrite-Cache', 'miss') + ->send('', null) + ; + + $data = $resize->output($output, $quality); + + $cache->save($key, $data); + + echo $data; + + unset($resize); + }, ['response']); App::get('/v1/avatars/qr') ->desc('Get QR Code') ->groups(['api', 'avatars']) - ->param('text', '', function () { return new Text(512); }, 'Plain text to be converted to QR code image.') - ->param('size', 400, function () { return new Range(0, 1000); }, 'QR code size. Pass an integer between 0 to 1000. Defaults to 400.', true) - ->param('margin', 1, function () { return new Range(0, 10); }, 'Margin from edge. Pass an integer between 0 to 10. Defaults to 1.', true) - ->param('download', false, function () { return new Boolean(true); }, 'Return resulting image with \'Content-Disposition: attachment \' headers for the browser to start downloading it. Pass 0 for no header, or 1 for otherwise. Default value is set to 0.', true) ->label('scope', 'avatars.read') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'avatars') ->label('sdk.method', 'getQR') ->label('sdk.methodType', 'location') ->label('sdk.description', '/docs/references/avatars/get-qr.md') - ->action( - function ($text, $size, $margin, $download) use ($response) { - $download = ($download === '1' || $download === 'true' || $download === 1 || $download === true); + ->param('text', '', function () { return new Text(512); }, 'Plain text to be converted to QR code image.') + ->param('size', 400, function () { return new Range(0, 1000); }, 'QR code size. Pass an integer between 0 to 1000. Defaults to 400.', true) + ->param('margin', 1, function () { return new Range(0, 10); }, 'Margin from edge. Pass an integer between 0 to 10. Defaults to 1.', true) + ->param('download', false, function () { return new Boolean(true); }, 'Return resulting image with \'Content-Disposition: attachment \' headers for the browser to start downloading it. Pass 0 for no header, or 1 for otherwise. Default value is set to 0.', true) + ->action(function ($text, $size, $margin, $download, $response) { + /** @var Utopia\Response $response */ - $renderer = new ImageRenderer( - new RendererStyle($size, $margin), - new ImagickImageBackEnd('png', 100) - ); + $download = ($download === '1' || $download === 'true' || $download === 1 || $download === true); - $writer = new Writer($renderer); + $renderer = new ImageRenderer( + new RendererStyle($size, $margin), + new ImagickImageBackEnd('png', 100) + ); - if ($download) { - $response->addHeader('Content-Disposition', 'attachment; filename="qr.png"'); - } + $writer = new Writer($renderer); - $response - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache - ->setContentType('image/png') - ->send($writer->writeString($text)) - ; + if ($download) { + $response->addHeader('Content-Disposition', 'attachment; filename="qr.png"'); } - ); + + $response + ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache + ->setContentType('image/png') + ->send($writer->writeString($text)) + ; + }, ['response']); App::get('/v1/avatars/initials') ->desc('Get User Initials') ->groups(['api', 'avatars']) - ->param('name', '', function () { return new Text(512); }, 'Full Name. When empty, current user name or email will be used.', true) - ->param('width', 500, function () { return new Range(0, 2000); }, 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true) - ->param('height', 500, function () { return new Range(0, 2000); }, 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true) - ->param('color', '', function () { return new HexColor(); }, 'Changes text color. By default a random color will be picked and stay will persistent to the given name.', true) - ->param('background', '', function () { return new HexColor(); }, 'Changes background color. By default a random color will be picked and stay will persistent to the given name.', true) ->label('scope', 'avatars.read') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'avatars') ->label('sdk.method', 'getInitials') ->label('sdk.methodType', 'location') ->label('sdk.description', '/docs/references/avatars/get-initials.md') - ->action( - function ($name, $width, $height, $color, $background) use ($response, $user) { - $themes = [ - ['color' => '#27005e', 'background' => '#e1d2f6'], // VIOLET - ['color' => '#5e2700', 'background' => '#f3d9c6'], // ORANGE - ['color' => '#006128', 'background' => '#c9f3c6'], // GREEN - ['color' => '#580061', 'background' => '#f2d1f5'], // FUSCHIA - ['color' => '#00365d', 'background' => '#c6e1f3'], // BLUE - ['color' => '#00075c', 'background' => '#d2d5f6'], // INDIGO - ['color' => '#610038', 'background' => '#f5d1e6'], // PINK - ['color' => '#386100', 'background' => '#dcf1bd'], // LIME - ['color' => '#615800', 'background' => '#f1ecba'], // YELLOW - ['color' => '#610008', 'background' => '#f6d2d5'] // RED - ]; + ->param('name', '', function () { return new Text(512); }, 'Full Name. When empty, current user name or email will be used.', true) + ->param('width', 500, function () { return new Range(0, 2000); }, 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true) + ->param('height', 500, function () { return new Range(0, 2000); }, 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true) + ->param('color', '', function () { return new HexColor(); }, 'Changes text color. By default a random color will be picked and stay will persistent to the given name.', true) + ->param('background', '', function () { return new HexColor(); }, 'Changes background color. By default a random color will be picked and stay will persistent to the given name.', true) + ->action(function ($name, $width, $height, $color, $background, $response, $user) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $user */ - $rand = \rand(0, \count($themes)-1); + $themes = [ + ['color' => '#27005e', 'background' => '#e1d2f6'], // VIOLET + ['color' => '#5e2700', 'background' => '#f3d9c6'], // ORANGE + ['color' => '#006128', 'background' => '#c9f3c6'], // GREEN + ['color' => '#580061', 'background' => '#f2d1f5'], // FUSCHIA + ['color' => '#00365d', 'background' => '#c6e1f3'], // BLUE + ['color' => '#00075c', 'background' => '#d2d5f6'], // INDIGO + ['color' => '#610038', 'background' => '#f5d1e6'], // PINK + ['color' => '#386100', 'background' => '#dcf1bd'], // LIME + ['color' => '#615800', 'background' => '#f1ecba'], // YELLOW + ['color' => '#610008', 'background' => '#f6d2d5'] // RED + ]; - $name = (!empty($name)) ? $name : $user->getAttribute('name', $user->getAttribute('email', '')); - $words = \explode(' ', \strtoupper($name)); - $initials = null; - $code = 0; + $rand = \rand(0, \count($themes)-1); - foreach ($words as $key => $w) { - $initials .= (isset($w[0])) ? $w[0] : ''; - $code += (isset($w[0])) ? \ord($w[0]) : 0; + $name = (!empty($name)) ? $name : $user->getAttribute('name', $user->getAttribute('email', '')); + $words = \explode(' ', \strtoupper($name)); + $initials = null; + $code = 0; - if ($key == 1) { - break; - } + foreach ($words as $key => $w) { + $initials .= (isset($w[0])) ? $w[0] : ''; + $code += (isset($w[0])) ? \ord($w[0]) : 0; + + if ($key == 1) { + break; } - - $length = \count($words); - $rand = \substr($code,-1); - $background = (!empty($background)) ? '#'.$background : $themes[$rand]['background']; - $color = (!empty($color)) ? '#'.$color : $themes[$rand]['color']; - - $image = new \Imagick(); - $draw = new \ImagickDraw(); - $fontSize = \min($width, $height) / 2; - - $draw->setFont(__DIR__."/../../../public/fonts/poppins-v9-latin-500.ttf"); - $image->setFont(__DIR__."/../../../public/fonts/poppins-v9-latin-500.ttf"); - - $draw->setFillColor(new \ImagickPixel($color)); - $draw->setFontSize($fontSize); - - $draw->setTextAlignment(\Imagick::ALIGN_CENTER); - $draw->annotation($width / 1.97, ($height / 2) + ($fontSize / 3), $initials); - - $image->newImage($width, $height, $background); - $image->setImageFormat("png"); - $image->drawImage($draw); - - //$image->setImageCompressionQuality(9 - round(($quality / 100) * 9)); - - $response - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache - ->setContentType('image/png') - ->send($image->getImageBlob()) - ; } - ); \ No newline at end of file + + $length = \count($words); + $rand = \substr($code,-1); + $background = (!empty($background)) ? '#'.$background : $themes[$rand]['background']; + $color = (!empty($color)) ? '#'.$color : $themes[$rand]['color']; + + $image = new \Imagick(); + $draw = new \ImagickDraw(); + $fontSize = \min($width, $height) / 2; + + $draw->setFont(__DIR__."/../../../public/fonts/poppins-v9-latin-500.ttf"); + $image->setFont(__DIR__."/../../../public/fonts/poppins-v9-latin-500.ttf"); + + $draw->setFillColor(new \ImagickPixel($color)); + $draw->setFontSize($fontSize); + + $draw->setTextAlignment(\Imagick::ALIGN_CENTER); + $draw->annotation($width / 1.97, ($height / 2) + ($fontSize / 3), $initials); + + $image->newImage($width, $height, $background); + $image->setImageFormat("png"); + $image->drawImage($draw); + + //$image->setImageCompressionQuality(9 - round(($quality / 100) * 9)); + + $response + ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache + ->setContentType('image/png') + ->send($image->getImageBlob()) + ; + }, ['response', 'user']); \ No newline at end of file diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index fc505bcf08..b3fcfc3c73 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -1,7 +1,5 @@ desc('Create Collection') ->groups(['api', 'database']) @@ -38,67 +33,70 @@ App::post('/v1/database/collections') ->param('name', '', function () { return new Text(256); }, 'Collection name.') ->param('read', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') ->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') - ->param('rules', [], function () use ($projectDB) { return new ArrayList(new Collection($projectDB, [Database::SYSTEM_COLLECTION_RULES], ['$collection' => Database::SYSTEM_COLLECTION_RULES, '$permissions' => ['read' => [], 'write' => []]])); }, 'Array of [rule objects](/docs/rules). Each rule define a collection field name, data type and validation.') - ->action( - function ($name, $read, $write, $rules) use ($response, $projectDB, $webhook, $audit) { - $parsedRules = []; + ->param('rules', [], function ($projectDB) { return new ArrayList(new Collection($projectDB, [Database::SYSTEM_COLLECTION_RULES], ['$collection' => Database::SYSTEM_COLLECTION_RULES, '$permissions' => ['read' => [], 'write' => []]])); }, 'Array of [rule objects](/docs/rules). Each rule define a collection field name, data type and validation.', false, ['projectDB']) + ->action(function ($name, $read, $write, $rules, $response, $projectDB, $webhook, $audit) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $webhook */ + /** @var Appwrite\Event\Event $audit */ - foreach ($rules as &$rule) { - $parsedRules[] = \array_merge([ - '$collection' => Database::SYSTEM_COLLECTION_RULES, - '$permissions' => [ - 'read' => $read, - 'write' => $write, - ], - ], $rule); - } + $parsedRules = []; - try { - $data = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, - 'name' => $name, - 'dateCreated' => \time(), - 'dateUpdated' => \time(), - 'structure' => true, - '$permissions' => [ - 'read' => $read, - 'write' => $write, - ], - 'rules' => $parsedRules, - ]); - } catch (AuthorizationException $exception) { - throw new Exception('Unauthorized action', 401); - } catch (StructureException $exception) { - throw new Exception('Bad structure. '.$exception->getMessage(), 400); - } catch (\Exception $exception) { - throw new Exception('Failed saving document to DB', 500); - } - - if (false === $data) { - throw new Exception('Failed saving collection to DB', 500); - } - - $data = $data->getArrayCopy(); - - $webhook - ->setParam('payload', $data) - ; - - $audit - ->setParam('event', 'database.collections.create') - ->setParam('resource', 'database/collection/'.$data['$id']) - ->setParam('data', $data) - ; - - /* - * View - */ - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($data) - ; + foreach ($rules as &$rule) { + $parsedRules[] = \array_merge([ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + '$permissions' => [ + 'read' => $read, + 'write' => $write, + ], + ], $rule); } - ); + + try { + $data = $projectDB->createDocument([ + '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, + 'name' => $name, + 'dateCreated' => \time(), + 'dateUpdated' => \time(), + 'structure' => true, + '$permissions' => [ + 'read' => $read, + 'write' => $write, + ], + 'rules' => $parsedRules, + ]); + } catch (AuthorizationException $exception) { + throw new Exception('Unauthorized action', 401); + } catch (StructureException $exception) { + throw new Exception('Bad structure. '.$exception->getMessage(), 400); + } catch (\Exception $exception) { + throw new Exception('Failed saving document to DB', 500); + } + + if (false === $data) { + throw new Exception('Failed saving collection to DB', 500); + } + + $data = $data->getArrayCopy(); + + $webhook + ->setParam('payload', $data) + ; + + $audit + ->setParam('event', 'database.collections.create') + ->setParam('resource', 'database/collection/'.$data['$id']) + ->setParam('data', $data) + ; + + /* + * View + */ + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->json($data) + ; + }, ['response', 'projectDB', 'webhook', 'audit']); App::get('/v1/database/collections') ->desc('List Collections') @@ -112,42 +110,24 @@ App::get('/v1/database/collections') ->param('limit', 25, function () { return new Range(0, 100); }, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('offset', 0, function () { return new Range(0, 40000); }, 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', function () { return new WhiteList(['ASC', 'DESC']); }, 'Order result by ASC or DESC order.', true) - ->action( - function ($search, $limit, $offset, $orderType) use ($response, $projectDB) { - /*$vl = new Structure($projectDB); + ->action(function ($search, $limit, $offset, $orderType, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - var_dump($vl->isValid(new Document([ - '$collection' => Database::SYSTEM_COLLECTION_RULES, - '$permissions' => [ - 'read' => ['*'], - 'write' => ['*'], - ], - 'label' => 'Platforms', - 'key' => 'platforms', - 'type' => 'document', - 'default' => [], - 'required' => false, - 'array' => true, - 'options' => [Database::SYSTEM_COLLECTION_PLATFORMS], - ]))); + $results = $projectDB->getCollection([ + 'limit' => $limit, + 'offset' => $offset, + 'orderField' => 'name', + 'orderType' => $orderType, + 'orderCast' => 'string', + 'search' => $search, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_COLLECTIONS, + ], + ]); - var_dump($vl->getDescription());*/ - - $results = $projectDB->getCollection([ - 'limit' => $limit, - 'offset' => $offset, - 'orderField' => 'name', - 'orderType' => $orderType, - 'orderCast' => 'string', - 'search' => $search, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_COLLECTIONS, - ], - ]); - - $response->json(['sum' => $projectDB->getSum(), 'collections' => $results]); - } - ); + $response->json(['sum' => $projectDB->getSum(), 'collections' => $results]); + }, ['response', 'projectDB']); App::get('/v1/database/collections/:collectionId') ->desc('Get Collection') @@ -158,17 +138,18 @@ App::get('/v1/database/collections/:collectionId') ->label('sdk.method', 'getCollection') ->label('sdk.description', '/docs/references/database/get-collection.md') ->param('collectionId', '', function () { return new UID(); }, 'Collection unique ID.') - ->action( - function ($collectionId) use ($response, $projectDB) { - $collection = $projectDB->getDocument($collectionId, false); + ->action(function ($collectionId, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + + $collection = $projectDB->getDocument($collectionId, false); - if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { - throw new Exception('Collection not found', 404); - } - - $response->json($collection->getArrayCopy()); + if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { + throw new Exception('Collection not found', 404); } - ); + + $response->json($collection->getArrayCopy()); + }, ['response', 'projectDB']); // App::get('/v1/database/collections/:collectionId/logs') // ->desc('Get Collection Logs') @@ -249,64 +230,67 @@ App::put('/v1/database/collections/:collectionId') ->param('read', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions(/docs/permissions) and get a full list of available permissions.') ->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') ->param('rules', [], function () use ($projectDB) { return new ArrayList(new Collection($projectDB, [Database::SYSTEM_COLLECTION_RULES], ['$collection' => Database::SYSTEM_COLLECTION_RULES, '$permissions' => ['read' => [], 'write' => []]])); }, 'Array of [rule objects](/docs/rules). Each rule define a collection field name, data type and validation.', true) - ->action( - function ($collectionId, $name, $read, $write, $rules) use ($response, $projectDB, $webhook, $audit) { - $collection = $projectDB->getDocument($collectionId, false); + ->action(function ($collectionId, $name, $read, $write, $rules, $response, $projectDB, $webhook, $audit) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $webhook */ + /** @var Appwrite\Event\Event $audit */ - if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { - throw new Exception('Collection not found', 404); - } + $collection = $projectDB->getDocument($collectionId, false); - $parsedRules = []; - - foreach ($rules as &$rule) { - $parsedRules[] = \array_merge([ - '$collection' => Database::SYSTEM_COLLECTION_RULES, - '$permissions' => [ - 'read' => $read, - 'write' => $write, - ], - ], $rule); - } - - try { - $collection = $projectDB->updateDocument(\array_merge($collection->getArrayCopy(), [ - 'name' => $name, - 'structure' => true, - 'dateUpdated' => \time(), - '$permissions' => [ - 'read' => $read, - 'write' => $write, - ], - 'rules' => $parsedRules, - ])); - } catch (AuthorizationException $exception) { - throw new Exception('Unauthorized action', 401); - } catch (StructureException $exception) { - throw new Exception('Bad structure. '.$exception->getMessage(), 400); - } catch (\Exception $exception) { - throw new Exception('Failed saving document to DB', 500); - } - - if (false === $collection) { - throw new Exception('Failed saving collection to DB', 500); - } - - $data = $collection->getArrayCopy(); - - $webhook - ->setParam('payload', $data) - ; - - $audit - ->setParam('event', 'database.collections.update') - ->setParam('resource', 'database/collections/'.$data['$id']) - ->setParam('data', $data) - ; - - $response->json($collection->getArrayCopy()); + if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { + throw new Exception('Collection not found', 404); } - ); + + $parsedRules = []; + + foreach ($rules as &$rule) { + $parsedRules[] = \array_merge([ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + '$permissions' => [ + 'read' => $read, + 'write' => $write, + ], + ], $rule); + } + + try { + $collection = $projectDB->updateDocument(\array_merge($collection->getArrayCopy(), [ + 'name' => $name, + 'structure' => true, + 'dateUpdated' => \time(), + '$permissions' => [ + 'read' => $read, + 'write' => $write, + ], + 'rules' => $parsedRules, + ])); + } catch (AuthorizationException $exception) { + throw new Exception('Unauthorized action', 401); + } catch (StructureException $exception) { + throw new Exception('Bad structure. '.$exception->getMessage(), 400); + } catch (\Exception $exception) { + throw new Exception('Failed saving document to DB', 500); + } + + if (false === $collection) { + throw new Exception('Failed saving collection to DB', 500); + } + + $data = $collection->getArrayCopy(); + + $webhook + ->setParam('payload', $data) + ; + + $audit + ->setParam('event', 'database.collections.update') + ->setParam('resource', 'database/collections/'.$data['$id']) + ->setParam('data', $data) + ; + + $response->json($collection->getArrayCopy()); + }, ['response', 'projectDB', 'webhook', 'audit']); App::delete('/v1/database/collections/:collectionId') ->desc('Delete Collection') @@ -318,33 +302,36 @@ App::delete('/v1/database/collections/:collectionId') ->label('sdk.method', 'deleteCollection') ->label('sdk.description', '/docs/references/database/delete-collection.md') ->param('collectionId', '', function () { return new UID(); }, 'Collection unique ID.') - ->action( - function ($collectionId) use ($response, $projectDB, $webhook, $audit) { - $collection = $projectDB->getDocument($collectionId, false); + ->action(function ($collectionId, $response, $projectDB, $webhook, $audit) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $webhook */ + /** @var Appwrite\Event\Event $audit */ - if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { - throw new Exception('Collection not found', 404); - } + $collection = $projectDB->getDocument($collectionId, false); - if (!$projectDB->deleteDocument($collectionId)) { - throw new Exception('Failed to remove collection from DB', 500); - } - - $data = $collection->getArrayCopy(); - - $webhook - ->setParam('payload', $data) - ; - - $audit - ->setParam('event', 'database.collections.delete') - ->setParam('resource', 'database/collections/'.$data['$id']) - ->setParam('data', $data) - ; - - $response->noContent(); + if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { + throw new Exception('Collection not found', 404); } - ); + + if (!$projectDB->deleteDocument($collectionId)) { + throw new Exception('Failed to remove collection from DB', 500); + } + + $data = $collection->getArrayCopy(); + + $webhook + ->setParam('payload', $data) + ; + + $audit + ->setParam('event', 'database.collections.delete') + ->setParam('resource', 'database/collections/'.$data['$id']) + ->setParam('data', $data) + ; + + $response->noContent(); + }, ['response', 'projectDB', 'webhook', 'audit']); App::post('/v1/database/collections/:collectionId/documents') ->desc('Create Document') @@ -362,109 +349,112 @@ App::post('/v1/database/collections/:collectionId/documents') ->param('parentDocument', '', function () { return new UID(); }, 'Parent document unique ID. Use when you want your new document to be a child of a parent document.', true) ->param('parentProperty', '', function () { return new Key(); }, 'Parent document property name. Use when you want your new document to be a child of a parent document.', true) ->param('parentPropertyType', Document::SET_TYPE_ASSIGN, function () { return new WhiteList([Document::SET_TYPE_ASSIGN, Document::SET_TYPE_APPEND, Document::SET_TYPE_PREPEND]); }, 'Parent document property connection type. You can set this value to **assign**, **append** or **prepend**, default value is assign. Use when you want your new document to be a child of a parent document.', true) - ->action( - function ($collectionId, $data, $read, $write, $parentDocument, $parentProperty, $parentPropertyType) use ($response, $projectDB, $webhook, $audit) { - $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array + ->action(function ($collectionId, $data, $read, $write, $parentDocument, $parentProperty, $parentPropertyType, $response, $projectDB, $webhook, $audit) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $webhook */ + /** @var Appwrite\Event\Event $audit */ + + $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array - if (empty($data)) { - throw new Exception('Missing payload', 400); + if (empty($data)) { + throw new Exception('Missing payload', 400); + } + + if (isset($data['$id'])) { + throw new Exception('$id is not allowed for creating new documents, try update instead', 400); + } + + $collection = $projectDB->getDocument($collectionId, false); + + if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { + throw new Exception('Collection not found', 404); + } + + $data['$collection'] = $collectionId; // Adding this param to make API easier for developers + $data['$permissions'] = [ + 'read' => $read, + 'write' => $write, + ]; + + // Read parent document + validate not 404 + validate read / write permission like patch method + // Add payload to parent document property + if ((!empty($parentDocument)) && (!empty($parentProperty))) { + $parentDocument = $projectDB->getDocument($parentDocument, false); + + if (empty($parentDocument->getArrayCopy())) { // Check empty + throw new Exception('No parent document found', 404); } - if (isset($data['$id'])) { - throw new Exception('$id is not allowed for creating new documents, try update instead', 400); - } - - $collection = $projectDB->getDocument($collectionId, false); - - if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { - throw new Exception('Collection not found', 404); - } - - $data['$collection'] = $collectionId; // Adding this param to make API easier for developers - $data['$permissions'] = [ - 'read' => $read, - 'write' => $write, - ]; - - // Read parent document + validate not 404 + validate read / write permission like patch method - // Add payload to parent document property - if ((!empty($parentDocument)) && (!empty($parentProperty))) { - $parentDocument = $projectDB->getDocument($parentDocument, false); - - if (empty($parentDocument->getArrayCopy())) { // Check empty - throw new Exception('No parent document found', 404); - } - - /* - * 1. Check child has valid structure, - * 2. Check user have write permission for parent document - * 3. Assign parent data (including child) to $data - * 4. Validate the combined result has valid structure (inside $projectDB->createDocument method) - */ - - $new = new Document($data); - - $structure = new Structure($projectDB); - - if (!$structure->isValid($new)) { - throw new Exception('Invalid data structure: '.$structure->getDescription(), 400); - } - - $authorization = new Authorization($parentDocument, 'write'); - - if (!$authorization->isValid($new->getPermissions())) { - throw new Exception('Unauthorized action', 401); - } - - $parentDocument - ->setAttribute($parentProperty, $data, $parentPropertyType); - - $data = $parentDocument->getArrayCopy(); - } - - /** - * Set default collection values - */ - foreach ($collection->getAttribute('rules') as $key => $rule) { - $key = (isset($rule['key'])) ? $rule['key'] : ''; - $default = (isset($rule['default'])) ? $rule['default'] : null; - - if (!isset($data[$key])) { - $data[$key] = $default; - } - } - - try { - $data = $projectDB->createDocument($data); - } catch (AuthorizationException $exception) { - throw new Exception('Unauthorized action', 401); - } catch (StructureException $exception) { - throw new Exception('Bad structure. '.$exception->getMessage(), 400); - } catch (\Exception $exception) { - throw new Exception('Failed saving document to DB'.$exception->getMessage(), 500); - } - - $data = $data->getArrayCopy(); - - $webhook - ->setParam('payload', $data) - ; - - $audit - ->setParam('event', 'database.documents.create') - ->setParam('resource', 'database/document/'.$data['$id']) - ->setParam('data', $data) - ; - /* - * View - */ - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($data) - ; + * 1. Check child has valid structure, + * 2. Check user have write permission for parent document + * 3. Assign parent data (including child) to $data + * 4. Validate the combined result has valid structure (inside $projectDB->createDocument method) + */ + + $new = new Document($data); + + $structure = new Structure($projectDB); + + if (!$structure->isValid($new)) { + throw new Exception('Invalid data structure: '.$structure->getDescription(), 400); + } + + $authorization = new Authorization($parentDocument, 'write'); + + if (!$authorization->isValid($new->getPermissions())) { + throw new Exception('Unauthorized action', 401); + } + + $parentDocument + ->setAttribute($parentProperty, $data, $parentPropertyType); + + $data = $parentDocument->getArrayCopy(); } - ); + + /** + * Set default collection values + */ + foreach ($collection->getAttribute('rules') as $key => $rule) { + $key = (isset($rule['key'])) ? $rule['key'] : ''; + $default = (isset($rule['default'])) ? $rule['default'] : null; + + if (!isset($data[$key])) { + $data[$key] = $default; + } + } + + try { + $data = $projectDB->createDocument($data); + } catch (AuthorizationException $exception) { + throw new Exception('Unauthorized action', 401); + } catch (StructureException $exception) { + throw new Exception('Bad structure. '.$exception->getMessage(), 400); + } catch (\Exception $exception) { + throw new Exception('Failed saving document to DB'.$exception->getMessage(), 500); + } + + $data = $data->getArrayCopy(); + + $webhook + ->setParam('payload', $data) + ; + + $audit + ->setParam('event', 'database.documents.create') + ->setParam('resource', 'database/document/'.$data['$id']) + ->setParam('data', $data) + ; + + /* + * View + */ + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->json($data) + ; + }, ['response', 'projectDB', 'webhook', 'audit']); App::get('/v1/database/collections/:collectionId/documents') ->desc('List Documents') @@ -482,49 +472,50 @@ App::get('/v1/database/collections/:collectionId/documents') ->param('orderType', 'ASC', function () { return new WhiteList(array('DESC', 'ASC')); }, 'Order direction. Possible values are DESC for descending order, or ASC for ascending order.', true) ->param('orderCast', 'string', function () { return new WhiteList(array('int', 'string', 'date', 'time', 'datetime')); }, 'Order field type casting. Possible values are int, string, date, time or datetime. The database will attempt to cast the order field to the value you pass here. The default value is a string.', true) ->param('search', '', function () { return new Text(256); }, 'Search query. Enter any free text search. The database will try to find a match against all document attributes and children.', true) - ->action( - function ($collectionId, $filters, $offset, $limit, $orderField, $orderType, $orderCast, $search) use ($response, $projectDB) { - $collection = $projectDB->getDocument($collectionId, false); + ->action(function ($collectionId, $filters, $offset, $limit, $orderField, $orderType, $orderCast, $search, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { - throw new Exception('Collection not found', 404); - } + $collection = $projectDB->getDocument($collectionId, false); - $list = $projectDB->getCollection([ - 'limit' => $limit, - 'offset' => $offset, - 'orderField' => $orderField, - 'orderType' => $orderType, - 'orderCast' => $orderCast, - 'search' => $search, - 'filters' => \array_merge($filters, [ - '$collection='.$collectionId, - ]), - ]); - - if (App::isDevelopment()) { - $collection - ->setAttribute('debug', $projectDB->getDebug()) - ->setAttribute('limit', $limit) - ->setAttribute('offset', $offset) - ->setAttribute('orderField', $orderField) - ->setAttribute('orderType', $orderType) - ->setAttribute('orderCast', $orderCast) - ->setAttribute('filters', $filters) - ; - } - - $collection - ->setAttribute('sum', $projectDB->getSum()) - ->setAttribute('documents', $list) - ; - - /* - * View - */ - $response->json($collection->getArrayCopy(/*['$id', '$collection', 'name', 'documents']*/[], ['rules'])); + if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { + throw new Exception('Collection not found', 404); } - ); + + $list = $projectDB->getCollection([ + 'limit' => $limit, + 'offset' => $offset, + 'orderField' => $orderField, + 'orderType' => $orderType, + 'orderCast' => $orderCast, + 'search' => $search, + 'filters' => \array_merge($filters, [ + '$collection='.$collectionId, + ]), + ]); + + if (App::isDevelopment()) { + $collection + ->setAttribute('debug', $projectDB->getDebug()) + ->setAttribute('limit', $limit) + ->setAttribute('offset', $offset) + ->setAttribute('orderField', $orderField) + ->setAttribute('orderType', $orderType) + ->setAttribute('orderCast', $orderCast) + ->setAttribute('filters', $filters) + ; + } + + $collection + ->setAttribute('sum', $projectDB->getSum()) + ->setAttribute('documents', $list) + ; + + /* + * View + */ + $response->json($collection->getArrayCopy(/*['$id', '$collection', 'name', 'documents']*/[], ['rules'])); + }, ['response', 'projectDB']); App::get('/v1/database/collections/:collectionId/documents/:documentId') ->desc('Get Document') @@ -536,41 +527,43 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId') ->label('sdk.description', '/docs/references/database/get-document.md') ->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') ->param('documentId', null, function () { return new UID(); }, 'Document unique ID.') - ->action( - function ($collectionId, $documentId) use ($response, $request, $projectDB) { - $document = $projectDB->getDocument($documentId, false); - $collection = $projectDB->getDocument($collectionId, false); + ->action(function ($collectionId, $documentId, $request, $response, $projectDB) { + /** @var Utopia\Request $request */ + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - if (empty($document->getArrayCopy()) || $document->getCollection() != $collection->getId()) { // Check empty + $document = $projectDB->getDocument($documentId, false); + $collection = $projectDB->getDocument($collectionId, false); + + if (empty($document->getArrayCopy()) || $document->getCollection() != $collection->getId()) { // Check empty + throw new Exception('No document found', 404); + } + + $output = $document->getArrayCopy(); + + $paths = \explode('/', $request->getParam('q', '')); + $paths = \array_slice($paths, 7, \count($paths)); + + if (\count($paths) > 0) { + if (\count($paths) % 2 == 1) { + $output = $document->getAttribute(\implode('.', $paths)); + } else { + $id = (int) \array_pop($paths); + $output = $document->search('$id', $id, $document->getAttribute(\implode('.', $paths))); + } + + $output = ($output instanceof Document) ? $output->getArrayCopy() : $output; + + if (!\is_array($output)) { throw new Exception('No document found', 404); } - - $output = $document->getArrayCopy(); - - $paths = \explode('/', $request->getParam('q', '')); - $paths = \array_slice($paths, 7, \count($paths)); - - if (\count($paths) > 0) { - if (\count($paths) % 2 == 1) { - $output = $document->getAttribute(\implode('.', $paths)); - } else { - $id = (int) \array_pop($paths); - $output = $document->search('$id', $id, $document->getAttribute(\implode('.', $paths))); - } - - $output = ($output instanceof Document) ? $output->getArrayCopy() : $output; - - if (!\is_array($output)) { - throw new Exception('No document found', 404); - } - } - - /* - * View - */ - $response->json($output); } - ); + + /* + * View + */ + $response->json($output); + }, ['request', 'response', 'projectDB']); App::patch('/v1/database/collections/:collectionId/documents/:documentId') ->desc('Update Document') @@ -586,71 +579,74 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') ->param('data', [], function () { return new JSON(); }, 'Document data as JSON object.') ->param('read', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') ->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') - ->action( - function ($collectionId, $documentId, $data, $read, $write) use ($response, $projectDB, $webhook, $audit) { - $collection = $projectDB->getDocument($collectionId, false); - $document = $projectDB->getDocument($documentId, false); + ->action(function ($collectionId, $documentId, $data, $read, $write, $response, $projectDB, $webhook, $audit) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $webhook */ + /** @var Appwrite\Event\Event $audit */ - $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array + $collection = $projectDB->getDocument($collectionId, false); + $document = $projectDB->getDocument($documentId, false); - if (!\is_array($data)) { - throw new Exception('Data param should be a valid JSON', 400); - } + $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array - if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { - throw new Exception('Collection not found', 404); - } - - if (empty($document->getArrayCopy()) || $document->getCollection() != $collectionId) { // Check empty - throw new Exception('No document found', 404); - } - - //TODO check merge read write permissions - - if (!empty($read)) { // Overwrite permissions only when passed - $data['$permissions']['read'] = $read; - } - - if (!empty($write)) { // Overwrite permissions only when passed - $data['$permissions']['write'] = $read; - } - - $data = \array_merge($document->getArrayCopy(), $data); - - $data['$collection'] = $collection->getId(); // Make sure user don't switch collectionID - $data['$id'] = $document->getId(); // Make sure user don't switch document unique ID - - if (empty($data)) { - throw new Exception('Missing payload', 400); - } - try { - $data = $projectDB->updateDocument($data); - } catch (AuthorizationException $exception) { - throw new Exception('Unauthorized action', 401); - } catch (StructureException $exception) { - throw new Exception('Bad structure. '.$exception->getMessage(), 400); - } catch (\Exception $exception) { - throw new Exception('Failed saving document to DB', 500); - } - - $data = $data->getArrayCopy(); - - $webhook - ->setParam('payload', $data) - ; - - $audit - ->setParam('event', 'database.documents.update') - ->setParam('resource', 'database/document/'.$data['$id']) - ->setParam('data', $data) - ; - - /* - * View - */ - $response->json($data); + if (!\is_array($data)) { + throw new Exception('Data param should be a valid JSON', 400); } - ); + + if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { + throw new Exception('Collection not found', 404); + } + + if (empty($document->getArrayCopy()) || $document->getCollection() != $collectionId) { // Check empty + throw new Exception('No document found', 404); + } + + //TODO check merge read write permissions + + if (!empty($read)) { // Overwrite permissions only when passed + $data['$permissions']['read'] = $read; + } + + if (!empty($write)) { // Overwrite permissions only when passed + $data['$permissions']['write'] = $read; + } + + $data = \array_merge($document->getArrayCopy(), $data); + + $data['$collection'] = $collection->getId(); // Make sure user don't switch collectionID + $data['$id'] = $document->getId(); // Make sure user don't switch document unique ID + + if (empty($data)) { + throw new Exception('Missing payload', 400); + } + try { + $data = $projectDB->updateDocument($data); + } catch (AuthorizationException $exception) { + throw new Exception('Unauthorized action', 401); + } catch (StructureException $exception) { + throw new Exception('Bad structure. '.$exception->getMessage(), 400); + } catch (\Exception $exception) { + throw new Exception('Failed saving document to DB', 500); + } + + $data = $data->getArrayCopy(); + + $webhook + ->setParam('payload', $data) + ; + + $audit + ->setParam('event', 'database.documents.update') + ->setParam('resource', 'database/document/'.$data['$id']) + ->setParam('data', $data) + ; + + /* + * View + */ + $response->json($data); + }, ['response', 'projectDB', 'webhook', 'audit']); App::delete('/v1/database/collections/:collectionId/documents/:documentId') ->desc('Delete Document') @@ -663,41 +659,44 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') ->label('sdk.description', '/docs/references/database/delete-document.md') ->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID. You can create a new collection with validation rules using the Database service [server integration](/docs/server/database#createCollection).') ->param('documentId', null, function () { return new UID(); }, 'Document unique ID.') - ->action( - function ($collectionId, $documentId) use ($response, $projectDB, $audit, $webhook) { - $collection = $projectDB->getDocument($collectionId, false); - $document = $projectDB->getDocument($documentId, false); + ->action(function ($collectionId, $documentId, $response, $projectDB, $webhook, $audit) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $webhook */ + /** @var Appwrite\Event\Event $audit */ - if (empty($document->getArrayCopy()) || $document->getCollection() != $collectionId) { // Check empty - throw new Exception('No document found', 404); - } + $collection = $projectDB->getDocument($collectionId, false); + $document = $projectDB->getDocument($documentId, false); - if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { - throw new Exception('Collection not found', 404); - } - - try { - $projectDB->deleteDocument($documentId); - } catch (AuthorizationException $exception) { - throw new Exception('Unauthorized action', 401); - } catch (StructureException $exception) { - throw new Exception('Bad structure. '.$exception->getMessage(), 400); - } catch (\Exception $exception) { - throw new Exception('Failed to remove document from DB', 500); - } - - $data = $document->getArrayCopy(); - - $webhook - ->setParam('payload', $data) - ; - - $audit - ->setParam('event', 'database.documents.delete') - ->setParam('resource', 'database/document/'.$data['$id']) - ->setParam('data', $data) // Audit document in case of malicious or disastrous action - ; - - $response->noContent(); + if (empty($document->getArrayCopy()) || $document->getCollection() != $collectionId) { // Check empty + throw new Exception('No document found', 404); } - ); + + if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { + throw new Exception('Collection not found', 404); + } + + try { + $projectDB->deleteDocument($documentId); + } catch (AuthorizationException $exception) { + throw new Exception('Unauthorized action', 401); + } catch (StructureException $exception) { + throw new Exception('Bad structure. '.$exception->getMessage(), 400); + } catch (\Exception $exception) { + throw new Exception('Failed to remove document from DB', 500); + } + + $data = $document->getArrayCopy(); + + $webhook + ->setParam('payload', $data) + ; + + $audit + ->setParam('event', 'database.documents.delete') + ->setParam('resource', 'database/document/'.$data['$id']) + ->setParam('data', $data) // Audit document in case of malicious or disastrous action + ; + + $response->noContent(); + }, ['response', 'projectDB', 'webhook', 'audit']); \ No newline at end of file diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index e8c0296190..a3477e3984 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -1,7 +1,5 @@ label('sdk.namespace', 'health') ->label('sdk.method', 'get') ->label('sdk.description', '/docs/references/health/get.md') - ->action( - function () use ($response) { - $response->json(['status' => 'OK']); - } - ); + ->action(function ($response) { + /** @var Utopia\Response $response */ + + $response->json(['status' => 'OK']); + }, ['response']); App::get('/v1/health/version') ->desc('Get Version') ->groups(['api', 'health']) ->label('scope', 'public') - ->action( - function () use ($response) { - $response->json(['version' => APP_VERSION_STABLE]); - } - ); + ->action(function ($response) { + /** @var Utopia\Response $response */ + + $response->json(['version' => APP_VERSION_STABLE]); + }, ['response']); App::get('/v1/health/db') ->desc('Get DB') @@ -40,13 +38,14 @@ App::get('/v1/health/db') ->label('sdk.namespace', 'health') ->label('sdk.method', 'getDB') ->label('sdk.description', '/docs/references/health/get-db.md') - ->action( - function () use ($response, $register) { - $register->get('db'); /* @var $db PDO */ + ->action(function ($response, $register) { + /** @var Utopia\Response $response */ + /** @var Utopia\Registry\Registry $register */ - $response->json(['status' => 'OK']); - } - ); + $register->get('db'); /* @var $db PDO */ + + $response->json(['status' => 'OK']); + }, ['response', 'register']); App::get('/v1/health/cache') ->desc('Get Cache') @@ -56,13 +55,13 @@ App::get('/v1/health/cache') ->label('sdk.namespace', 'health') ->label('sdk.method', 'getCache') ->label('sdk.description', '/docs/references/health/get-cache.md') - ->action( - function () use ($response, $register) { - $register->get('cache'); /* @var $cache Predis\Client */ + ->action(function ($response, $register) { + /** @var Utopia\Response $response */ + /** @var Utopia\Registry\Registry $register */ + $register->get('cache'); /* @var $cache Predis\Client */ - $response->json(['status' => 'OK']); - } - ); + $response->json(['status' => 'OK']); + }, ['response']); App::get('/v1/health/time') ->desc('Get Time') @@ -72,45 +71,45 @@ App::get('/v1/health/time') ->label('sdk.namespace', 'health') ->label('sdk.method', 'getTime') ->label('sdk.description', '/docs/references/health/get-time.md') - ->action( - function () use ($response) { - /* - * Code from: @see https://www.beliefmedia.com.au/query-ntp-time-server - */ - $host = 'time.google.com'; // https://developers.google.com/time/ - $gap = 60; // Allow [X] seconds gap + ->action(function ($response) { + /** @var Utopia\Response $response */ - /* Create a socket and connect to NTP server */ - $sock = \socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + /* + * Code from: @see https://www.beliefmedia.com.au/query-ntp-time-server + */ + $host = 'time.google.com'; // https://developers.google.com/time/ + $gap = 60; // Allow [X] seconds gap - \socket_connect($sock, $host, 123); + /* Create a socket and connect to NTP server */ + $sock = \socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); - /* Send request */ - $msg = "\010".\str_repeat("\0", 47); + \socket_connect($sock, $host, 123); - \socket_send($sock, $msg, \strlen($msg), 0); + /* Send request */ + $msg = "\010".\str_repeat("\0", 47); - /* Receive response and close socket */ - \socket_recv($sock, $recv, 48, MSG_WAITALL); - \socket_close($sock); + \socket_send($sock, $msg, \strlen($msg), 0); - /* Interpret response */ - $data = \unpack('N12', $recv); - $timestamp = \sprintf('%u', $data[9]); + /* Receive response and close socket */ + \socket_recv($sock, $recv, 48, MSG_WAITALL); + \socket_close($sock); - /* NTP is number of seconds since 0000 UT on 1 January 1900 - Unix time is seconds since 0000 UT on 1 January 1970 */ - $timestamp -= 2208988800; + /* Interpret response */ + $data = \unpack('N12', $recv); + $timestamp = \sprintf('%u', $data[9]); - $diff = ($timestamp - \time()); + /* NTP is number of seconds since 0000 UT on 1 January 1900 + Unix time is seconds since 0000 UT on 1 January 1970 */ + $timestamp -= 2208988800; - if ($diff > $gap || $diff < ($gap * -1)) { - throw new Exception('Server time gaps detected'); - } + $diff = ($timestamp - \time()); - $response->json(['remote' => $timestamp, 'local' => \time(), 'diff' => $diff]); + if ($diff > $gap || $diff < ($gap * -1)) { + throw new Exception('Server time gaps detected'); } - ); + + $response->json(['remote' => $timestamp, 'local' => \time(), 'diff' => $diff]); + }, ['response']); App::get('/v1/health/queue/webhooks') ->desc('Get Webhooks Queue') @@ -120,11 +119,11 @@ App::get('/v1/health/queue/webhooks') ->label('sdk.namespace', 'health') ->label('sdk.method', 'getQueueWebhooks') ->label('sdk.description', '/docs/references/health/get-queue-webhooks.md') - ->action( - function () use ($response) { - $response->json(['size' => Resque::size('v1-webhooks')]); - } - ); + ->action(function ($response) { + /** @var Utopia\Response $response */ + + $response->json(['size' => Resque::size('v1-webhooks')]); + }, ['response']); App::get('/v1/health/queue/tasks') ->desc('Get Tasks Queue') @@ -134,11 +133,11 @@ App::get('/v1/health/queue/tasks') ->label('sdk.namespace', 'health') ->label('sdk.method', 'getQueueTasks') ->label('sdk.description', '/docs/references/health/get-queue-tasks.md') - ->action( - function () use ($response) { - $response->json(['size' => Resque::size('v1-tasks')]); - } - ); + ->action(function ($response) { + /** @var Utopia\Response $response */ + + $response->json(['size' => Resque::size('v1-tasks')]); + }, ['response']); App::get('/v1/health/queue/logs') ->desc('Get Logs Queue') @@ -148,11 +147,11 @@ App::get('/v1/health/queue/logs') ->label('sdk.namespace', 'health') ->label('sdk.method', 'getQueueLogs') ->label('sdk.description', '/docs/references/health/get-queue-logs.md') - ->action( - function () use ($response) { - $response->json(['size' => Resque::size('v1-audit')]); - } - ); + ->action(function ($response) { + /** @var Utopia\Response $response */ + + $response->json(['size' => Resque::size('v1-audit')]); + }, ['response']); App::get('/v1/health/queue/usage') ->desc('Get Usage Queue') @@ -162,11 +161,11 @@ App::get('/v1/health/queue/usage') ->label('sdk.namespace', 'health') ->label('sdk.method', 'getQueueUsage') ->label('sdk.description', '/docs/references/health/get-queue-usage.md') - ->action( - function () use ($response) { - $response->json(['size' => Resque::size('v1-usage')]); - } - ); + ->action(function ($response) { + /** @var Utopia\Response $response */ + + $response->json(['size' => Resque::size('v1-usage')]); + }, ['response']); App::get('/v1/health/queue/certificates') ->desc('Get Certificate Queue') @@ -176,11 +175,11 @@ App::get('/v1/health/queue/certificates') ->label('sdk.namespace', 'health') ->label('sdk.method', 'getQueueCertificates') ->label('sdk.description', '/docs/references/health/get-queue-certificates.md') - ->action( - function () use ($response) { - $response->json(['size' => Resque::size('v1-certificates')]); - } - ); + ->action(function ($response) { + /** @var Utopia\Response $response */ + + $response->json(['size' => Resque::size('v1-certificates')]); + }, ['response']); App::get('/v1/health/queue/functions') ->desc('Get Functions Queue') @@ -190,11 +189,11 @@ App::get('/v1/health/queue/functions') ->label('sdk.namespace', 'health') ->label('sdk.method', 'getQueueFunctions') ->label('sdk.description', '/docs/references/health/get-queue-functions.md') - ->action( - function () use ($response) { - $response->json(['size' => Resque::size('v1-functions')]); - } - ); + ->action(function ($response) { + /** @var Utopia\Response $response */ + + $response->json(['size' => Resque::size('v1-functions')]); + }, ['response']); App::get('/v1/health/storage/local') ->desc('Get Local Storage') @@ -204,28 +203,28 @@ App::get('/v1/health/storage/local') ->label('sdk.namespace', 'health') ->label('sdk.method', 'getStorageLocal') ->label('sdk.description', '/docs/references/health/get-storage-local.md') - ->action( - function () use ($response) { - foreach ([ - 'Uploads' => APP_STORAGE_UPLOADS, - 'Cache' => APP_STORAGE_CACHE, - 'Config' => APP_STORAGE_CONFIG, - 'Certs' => APP_STORAGE_CERTIFICATES - ] as $key => $volume) { - $device = new Local($volume); + ->action(function ($response) { + /** @var Utopia\Response $response */ - if (!\is_readable($device->getRoot())) { - throw new Exception('Device '.$key.' dir is not readable'); - } + foreach ([ + 'Uploads' => APP_STORAGE_UPLOADS, + 'Cache' => APP_STORAGE_CACHE, + 'Config' => APP_STORAGE_CONFIG, + 'Certs' => APP_STORAGE_CERTIFICATES + ] as $key => $volume) { + $device = new Local($volume); - if (!\is_writable($device->getRoot())) { - throw new Exception('Device '.$key.' dir is not writable'); - } + if (!\is_readable($device->getRoot())) { + throw new Exception('Device '.$key.' dir is not readable'); } - $response->json(['status' => 'OK']); + if (!\is_writable($device->getRoot())) { + throw new Exception('Device '.$key.' dir is not writable'); + } } - ); + + $response->json(['status' => 'OK']); + }, ['response']); App::get('/v1/health/anti-virus') ->desc('Get Anti virus') @@ -235,20 +234,20 @@ App::get('/v1/health/anti-virus') ->label('sdk.namespace', 'health') ->label('sdk.method', 'getAntiVirus') ->label('sdk.description', '/docs/references/health/get-storage-anti-virus.md') - ->action( - function () use ($response) { - if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'disabled') { // Check if scans are enabled - throw new Exception('Anitvirus is disabled'); - } + ->action(function ($response) { + /** @var Utopia\Response $response */ - $antiVirus = new Network('clamav', 3310); - - $response->json([ - 'status' => (@$antiVirus->ping()) ? 'online' : 'offline', - 'version' => @$antiVirus->version(), - ]); + if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'disabled') { // Check if scans are enabled + throw new Exception('Anitvirus is disabled'); } - ); + + $antiVirus = new Network('clamav', 3310); + + $response->json([ + 'status' => (@$antiVirus->ping()) ? 'online' : 'offline', + 'version' => @$antiVirus->version(), + ]); + }, ['response']); App::get('/v1/health/stats') // Currently only used internally ->desc('Get System Stats') @@ -258,34 +257,35 @@ App::get('/v1/health/stats') // Currently only used internally // ->label('sdk.namespace', 'health') // ->label('sdk.method', 'getStats') ->label('docs', false) - ->action( - function () use ($response, $register) { - $device = Storage::getDevice('local'); - $cache = $register->get('cache'); + ->action(function ($response, $register) { + /** @var Utopia\Response $response */ + /** @var Utopia\Registry\Registry $register */ - $cacheStats = $cache->info(); + $device = Storage::getDevice('local'); + $cache = $register->get('cache'); - $response - ->json([ - 'server' => [ - 'name' => 'nginx', - 'version' => \shell_exec('nginx -v 2>&1'), - ], - 'storage' => [ - 'used' => Storage::human($device->getDirectorySize($device->getRoot().'/')), - 'partitionTotal' => Storage::human($device->getPartitionTotalSpace()), - 'partitionFree' => Storage::human($device->getPartitionFreeSpace()), - ], - 'cache' => [ - 'uptime' => (isset($cacheStats['uptime_in_seconds'])) ? $cacheStats['uptime_in_seconds'] : 0, - 'clients' => (isset($cacheStats['connected_clients'])) ? $cacheStats['connected_clients'] : 0, - 'hits' => (isset($cacheStats['keyspace_hits'])) ? $cacheStats['keyspace_hits'] : 0, - 'misses' => (isset($cacheStats['keyspace_misses'])) ? $cacheStats['keyspace_misses'] : 0, - 'memory_used' => (isset($cacheStats['used_memory'])) ? $cacheStats['used_memory'] : 0, - 'memory_used_human' => (isset($cacheStats['used_memory_human'])) ? $cacheStats['used_memory_human'] : 0, - 'memory_used_peak' => (isset($cacheStats['used_memory_peak'])) ? $cacheStats['used_memory_peak'] : 0, - 'memory_used_peak_human' => (isset($cacheStats['used_memory_peak_human'])) ? $cacheStats['used_memory_peak_human'] : 0, - ], - ]); - } - ); + $cacheStats = $cache->info(); + + $response + ->json([ + 'server' => [ + 'name' => 'nginx', + 'version' => \shell_exec('nginx -v 2>&1'), + ], + 'storage' => [ + 'used' => Storage::human($device->getDirectorySize($device->getRoot().'/')), + 'partitionTotal' => Storage::human($device->getPartitionTotalSpace()), + 'partitionFree' => Storage::human($device->getPartitionFreeSpace()), + ], + 'cache' => [ + 'uptime' => (isset($cacheStats['uptime_in_seconds'])) ? $cacheStats['uptime_in_seconds'] : 0, + 'clients' => (isset($cacheStats['connected_clients'])) ? $cacheStats['connected_clients'] : 0, + 'hits' => (isset($cacheStats['keyspace_hits'])) ? $cacheStats['keyspace_hits'] : 0, + 'misses' => (isset($cacheStats['keyspace_misses'])) ? $cacheStats['keyspace_misses'] : 0, + 'memory_used' => (isset($cacheStats['used_memory'])) ? $cacheStats['used_memory'] : 0, + 'memory_used_human' => (isset($cacheStats['used_memory_human'])) ? $cacheStats['used_memory_human'] : 0, + 'memory_used_peak' => (isset($cacheStats['used_memory_peak'])) ? $cacheStats['used_memory_peak'] : 0, + 'memory_used_peak_human' => (isset($cacheStats['used_memory_peak_human'])) ? $cacheStats['used_memory_peak_human'] : 0, + ], + ]); + }, ['response', 'register']); diff --git a/app/controllers/api/locale.php b/app/controllers/api/locale.php index 0bbc12f4c7..2ae098abb0 100644 --- a/app/controllers/api/locale.php +++ b/app/controllers/api/locale.php @@ -1,9 +1,6 @@ label('sdk.namespace', 'locale') ->label('sdk.method', 'get') ->label('sdk.description', '/docs/references/locale/get-locale.md') - ->action( - function () use ($response, $request) { - $eu = include __DIR__.'/../../config/eu.php'; - $currencies = include __DIR__.'/../../config/currencies.php'; - $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb'); - $output = []; - $ip = $request->getIP(); - $time = (60 * 60 * 24 * 45); // 45 days cache - $countries = Locale::getText('countries'); - $continents = Locale::getText('continents'); + ->action(function ($request, $response, $locale) { + /** @var Utopia\Request $request */ + /** @var Utopia\Response $response */ + /** @var Utopia\Locale\Locale $locale */ - if (!App::isProduction()) { - $ip = '79.177.241.94'; - } + $eu = include __DIR__.'/../../config/eu.php'; + $currencies = include __DIR__.'/../../config/currencies.php'; + $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb'); + $output = []; + $ip = $request->getIP(); + $time = (60 * 60 * 24 * 45); // 45 days cache + $countries = $locale->getText('countries'); + $continents = $locale->getText('continents'); - $output['ip'] = $ip; - - $currency = null; - - try { - $record = $reader->country($ip); - $output['countryCode'] = $record->country->isoCode; - $output['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown'); - //$output['countryTimeZone'] = DateTimeZone::listIdentifiers(DateTimeZone::PER_COUNTRY, $record->country->isoCode); - $output['continent'] = (isset($continents[$record->continent->code])) ? $continents[$record->continent->code] : Locale::getText('locale.country.unknown'); - $output['continentCode'] = $record->continent->code; - $output['eu'] = (\in_array($record->country->isoCode, $eu)) ? true : false; - - foreach ($currencies as $code => $element) { - if (isset($element['locations']) && isset($element['code']) && \in_array($record->country->isoCode, $element['locations'])) { - $currency = $element['code']; - } - } - - $output['currency'] = $currency; - } catch (\Exception $e) { - $output['countryCode'] = '--'; - $output['country'] = Locale::getText('locale.country.unknown'); - $output['continent'] = Locale::getText('locale.country.unknown'); - $output['continentCode'] = '--'; - $output['eu'] = false; - $output['currency'] = $currency; - } - - $response - ->addHeader('Cache-Control', 'public, max-age='.$time) - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time).' GMT') // 45 days cache - ->json($output); + if (!App::isProduction()) { + $ip = '79.177.241.94'; } - ); + + $output['ip'] = $ip; + + $currency = null; + + try { + $record = $reader->country($ip); + $output['countryCode'] = $record->country->isoCode; + $output['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : $locale->getText('locale.country.unknown'); + //$output['countryTimeZone'] = DateTimeZone::listIdentifiers(DateTimeZone::PER_COUNTRY, $record->country->isoCode); + $output['continent'] = (isset($continents[$record->continent->code])) ? $continents[$record->continent->code] : $locale->getText('locale.country.unknown'); + $output['continentCode'] = $record->continent->code; + $output['eu'] = (\in_array($record->country->isoCode, $eu)) ? true : false; + + foreach ($currencies as $code => $element) { + if (isset($element['locations']) && isset($element['code']) && \in_array($record->country->isoCode, $element['locations'])) { + $currency = $element['code']; + } + } + + $output['currency'] = $currency; + } catch (\Exception $e) { + $output['countryCode'] = '--'; + $output['country'] = $locale->getText('locale.country.unknown'); + $output['continent'] = $locale->getText('locale.country.unknown'); + $output['continentCode'] = '--'; + $output['eu'] = false; + $output['currency'] = $currency; + } + + $response + ->addHeader('Cache-Control', 'public, max-age='.$time) + ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time).' GMT') // 45 days cache + ->json($output); + }, ['request', 'response', 'locale']); App::get('/v1/locale/countries') ->desc('List Countries') @@ -73,15 +72,16 @@ App::get('/v1/locale/countries') ->label('sdk.namespace', 'locale') ->label('sdk.method', 'getCountries') ->label('sdk.description', '/docs/references/locale/get-countries.md') - ->action( - function () use ($response) { - $list = Locale::getText('countries'); /* @var $list array */ + ->action(function ($response, $locale) { + /** @var Utopia\Response $response */ + /** @var Utopia\Locale\Locale $locale */ - \asort($list); + $list = $locale->getText('countries'); /* @var $list array */ - $response->json($list); - } - ); + \asort($list); + + $response->json($list); + }, ['response', 'locale']); App::get('/v1/locale/countries/eu') ->desc('List EU Countries') @@ -91,23 +91,24 @@ App::get('/v1/locale/countries/eu') ->label('sdk.namespace', 'locale') ->label('sdk.method', 'getCountriesEU') ->label('sdk.description', '/docs/references/locale/get-countries-eu.md') - ->action( - function () use ($response) { - $countries = Locale::getText('countries'); /* @var $countries array */ - $eu = include __DIR__.'/../../config/eu.php'; - $list = []; + ->action(function ($response, $locale) { + /** @var Utopia\Response $response */ + /** @var Utopia\Locale\Locale $locale */ - foreach ($eu as $code) { - if (\array_key_exists($code, $countries)) { - $list[$code] = $countries[$code]; - } + $countries = $locale->getText('countries'); /* @var $countries array */ + $eu = include __DIR__.'/../../config/eu.php'; + $list = []; + + foreach ($eu as $code) { + if (\array_key_exists($code, $countries)) { + $list[$code] = $countries[$code]; } - - \asort($list); - - $response->json($list); } - ); + + \asort($list); + + $response->json($list); + }, ['response', 'locale']); App::get('/v1/locale/countries/phones') ->desc('List Countries Phone Codes') @@ -117,23 +118,24 @@ App::get('/v1/locale/countries/phones') ->label('sdk.namespace', 'locale') ->label('sdk.method', 'getCountriesPhones') ->label('sdk.description', '/docs/references/locale/get-countries-phones.md') - ->action( - function () use ($response) { - $list = include __DIR__.'/../../config/phones.php'; /* @var $list array */ + ->action(function ($response, $locale) { + /** @var Utopia\Response $response */ + /** @var Utopia\Locale\Locale $locale */ - $countries = Locale::getText('countries'); /* @var $countries array */ + $list = include __DIR__.'/../../config/phones.php'; /* @var $list array */ - foreach ($list as $code => $name) { - if (\array_key_exists($code, $countries)) { - $list[$code] = '+'.$list[$code]; - } + $countries = $locale->getText('countries'); /* @var $countries array */ + + foreach ($list as $code => $name) { + if (\array_key_exists($code, $countries)) { + $list[$code] = '+'.$list[$code]; } - - \asort($list); - - $response->json($list); } - ); + + \asort($list); + + $response->json($list); + }, ['response', 'locale']); App::get('/v1/locale/continents') ->desc('List Continents') @@ -143,15 +145,16 @@ App::get('/v1/locale/continents') ->label('sdk.namespace', 'locale') ->label('sdk.method', 'getContinents') ->label('sdk.description', '/docs/references/locale/get-continents.md') - ->action( - function () use ($response) { - $list = Locale::getText('continents'); /* @var $list array */ + ->action(function ($response, $locale) { + /** @var Utopia\Response $response */ + /** @var Utopia\Locale\Locale $locale */ - \asort($list); + $list = $locale->getText('continents'); /* @var $list array */ - $response->json($list); - } - ); + \asort($list); + + $response->json($list); + }, ['response', 'locale']); App::get('/v1/locale/currencies') @@ -162,13 +165,13 @@ App::get('/v1/locale/currencies') ->label('sdk.namespace', 'locale') ->label('sdk.method', 'getCurrencies') ->label('sdk.description', '/docs/references/locale/get-currencies.md') - ->action( - function () use ($response) { - $currencies = include __DIR__.'/../../config/currencies.php'; + ->action(function ($response) { + /** @var Utopia\Response $response */ - $response->json($currencies); - } - ); + $currencies = include __DIR__.'/../../config/currencies.php'; + + $response->json($currencies); + }, ['response']); App::get('/v1/locale/languages') @@ -179,10 +182,10 @@ App::get('/v1/locale/languages') ->label('sdk.namespace', 'locale') ->label('sdk.method', 'getLanguages') ->label('sdk.description', '/docs/references/locale/get-languages.md') - ->action( - function () use ($response) { - $languages = include __DIR__.'/../../config/languages.php'; + ->action(function ($response) { + /** @var Utopia\Response $response */ - $response->json($languages); - } - ); \ No newline at end of file + $languages = include __DIR__.'/../../config/languages.php'; + + $response->json($languages); + }, ['response']); \ No newline at end of file diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 4b366efa2e..ab3dd77624 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -1,7 +1,5 @@ __DIR__.'/../../config/files/none.png', - - // Video Files - 'video/mp4' => __DIR__.'/../../config/files/video.png', - 'video/x-flv' => __DIR__.'/../../config/files/video.png', - 'application/x-mpegURL' => __DIR__.'/../../config/files/video.png', - 'video/MP2T' => __DIR__.'/../../config/files/video.png', - 'video/3gpp' => __DIR__.'/../../config/files/video.png', - 'video/quicktime' => __DIR__.'/../../config/files/video.png', - 'video/x-msvideo' => __DIR__.'/../../config/files/video.png', - 'video/x-ms-wmv' => __DIR__.'/../../config/files/video.png', - - // // Microsoft Word - 'application/msword' => __DIR__.'/../../config/files/word.png', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => __DIR__.'/../../config/files/word.png', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => __DIR__.'/../../config/files/word.png', - 'application/vnd.ms-word.document.macroEnabled.12' => __DIR__.'/../../config/files/word.png', - - // // Microsoft Excel - 'application/vnd.ms-excel' => __DIR__.'/../../config/files/excel.png', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => __DIR__.'/../../config/files/excel.png', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => __DIR__.'/../../config/files/excel.png', - 'application/vnd.ms-excel.sheet.macroEnabled.12' => __DIR__.'/../../config/files/excel.png', - 'application/vnd.ms-excel.template.macroEnabled.12' => __DIR__.'/../../config/files/excel.png', - 'application/vnd.ms-excel.addin.macroEnabled.12' => __DIR__.'/../../config/files/excel.png', - 'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => __DIR__.'/../../config/files/excel.png', - - // // Microsoft Power Point - 'application/vnd.ms-powerpoint' => __DIR__.'/../../config/files/ppt.png', - 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => __DIR__.'/../../config/files/ppt.png', - 'application/vnd.openxmlformats-officedocument.presentationml.template' => __DIR__.'/../../config/files/ppt.png', - 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => __DIR__.'/../../config/files/ppt.png', - 'application/vnd.ms-powerpoint.addin.macroEnabled.12' => __DIR__.'/../../config/files/ppt.png', - 'application/vnd.ms-powerpoint.presentation.macroEnabled.12' => __DIR__.'/../../config/files/ppt.png', - 'application/vnd.ms-powerpoint.template.macroEnabled.12' => __DIR__.'/../../config/files/ppt.png', - 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12' => __DIR__.'/../../config/files/ppt.png', - - // Adobe PDF - 'application/pdf' => __DIR__.'/../../config/files/pdf.png', -]; +use Utopia\Config\Config; $inputs = [ 'jpg' => 'image/jpeg', @@ -81,58 +38,9 @@ $outputs = [ 'webp' => 'image/webp', ]; -$mimes = [ - 'image/jpeg', - 'image/jpeg', - 'image/gif', - 'image/png', - 'image/webp', - - // Video Files - 'video/mp4', - 'video/x-flv', - 'application/x-mpegURL', - 'video/MP2T', - 'video/3gpp', - 'video/quicktime', - 'video/x-msvideo', - 'video/x-ms-wmv', - - // Microsoft Word - 'application/msword', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', - 'application/vnd.ms-word.document.macroEnabled.12', - - // Microsoft Excel - 'application/vnd.ms-excel', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', - 'application/vnd.ms-excel.sheet.macroEnabled.12', - 'application/vnd.ms-excel.template.macroEnabled.12', - 'application/vnd.ms-excel.addin.macroEnabled.12', - 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', - - // Microsoft Power Point - 'application/vnd.ms-powerpoint', - 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - 'application/vnd.openxmlformats-officedocument.presentationml.template', - 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', - 'application/vnd.ms-powerpoint.addin.macroEnabled.12', - 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', - 'application/vnd.ms-powerpoint.template.macroEnabled.12', - 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', - - // Microsoft Access - 'application/vnd.ms-access', - - // Adobe PDF - 'application/pdf', -]; - -App::init(function () use ($project) { +App::init(function ($project) { Storage::addDevice('local', new Local(APP_STORAGE_UPLOADS.'/app-'.$project->getId())); -}, 'storage'); +}, ['project'], 'storage'); App::post('/v1/storage/files') ->desc('Create File') @@ -148,128 +56,133 @@ App::post('/v1/storage/files') ->param('file', [], function () { return new File(); }, 'Binary File.', false) ->param('read', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') ->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') - // ->param('folderId', '', function () { return new UID(); }, 'Folder to associate files with.', true) - ->action( - function ($file, $read, $write, $folderId = '') use ($request, $response, $user, $projectDB, $webhook, $audit, $usage) { - $file = $request->getFiles('file'); - $read = (empty($read)) ? ['user:'.$user->getId()] : $read; - $write = (empty($write)) ? ['user:'.$user->getId()] : $write; + ->action(function ($file, $read, $write, $request, $response, $user, $projectDB, $webhook, $audit, $usage) { + /** @var Utopia\Request $request */ + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $user */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $webhook */ + /** @var Appwrite\Event\Event $audit */ + /** @var Appwrite\Event\Event $usage */ - /* - * Validators - */ - //$fileType = new FileType(array(FileType::FILE_TYPE_PNG, FileType::FILE_TYPE_GIF, FileType::FILE_TYPE_JPEG)); - $fileSize = new FileSize(App::getEnv('_APP_STORAGE_LIMIT', 0)); - $upload = new Upload(); + $file = $request->getFiles('file'); + $read = (empty($read)) ? ['user:'.$user->getId()] : $read; + $write = (empty($write)) ? ['user:'.$user->getId()] : $write; - if (empty($file)) { - throw new Exception('No file sent', 400); - } + /* + * Validators + */ + //$fileType = new FileType(array(FileType::FILE_TYPE_PNG, FileType::FILE_TYPE_GIF, FileType::FILE_TYPE_JPEG)); + $fileSize = new FileSize(App::getEnv('_APP_STORAGE_LIMIT', 0)); + $upload = new Upload(); - // Make sure we handle a single file and multiple files the same way - $file['name'] = (\is_array($file['name']) && isset($file['name'][0])) ? $file['name'][0] : $file['name']; - $file['tmp_name'] = (\is_array($file['tmp_name']) && isset($file['tmp_name'][0])) ? $file['tmp_name'][0] : $file['tmp_name']; - $file['size'] = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size']; + if (empty($file)) { + throw new Exception('No file sent', 400); + } - // Check if file type is allowed (feature for project settings?) - //if (!$fileType->isValid($file['tmp_name'])) { - //throw new Exception('File type not allowed', 400); - //} + // Make sure we handle a single file and multiple files the same way + $file['name'] = (\is_array($file['name']) && isset($file['name'][0])) ? $file['name'][0] : $file['name']; + $file['tmp_name'] = (\is_array($file['tmp_name']) && isset($file['tmp_name'][0])) ? $file['tmp_name'][0] : $file['tmp_name']; + $file['size'] = (\is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size']; + + // Check if file type is allowed (feature for project settings?) + //if (!$fileType->isValid($file['tmp_name'])) { + //throw new Exception('File type not allowed', 400); + //} + + // Check if file size is exceeding allowed limit + if (!$fileSize->isValid($file['size'])) { + throw new Exception('File size not allowed', 400); + } + + /* + * Models + */ + $device = Storage::getDevice('local'); + + if (!$upload->isValid($file['tmp_name'])) { + throw new Exception('Invalid file', 403); + } + + // Save to storage + $size = $device->getFileSize($file['tmp_name']); + $path = $device->getPath(\uniqid().'.'.\pathinfo($file['name'], PATHINFO_EXTENSION)); + + if (!$device->upload($file['tmp_name'], $path)) { // TODO deprecate 'upload' and replace with 'move' + throw new Exception('Failed moving file', 500); + } + + $mimeType = $device->getFileMimeType($path); // Get mime-type before compression and encryption + + if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled') { // Check if scans are enabled + $antiVirus = new Network('clamav', 3310); // Check if file size is exceeding allowed limit - if (!$fileSize->isValid($file['size'])) { - throw new Exception('File size not allowed', 400); - } - - /* - * Models - */ - $device = Storage::getDevice('local'); - - if (!$upload->isValid($file['tmp_name'])) { + if (!$antiVirus->fileScan($path)) { + $device->delete($path); throw new Exception('Invalid file', 403); } - - // Save to storage - $size = $device->getFileSize($file['tmp_name']); - $path = $device->getPath(\uniqid().'.'.\pathinfo($file['name'], PATHINFO_EXTENSION)); - - if (!$device->upload($file['tmp_name'], $path)) { // TODO deprecate 'upload' and replace with 'move' - throw new Exception('Failed moving file', 500); - } - - $mimeType = $device->getFileMimeType($path); // Get mime-type before compression and encryption - - if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled') { // Check if scans are enabled - $antiVirus = new Network('clamav', 3310); - - // Check if file size is exceeding allowed limit - if (!$antiVirus->fileScan($path)) { - $device->delete($path); - throw new Exception('Invalid file', 403); - } - } - - // Compression - $compressor = new GZIP(); - $data = $device->read($path); - $data = $compressor->compress($data); - $key = App::getEnv('_APP_OPENSSL_KEY_V1'); - $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); - $data = OpenSSL::encrypt($data, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag); - - if (!$device->write($path, $data)) { - throw new Exception('Failed to save file', 500); - } - - $sizeActual = $device->getFileSize($path); - - $file = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_FILES, - '$permissions' => [ - 'read' => $read, - 'write' => $write, - ], - 'dateCreated' => \time(), - 'folderId' => $folderId, - 'name' => $file['name'], - 'path' => $path, - 'signature' => $device->getFileHash($path), - 'mimeType' => $mimeType, - 'sizeOriginal' => $size, - 'sizeActual' => $sizeActual, - 'algorithm' => $compressor->getName(), - 'token' => \bin2hex(\random_bytes(64)), - 'comment' => '', - 'fileOpenSSLVersion' => '1', - 'fileOpenSSLCipher' => OpenSSL::CIPHER_AES_128_GCM, - 'fileOpenSSLTag' => \bin2hex($tag), - 'fileOpenSSLIV' => \bin2hex($iv), - ]); - - if (false === $file) { - throw new Exception('Failed saving file to DB', 500); - } - - $webhook - ->setParam('payload', $file->getArrayCopy()) - ; - - $audit - ->setParam('event', 'storage.files.create') - ->setParam('resource', 'storage/files/'.$file->getId()) - ; - - $usage - ->setParam('storage', $sizeActual) - ; - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($file->getArrayCopy()) - ; } - ); + + // Compression + $compressor = new GZIP(); + $data = $device->read($path); + $data = $compressor->compress($data); + $key = App::getEnv('_APP_OPENSSL_KEY_V1'); + $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); + $data = OpenSSL::encrypt($data, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag); + + if (!$device->write($path, $data)) { + throw new Exception('Failed to save file', 500); + } + + $sizeActual = $device->getFileSize($path); + + $file = $projectDB->createDocument([ + '$collection' => Database::SYSTEM_COLLECTION_FILES, + '$permissions' => [ + 'read' => $read, + 'write' => $write, + ], + 'dateCreated' => \time(), + 'folderId' => '', + 'name' => $file['name'], + 'path' => $path, + 'signature' => $device->getFileHash($path), + 'mimeType' => $mimeType, + 'sizeOriginal' => $size, + 'sizeActual' => $sizeActual, + 'algorithm' => $compressor->getName(), + 'token' => \bin2hex(\random_bytes(64)), + 'comment' => '', + 'fileOpenSSLVersion' => '1', + 'fileOpenSSLCipher' => OpenSSL::CIPHER_AES_128_GCM, + 'fileOpenSSLTag' => \bin2hex($tag), + 'fileOpenSSLIV' => \bin2hex($iv), + ]); + + if (false === $file) { + throw new Exception('Failed saving file to DB', 500); + } + + $webhook + ->setParam('payload', $file->getArrayCopy()) + ; + + $audit + ->setParam('event', 'storage.files.create') + ->setParam('resource', 'storage/files/'.$file->getId()) + ; + + $usage + ->setParam('storage', $sizeActual) + ; + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->json($file->getArrayCopy()) + ; + }, ['request', 'response', 'user', 'projectDB', 'webhook', 'audit', 'usage']); App::get('/v1/storage/files') ->desc('List Files') @@ -283,27 +196,28 @@ App::get('/v1/storage/files') ->param('limit', 25, function () { return new Range(0, 100); }, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('offset', 0, function () { return new Range(0, 2000); }, 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', function () { return new WhiteList(['ASC', 'DESC']); }, 'Order result by ASC or DESC order.', true) - ->action( - function ($search, $limit, $offset, $orderType) use ($response, $projectDB) { - $results = $projectDB->getCollection([ - 'limit' => $limit, - 'offset' => $offset, - 'orderField' => 'dateCreated', - 'orderType' => $orderType, - 'orderCast' => 'int', - 'search' => $search, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_FILES, - ], - ]); + ->action(function ($search, $limit, $offset, $orderType, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - $results = \array_map(function ($value) { /* @var $value \Database\Document */ - return $value->getArrayCopy(['$id', '$permissions', 'name', 'dateCreated', 'signature', 'mimeType', 'sizeOriginal']); - }, $results); + $results = $projectDB->getCollection([ + 'limit' => $limit, + 'offset' => $offset, + 'orderField' => 'dateCreated', + 'orderType' => $orderType, + 'orderCast' => 'int', + 'search' => $search, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_FILES, + ], + ]); - $response->json(['sum' => $projectDB->getSum(), 'files' => $results]); - } - ); + $results = \array_map(function ($value) { /* @var $value \Database\Document */ + return $value->getArrayCopy(['$id', '$permissions', 'name', 'dateCreated', 'signature', 'mimeType', 'sizeOriginal']); + }, $results); + + $response->json(['sum' => $projectDB->getSum(), 'files' => $results]); + }, ['response', 'projectDB']); App::get('/v1/storage/files/:fileId') ->desc('Get File') @@ -314,17 +228,18 @@ App::get('/v1/storage/files/:fileId') ->label('sdk.method', 'getFile') ->label('sdk.description', '/docs/references/storage/get-file.md') ->param('fileId', '', function () { return new UID(); }, 'File unique ID.') - ->action( - function ($fileId) use ($response, $projectDB) { - $file = $projectDB->getDocument($fileId); + ->action(function ($fileId, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { - throw new Exception('File not found', 404); - } + $file = $projectDB->getDocument($fileId); - $response->json($file->getArrayCopy(['$id', '$permissions', 'name', 'dateCreated', 'signature', 'mimeType', 'sizeOriginal'])); + if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { + throw new Exception('File not found', 404); } - ); + + $response->json($file->getArrayCopy(['$id', '$permissions', 'name', 'dateCreated', 'signature', 'mimeType', 'sizeOriginal'])); + }, ['response', 'projectDB']); App::get('/v1/storage/files/:fileId/preview') ->desc('Get File Preview') @@ -343,7 +258,7 @@ App::get('/v1/storage/files/:fileId/preview') ->param('background', '', function () { return new HexColor(); }, 'Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.', true) ->param('output', null, function () use ($outputs) { return new WhiteList(\array_merge(\array_keys($outputs), [null])); }, 'Output format type (jpeg, jpg, png, gif and webp).', true) ->action( - function ($fileId, $width, $height, $quality, $background, $output) use ($request, $response, $projectDB, $project, $inputs, $outputs, $fileLogos) { + function ($fileId, $width, $height, $quality, $background, $output) use ($request, $response, $projectDB, $project, $inputs, $outputs) { $storage = 'local'; if (!\extension_loaded('imagick')) { @@ -372,6 +287,7 @@ App::get('/v1/storage/files/:fileId/preview') $algorithm = $file->getAttribute('algorithm'); $cipher = $file->getAttribute('fileOpenSSLCipher'); $mime = $file->getAttribute('mimeType'); + $fileLogos = Config::getParam('storage-logos'); if (!\in_array($mime, $inputs)) { $path = (\array_key_exists($mime, $fileLogos)) ? $fileLogos[$mime] : $fileLogos['default']; @@ -516,8 +432,9 @@ App::get('/v1/storage/files/:fileId/view') ->param('fileId', '', function () { return new UID(); }, 'File unique ID.') ->param('as', '', function () { return new WhiteList(['pdf', /*'html',*/ 'text']); }, 'Choose a file format to convert your file to. Currently you can only convert word and pdf files to pdf or txt. This option is currently experimental only, use at your own risk.', true) ->action( - function ($fileId, $as) use ($response, $projectDB, $mimes) { - $file = $projectDB->getDocument($fileId); + function ($fileId, $as) use ($response, $projectDB) { + $file = $projectDB->getDocument($fileId); + $mimes = Config::getParam('storage-mimes'); if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { throw new Exception('File not found', 404); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 76a61aef3c..c63e52550f 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -1,7 +1,5 @@ desc('Mock a get request for SDK tests') ->label('scope', 'public') @@ -335,7 +333,8 @@ App::get('/v1/mock/tests/general/oauth2/failure') } ); -App::shutdown(function() use ($response, $request, &$result, $utopia) { +App::shutdown(function($response, $request, $utopia) { + $result = []; $route = $utopia->match($request); $path = APP_STORAGE_CACHE.'/tests.json'; $tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : []; @@ -353,4 +352,4 @@ App::shutdown(function() use ($response, $request, &$result, $utopia) { } $response->json(['result' => $route->getMethod() . ':' . $route->getURL() . ':passed']); -}, 'mock'); \ No newline at end of file +}, ['response', 'request', 'utopia'], 'mock'); \ No newline at end of file diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index b7d9845244..38c40498c2 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -7,7 +7,7 @@ use Utopia\Abuse\Adapters\TimeLimit; global $utopia, $request, $response, $register, $user, $project; -App::init(function () use ($utopia, $request, $response, $register, $user, $project) { +App::init(function ($utopia, $request, $response, $register, $user, $project) { $route = $utopia->match($request); if (empty($project->getId()) && $route->getLabel('abuse-limit', 0) > 0) { // Abuse limit requires an active project scope @@ -47,4 +47,4 @@ App::init(function () use ($utopia, $request, $response, $register, $user, $proj if ($abuse->check() && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') { throw new Exception('Too many requests', 429); } -}, 'api'); \ No newline at end of file +}, ['utopia', 'request', 'response', 'register', 'user', 'project'], 'api'); \ No newline at end of file diff --git a/app/controllers/shared/web.php b/app/controllers/shared/web.php index 1a15890c87..9ff3612290 100644 --- a/app/controllers/shared/web.php +++ b/app/controllers/shared/web.php @@ -4,9 +4,7 @@ use Utopia\App; use Utopia\View; use Utopia\Config\Config; -$layout = new View(__DIR__.'/../../views/layouts/default.phtml'); - -App::init(function () use ($utopia, $response, $request, $layout) { +App::init(function ($utopia, $response, $request, $layout) { /* AJAX check */ if (!empty($request->getQuery('version', ''))) { @@ -29,7 +27,6 @@ App::init(function () use ($utopia, $response, $request, $layout) { ; $time = (60 * 60 * 24 * 45); // 45 days cache - $isDev = (\Utopia\App::MODE_TYPE_DEVELOPMENT == Config::getParam('env')); $response ->addHeader('Cache-Control', 'public, max-age='.$time) @@ -40,7 +37,7 @@ App::init(function () use ($utopia, $response, $request, $layout) { $scope = $route->getLabel('scope', ''); $layout ->setParam('version', Config::getParam('version')) - ->setParam('isDev', $isDev) + ->setParam('isDev', App::isDevelopment()) ->setParam('class', $scope) ; -}, 'web'); +}, ['utopia', 'response', 'request', 'layout'], 'web'); diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php index d3d28c52a1..3369d7011b 100644 --- a/app/controllers/web/console.php +++ b/app/controllers/web/console.php @@ -11,14 +11,19 @@ use Appwrite\Database\Validator\Authorization; use Appwrite\Database\Validator\UID; use Appwrite\Storage\Storage; -App::init(function () use ($layout) { +App::init(function ($layout) { + /** @var Utopia\View $layout */ + $layout ->setParam('description', 'Appwrite Console allows you to easily manage, monitor, and control your entire backend API and tools.') ->setParam('analytics', 'UA-26264668-5') ; -}, 'console'); +}, ['layout'], 'console'); + +App::shutdown(function ($response, $layout) { + /** @var Utopia\Response $response */ + /** @var Utopia\View $layout */ -App::shutdown(function () use ($response, $layout) { $header = new View(__DIR__.'/../../views/console/comps/header.phtml'); $footer = new View(__DIR__.'/../../views/console/comps/footer.phtml'); @@ -33,14 +38,16 @@ App::shutdown(function () use ($response, $layout) { ; $response->send($layout->render()); -}, 'console'); +}, ['response', 'layout'], 'console'); App::get('/error/:code') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'home') ->param('code', null, new \Utopia\Validator\Numeric(), 'Valid status code number', false) - ->action(function ($code) use ($layout) { + ->action(function ($code, $layout) { + /** @var Utopia\View $layout */ + $page = new View(__DIR__.'/../../views/error.phtml'); $page @@ -50,13 +57,15 @@ App::get('/error/:code') $layout ->setParam('title', APP_NAME.' - Error') ->setParam('body', $page); - }); + }, ['layout']); App::get('/console') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') - ->action(function () use ($layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ + $page = new View(__DIR__.'/../../views/console/index.phtml'); $page @@ -66,13 +75,15 @@ App::get('/console') $layout ->setParam('title', APP_NAME.' - Console') ->setParam('body', $page); - }); + }, ['layout']); App::get('/console/account') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') - ->action(function () use ($layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ + $page = new View(__DIR__.'/../../views/console/account/index.phtml'); $cc = new View(__DIR__.'/../../views/console/forms/credit-card.phtml'); @@ -84,37 +95,43 @@ App::get('/console/account') $layout ->setParam('title', 'Account - '.APP_NAME) ->setParam('body', $page); - }); + }, ['layout']); App::get('/console/notifications') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') - ->action(function () use ($layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ + $page = new View(__DIR__.'/../../views/v1/console/notifications/index.phtml'); $layout ->setParam('title', APP_NAME.' - Notifications') ->setParam('body', $page); - }); + }, ['layout']); App::get('/console/home') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') - ->action(function () use ($layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ + $page = new View(__DIR__.'/../../views/console/home/index.phtml'); $layout ->setParam('title', APP_NAME.' - Console') ->setParam('body', $page); - }); + }, ['layout']); App::get('/console/settings') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') - ->action(function () use ($layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ + $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', '')); $page = new View(__DIR__.'/../../views/console/settings/index.phtml'); @@ -127,13 +144,15 @@ App::get('/console/settings') $layout ->setParam('title', APP_NAME.' - Settings') ->setParam('body', $page); - }); + }, ['layout']); App::get('/console/webhooks') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') - ->action(function () use ($layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ + $page = new View(__DIR__.'/../../views/console/webhooks/index.phtml'); $page @@ -143,13 +162,15 @@ App::get('/console/webhooks') $layout ->setParam('title', APP_NAME.' - Webhooks') ->setParam('body', $page); - }); + }, ['layout']); App::get('/console/keys') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') - ->action(function () use ($layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ + $scopes = include __DIR__.'/../../../app/config/scopes.php'; $page = new View(__DIR__.'/../../views/console/keys/index.phtml'); @@ -158,38 +179,46 @@ App::get('/console/keys') $layout ->setParam('title', APP_NAME.' - API Keys') ->setParam('body', $page); - }); + }, ['layout']); App::get('/console/tasks') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') - ->action(function () use ($layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ + $page = new View(__DIR__.'/../../views/console/tasks/index.phtml'); $layout ->setParam('title', APP_NAME.' - Tasks') ->setParam('body', $page); - }); + }, ['layout']); App::get('/console/database') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') - ->action(function () use ($layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ + $page = new View(__DIR__.'/../../views/console/database/index.phtml'); $layout ->setParam('title', APP_NAME.' - Database') ->setParam('body', $page); - }); + }, ['layout']); App::get('/console/database/collection') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') ->param('id', '', function () { return new UID(); }, 'Collection unique ID.') - ->action(function ($id) use ($response, $layout, $projectDB) { + ->action(function ($id, $response, $layout, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Utopia\View $layout */ + /** @var Appwrite\Database\Database $projectDB */ + Authorization::disable(); $collection = $projectDB->getDocument($id, false); Authorization::reset(); @@ -214,14 +243,17 @@ App::get('/console/database/collection') ->addHeader('Expires', 0) ->addHeader('Pragma', 'no-cache') ; - }); + }, ['response', 'layout', 'projectDB']); App::get('/console/database/document') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') ->param('collection', '', function () { return new UID(); }, 'Collection unique ID.') - ->action(function ($collection) use ($layout, $projectDB) { + ->action(function ($collection, $layout, $projectDB) { + /** @var Utopia\View $layout */ + /** @var Appwrite\Database\Database $projectDB */ + Authorization::disable(); $collection = $projectDB->getDocument($collection, false); Authorization::reset(); @@ -244,13 +276,14 @@ App::get('/console/database/document') $layout ->setParam('title', APP_NAME.' - Database Document') ->setParam('body', $page); - }); + }, ['layout', 'projectDB']); App::get('/console/storage') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') - ->action(function () use ($request, $layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ $page = new View(__DIR__.'/../../views/console/storage/index.phtml'); $page @@ -262,13 +295,15 @@ App::get('/console/storage') $layout ->setParam('title', APP_NAME.' - Storage') ->setParam('body', $page); - }); + }, ['layout']); App::get('/console/users') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') - ->action(function () use ($layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ + $page = new View(__DIR__.'/../../views/console/users/index.phtml'); $page->setParam('providers', Config::getParam('providers')); @@ -276,28 +311,32 @@ App::get('/console/users') $layout ->setParam('title', APP_NAME.' - Users') ->setParam('body', $page); - }); + }, ['layout']); App::get('/console/users/user') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') - ->action(function () use ($layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ + $page = new View(__DIR__.'/../../views/console/users/user.phtml'); $layout ->setParam('title', APP_NAME.' - User') ->setParam('body', $page); - }); + }, ['layout']); App::get('/console/users/teams/team') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') ->action(function () use ($layout) { + /** @var Utopia\View $layout */ + $page = new View(__DIR__.'/../../views/console/users/team.phtml'); $layout ->setParam('title', APP_NAME.' - Team') ->setParam('body', $page); - }); + }, ['layout']); diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index 977da411e0..b9e75ca52d 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -8,7 +8,7 @@ use Utopia\Config\Config; use Utopia\Validator\WhiteList; use Utopia\Validator\Range; -App::init(function () use ($layout) { +App::init(function ($layout) { $header = new View(__DIR__.'/../../views/home/comps/header.phtml'); $footer = new View(__DIR__.'/../../views/home/comps/footer.phtml'); @@ -24,11 +24,11 @@ App::init(function () use ($layout) { ->setParam('header', [$header]) ->setParam('footer', [$footer]) ; -}, 'home'); +}, ['layout'], 'home'); -App::shutdown(function () use ($response, $layout) { +App::shutdown(function ($response, $layout) { $response->send($layout->render()); -}, 'home'); +}, ['response', 'layout'], 'home'); App::get('/') ->groups(['web', 'home']) diff --git a/app/init.php b/app/init.php index 077a85457c..695e1db0d5 100644 --- a/app/init.php +++ b/app/init.php @@ -60,6 +60,8 @@ Config::load('services', __DIR__.'/../app/config/services.php'); // List of ser Config::load('avatar-browsers', __DIR__.'/../app/config/avatars/browsers.php'); Config::load('avatar-credit-cards', __DIR__.'/../app/config/avatars/credit-cards.php'); Config::load('avatar-flags', __DIR__.'/../app/config/avatars/flags.php'); +Config::load('storage-logos', __DIR__.'/../app/config/storage/logos.php'); +Config::load('storage-mimes', __DIR__.'/../app/config/storage/mimes.php'); Resque::setBackend(App::getEnv('_APP_REDIS_HOST', '') .':'.App::getEnv('_APP_REDIS_PORT', '')); @@ -141,10 +143,10 @@ $register->set('smtp', function () { return $mail; }); -$register->set('queue-webhooks', function () { +$register->set('queue-webhook', function () { return new Event('v1-webhooks', 'WebhooksV1'); }); -$register->set('queue-audits', function () { +$register->set('queue-audit', function () { return new Event('v1-audits', 'AuditsV1'); }); $register->set('queue-usage', function () { @@ -208,8 +210,6 @@ Locale::setLanguage('vi', include __DIR__.'/config/locales/vi.php'); Locale::setLanguage('zh-cn', include __DIR__.'/config/locales/zh-cn.php'); Locale::setLanguage('zh-tw', include __DIR__.'/config/locales/zh-tw.php'); -Locale::setDefault('en'); - \stream_context_set_default([ // Set global user agent and http settings 'http' => [ 'method' => 'GET', diff --git a/app/views/layouts/default.phtml b/app/views/layouts/default.phtml index 211a9e93a0..1c60c3bf23 100644 --- a/app/views/layouts/default.phtml +++ b/app/views/layouts/default.phtml @@ -11,6 +11,7 @@ $litespeed = $this->getParam('litespeed', true); $analytics = $this->getParam('analytics', 'UA-26264668-9'); $env = $this->getParam('env', ''); $canonical = $this->getParam('canonical', ''); +$locale = $this->getParam('locale', null); if(!empty($platforms)) { $platforms = array_map(function($platform) { @@ -30,14 +31,14 @@ if(!empty($platforms)) { } ?> +--> <?php echo $this->getParam('title', ''); ?> - + @@ -76,7 +77,7 @@ if(!empty($platforms)) { API: '/v1', PROJECT: 'console', PLATFORMS: , - LOCALE: 'escape(Locale::getText('settings.locale')); ?>', + LOCALE: 'escape($locale->getText('settings.locale')); ?>', PREFIX: 'escape($this->getParam('prefix')); ?>', ROLES: getParam('roles', [])); ?>, PAGING_LIMIT: diff --git a/public/index.php b/public/index.php index 00966df979..ef4d11d2a2 100644 --- a/public/index.php +++ b/public/index.php @@ -14,5 +14,6 @@ ini_set('display_errors', 0); ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL); +trigger_error('hide errors in prod', E_USER_NOTICE); include __DIR__ . '/../app/app.php'; From e336c3315c04a80c72f09af8b4a31fb410ffdc89 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 30 Jun 2020 07:32:36 +0300 Subject: [PATCH 06/17] Added storage config files --- app/config/storage/inputs.php | 8 ++++++++ app/config/storage/outputs.php | 9 +++++++++ app/controllers/api/storage.php | 24 ++++++------------------ 3 files changed, 23 insertions(+), 18 deletions(-) create mode 100644 app/config/storage/inputs.php create mode 100644 app/config/storage/outputs.php diff --git a/app/config/storage/inputs.php b/app/config/storage/inputs.php new file mode 100644 index 0000000000..986195b9ce --- /dev/null +++ b/app/config/storage/inputs.php @@ -0,0 +1,8 @@ + 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'gif' => 'image/gif', + 'png' => 'image/png', +]; \ No newline at end of file diff --git a/app/config/storage/outputs.php b/app/config/storage/outputs.php new file mode 100644 index 0000000000..0c2e435d63 --- /dev/null +++ b/app/config/storage/outputs.php @@ -0,0 +1,9 @@ + 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'gif' => 'image/gif', + 'png' => 'image/png', + 'webp' => 'image/webp', +]; \ No newline at end of file diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index aaafdc930e..aba5663fa6 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -23,21 +23,6 @@ use Appwrite\Resize\Resize; use Appwrite\OpenSSL\OpenSSL; use Utopia\Config\Config; -$inputs = [ - 'jpg' => 'image/jpeg', - 'jpeg' => 'image/jpeg', - 'gif' => 'image/gif', - 'png' => 'image/png', -]; - -$outputs = [ - 'jpg' => 'image/jpeg', - 'jpeg' => 'image/jpeg', - 'gif' => 'image/gif', - 'png' => 'image/png', - 'webp' => 'image/webp', -]; - App::init(function ($project) { Storage::addDevice('local', new Local(APP_STORAGE_UPLOADS.'/app-'.$project->getId())); }, ['project'], 'storage'); @@ -256,9 +241,9 @@ App::get('/v1/storage/files/:fileId/preview') ->param('height', 0, function () { return new Range(0, 4000); }, 'Resize preview image height, Pass an integer between 0 to 4000.', true) ->param('quality', 100, function () { return new Range(0, 100); }, 'Preview image quality. Pass an integer between 0 to 100. Defaults to 100.', true) ->param('background', '', function () { return new HexColor(); }, 'Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.', true) - ->param('output', null, function () use ($outputs) { return new WhiteList(\array_merge(\array_keys($outputs), [null])); }, 'Output format type (jpeg, jpg, png, gif and webp).', true) + ->param('output', null, function () { return new WhiteList(\array_merge(\array_keys(Config::getParam('storage-outputs')), [null])); }, 'Output format type (jpeg, jpg, png, gif and webp).', true) ->action( - function ($fileId, $width, $height, $quality, $background, $output) use ($request, $response, $projectDB, $project, $inputs, $outputs) { + function ($fileId, $width, $height, $quality, $background, $output) use ($request, $response, $projectDB, $project) { $storage = 'local'; if (!\extension_loaded('imagick')) { @@ -273,6 +258,10 @@ App::get('/v1/storage/files/:fileId/preview') $output = 'jpg'; } + $inputs = Config::getParam('storage-inputs'); + $outputs = Config::getParam('storage-outputs'); + $fileLogos = Config::getParam('storage-logos'); + $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache $key = \md5($fileId.$width.$height.$quality.$background.$storage.$output); @@ -287,7 +276,6 @@ App::get('/v1/storage/files/:fileId/preview') $algorithm = $file->getAttribute('algorithm'); $cipher = $file->getAttribute('fileOpenSSLCipher'); $mime = $file->getAttribute('mimeType'); - $fileLogos = Config::getParam('storage-logos'); if (!\in_array($mime, $inputs)) { $path = (\array_key_exists($mime, $fileLogos)) ? $fileLogos[$mime] : $fileLogos['default']; From a43a7c74d948a43fbbf23429eed155859d7682b1 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 30 Jun 2020 07:33:47 +0300 Subject: [PATCH 07/17] Added docs --- app/config/storage/inputs.php | 2 +- app/config/storage/outputs.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/config/storage/inputs.php b/app/config/storage/inputs.php index 986195b9ce..c580316c53 100644 --- a/app/config/storage/inputs.php +++ b/app/config/storage/inputs.php @@ -1,6 +1,6 @@ 'image/jpeg', 'jpeg' => 'image/jpeg', 'gif' => 'image/gif', diff --git a/app/config/storage/outputs.php b/app/config/storage/outputs.php index 0c2e435d63..dc521df9e4 100644 --- a/app/config/storage/outputs.php +++ b/app/config/storage/outputs.php @@ -1,6 +1,6 @@ 'image/jpeg', 'jpeg' => 'image/jpeg', 'gif' => 'image/gif', From 8d5a1e45d309be483a43bd0a4e871accf0bb0398 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 30 Jun 2020 07:34:13 +0300 Subject: [PATCH 08/17] Moved configs to init --- app/app.php | 31 +-------------------- app/init.php | 69 +++++++++++++++++++++++++++++++++++++++++----- app/tasks/sdks.php | 3 +- 3 files changed, 64 insertions(+), 39 deletions(-) diff --git a/app/app.php b/app/app.php index e9e59ecc99..70c3239b44 100644 --- a/app/app.php +++ b/app/app.php @@ -2,8 +2,6 @@ require_once __DIR__.'/init.php'; -global $register, $project; - use Utopia\App; use Utopia\Request; use Utopia\Response; @@ -11,13 +9,10 @@ use Utopia\View; use Utopia\Exception; use Utopia\Config\Config; use Utopia\Domains\Domain; -use Utopia\Locale\Locale; use Appwrite\Auth\Auth; use Appwrite\Database\Database; use Appwrite\Database\Document; use Appwrite\Database\Validator\Authorization; -use Appwrite\Database\Adapter\MySQL as MySQLAdapter; -use Appwrite\Database\Adapter\Redis as RedisAdapter; use Appwrite\Network\Validator\Origin; // Config::setParam('domain', $request->getServer('HTTP_HOST', '')); @@ -524,30 +519,6 @@ foreach(Config::getParam('services', []) as $service) { include_once $service['controller']; } -// Runtime Execution - -App::setResource('register', function() use ($register) { return $register; }); -App::setResource('layout', function($locale) { - $layout = new View(__DIR__.'/views/layouts/default.phtml'); - $layout->setParam('locale', $locale); - return $layout; }, ['locale']); -App::setResource('locale', function($request) { return new Locale('en'); }, ['request']); - -// Queues -App::setResource('webhook', function($register) { return $register->get('queue-webhook'); }, ['register']); -App::setResource('audit', function($register) { return $register->get('queue-audit'); }, ['register']); -App::setResource('usage', function($register) { return $register->get('queue-usage'); }, ['register']); -App::setResource('mail', function($register) { return $register->get('queue-mails'); }, ['register']); -App::setResource('deletes', function($register) { return $register->get('queue-deletes'); }, ['register']); - -// Test Mock -App::setResource('clients', function() { return []; }); -App::setResource('user', function() { return new Document([]); }); -App::setResource('project', function() { return new Document([]); }); -App::setResource('console', function() { return new Document([]); }); -App::setResource('consoleDB', function() { return new Database(); }); -App::setResource('projectDB', function() { return new Database([]); }); -App::setResource('mode', function() { return false; }); - $app = new App('Asia/Tel_Aviv'); + $app->run(new Request(), new Response()); \ No newline at end of file diff --git a/app/init.php b/app/init.php index 695e1db0d5..197644dc2f 100644 --- a/app/init.php +++ b/app/init.php @@ -18,8 +18,10 @@ use Utopia\Registry\Registry; use Appwrite\Database\Database; use Appwrite\Database\Adapter\MySQL as MySQLAdapter; use Appwrite\Database\Adapter\Redis as RedisAdapter; +use Appwrite\Database\Document; use Appwrite\Event\Event; use PHPMailer\PHPMailer\PHPMailer; +use Utopia\View; const APP_NAME = 'Appwrite'; const APP_DOMAIN = 'appwrite.io'; @@ -62,6 +64,8 @@ Config::load('avatar-credit-cards', __DIR__.'/../app/config/avatars/credit-cards Config::load('avatar-flags', __DIR__.'/../app/config/avatars/flags.php'); Config::load('storage-logos', __DIR__.'/../app/config/storage/logos.php'); Config::load('storage-mimes', __DIR__.'/../app/config/storage/mimes.php'); +Config::load('storage-inputs', __DIR__.'/../app/config/storage/inputs.php'); +Config::load('storage-outputs', __DIR__.'/../app/config/storage/outputs.php'); Resque::setBackend(App::getEnv('_APP_REDIS_HOST', '') .':'.App::getEnv('_APP_REDIS_PORT', '')); @@ -220,11 +224,62 @@ Locale::setLanguage('zh-tw', include __DIR__.'/config/locales/zh-tw.php'); ], ]); -/* - * Auth & Project Scope - */ -$consoleDB = new Database(); -$consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)); -$consoleDB->setNamespace('app_console'); // Should be replaced with param if we want to have parent projects -$consoleDB->setMocks(Config::getParam('collections', [])); \ No newline at end of file +// Runtime Execution + +App::setResource('register', function() use ($register) { + return $register; +}); + +App::setResource('layout', function($locale) { + $layout = new View(__DIR__.'/views/layouts/default.phtml'); + $layout->setParam('locale', $locale); + + return $layout; +}, ['locale']); + +App::setResource('locale', function() { + return new Locale('en'); +}); + +// Queues +App::setResource('webhook', function($register) { + return $register->get('queue-webhook'); +}, ['register']); + +App::setResource('audit', function($register) { + return $register->get('queue-audit'); +}, ['register']); + +App::setResource('usage', function($register) { + return $register->get('queue-usage'); +}, ['register']); + +App::setResource('mail', function($register) { + return $register->get('queue-mails'); +}, ['register']); + +App::setResource('deletes', function($register) { + return $register->get('queue-deletes'); +}, ['register']); + +// Test Mock +App::setResource('clients', function() { return []; }); + +App::setResource('user', function() { return new Document([]); }); + +App::setResource('project', function() { return new Document([]); }); + +App::setResource('console', function() { return new Document([]); }); + +App::setResource('consoleDB', function($register) { + $consoleDB = new Database(); + $consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)); + $consoleDB->setNamespace('app_console'); // Should be replaced with param if we want to have parent projects + + $consoleDB->setMocks(Config::getParam('collections', [])); +}, ['register']); + +App::setResource('projectDB', function() { return new Database([]); }); + +App::setResource('mode', function() { return false; }); diff --git a/app/tasks/sdks.php b/app/tasks/sdks.php index 0753c4dfef..a8f6036982 100644 --- a/app/tasks/sdks.php +++ b/app/tasks/sdks.php @@ -1,8 +1,7 @@ #!/bin/env php Date: Tue, 30 Jun 2020 08:27:52 +0300 Subject: [PATCH 09/17] Refactored all config files and added new ones --- app/app.php | 4 +- app/config/{locales.php => locale/codes.php} | 0 app/config/{ => locale}/currencies.php | 0 app/config/{ => locale}/eu.php | 0 app/config/{ => locale}/languages.php | 0 app/config/{ => locale}/phones.php | 0 .../templates/email-base.tpl} | 0 .../templates/email-cta.tpl} | 0 .../translations}/af.continents.php | 0 .../translations}/af.countries.php | 0 .../{locales => locale/translations}/af.php | 0 .../translations}/ar.continents.php | 0 .../translations}/ar.countries.php | 0 .../{locales => locale/translations}/ar.php | 0 .../translations}/bn.continents.php | 0 .../translations}/bn.countries.php | 0 .../{locales => locale/translations}/bn.php | 0 .../translations}/cat.continents.php | 0 .../translations}/cat.countries.php | 0 .../{locales => locale/translations}/cat.php | 0 .../translations}/cz.continents.php | 0 .../translations}/cz.countries.php | 0 .../{locales => locale/translations}/cz.php | 0 .../translations}/de.continents.php | 0 .../translations}/de.countries.php | 0 .../{locales => locale/translations}/de.php | 0 .../translations}/en.continents.php | 0 .../translations}/en.countries.php | 0 .../{locales => locale/translations}/en.php | 0 .../translations}/es.continents.php | 0 .../translations}/es.countries.php | 0 .../{locales => locale/translations}/es.php | 0 .../translations}/fi.continents.php | 0 .../translations}/fi.countries.php | 0 .../{locales => locale/translations}/fi.php | 0 .../translations}/fo.continents.php | 0 .../translations}/fo.countries.php | 0 .../{locales => locale/translations}/fo.php | 0 .../translations}/fr.continents.php | 0 .../translations}/fr.countries.php | 0 .../{locales => locale/translations}/fr.php | 0 .../translations}/gr.continents.php | 0 .../translations}/gr.countries.php | 0 .../{locales => locale/translations}/gr.php | 0 .../translations}/he.continents.php | 0 .../translations}/he.countries.php | 0 .../{locales => locale/translations}/he.php | 0 .../translations}/hi.continents.php | 0 .../translations}/hi.countries.php | 0 .../{locales => locale/translations}/hi.php | 0 .../translations}/hu.continents.php | 0 .../translations}/hu.countries.php | 0 .../{locales => locale/translations}/hu.php | 0 .../translations}/hy.continents.php | 0 .../translations}/hy.countries.php | 0 .../{locales => locale/translations}/hy.php | 0 .../translations}/id.continents.php | 0 .../translations}/id.countries.php | 0 .../{locales => locale/translations}/id.php | 0 .../translations}/is.continents.php | 0 .../translations}/is.countries.php | 0 .../{locales => locale/translations}/is.php | 0 .../translations}/it.continents.php | 0 .../translations}/it.countries.php | 0 .../{locales => locale/translations}/it.php | 0 .../translations}/ja.continents.php | 0 .../translations}/ja.countries.php | 0 .../{locales => locale/translations}/ja.php | 0 .../translations}/jv.continents.php | 0 .../translations}/jv.countries.php | 0 .../{locales => locale/translations}/jv.php | 0 .../translations}/km.continents.php | 0 .../translations}/km.countries.php | 0 .../{locales => locale/translations}/km.php | 0 .../translations}/ko.continents.php | 0 .../translations}/ko.countries.php | 0 .../{locales => locale/translations}/ko.php | 0 .../translations}/lt.continents.php | 0 .../translations}/lt.countries.php | 0 .../{locales => locale/translations}/lt.php | 0 .../translations}/ml.continents.php | 0 .../translations}/ml.countries.php | 0 .../{locales => locale/translations}/ml.php | 0 .../translations}/ms.continents.php | 0 .../translations}/ms.countries.php | 0 .../{locales => locale/translations}/ms.php | 0 .../translations}/nl.continents.php | 0 .../translations}/nl.countries.php | 0 .../{locales => locale/translations}/nl.php | 0 .../translations}/no.continents.php | 0 .../translations}/no.countries.php | 0 .../{locales => locale/translations}/no.php | 0 .../translations}/ph.continents.php | 0 .../translations}/ph.countries.php | 0 .../{locales => locale/translations}/ph.php | 0 .../translations}/pl.continents.php | 0 .../translations}/pl.countries.php | 0 .../{locales => locale/translations}/pl.php | 0 .../translations}/pt-br.continents.php | 0 .../translations}/pt-br.countries.php | 0 .../translations}/pt-br.php | 0 .../translations}/pt-pt.continents.php | 0 .../translations}/pt-pt.countries.php | 0 .../translations}/pt-pt.php | 0 .../translations}/ro.continents.php | 0 .../translations}/ro.countries.php | 0 .../{locales => locale/translations}/ro.php | 0 .../translations}/ru.continents.php | 0 .../translations}/ru.countries.php | 0 .../{locales => locale/translations}/ru.php | 0 .../translations}/si.continents.php | 0 .../translations}/si.countries.php | 0 .../{locales => locale/translations}/si.php | 0 .../translations}/sl.continents.php | 0 .../translations}/sl.countries.php | 0 .../{locales => locale/translations}/sl.php | 0 .../translations}/sq.continents.php | 0 .../translations}/sq.countries.php | 0 .../{locales => locale/translations}/sq.php | 0 .../translations}/sv.continents.php | 0 .../translations}/sv.countries.php | 0 .../{locales => locale/translations}/sv.php | 0 .../translations}/ta.continents.php | 0 .../translations}/ta.countries.php | 0 .../{locales => locale/translations}/ta.php | 0 .../templates/af.email.auth.confirm.tpl | 0 .../templates/af.email.auth.invitation.tpl | 0 .../templates/af.email.auth.recovery.tpl | 0 .../templates/alb.email.auth.confirm.tpl | 0 .../templates/alb.email.auth.invitation.tpl | 0 .../templates/alb.email.auth.recovery.tpl | 0 .../templates/ar.email.auth.confirm.tpl | 0 .../templates/ar.email.auth.invitation.tpl | 0 .../templates/ar.email.auth.recovery.tpl | 0 .../templates/bn.email.auth.confirm.tpl | 0 .../templates/bn.email.auth.invitation.tpl | 0 .../templates/bn.email.auth.recovery.tpl | 0 .../templates/cat.email.auth.confirm.tpl | 0 .../templates/cat.email.auth.invitation.tpl | 0 .../templates/cat.email.auth.recovery.tpl | 0 .../templates/cz.email.auth.confirm.tpl | 0 .../templates/cz.email.auth.invitation.tpl | 0 .../templates/cz.email.auth.recovery.tpl | 0 .../templates/de.email.auth.confirm.tpl | 0 .../templates/de.email.auth.invitation.tpl | 0 .../templates/de.email.auth.recovery.tpl | 0 .../templates/en.email.auth.confirm.tpl | 0 .../templates/en.email.auth.invitation.tpl | 0 .../templates/en.email.auth.recovery.tpl | 0 .../templates/es.email.auth.confirm.tpl | 0 .../templates/es.email.auth.invitation.tpl | 0 .../templates/es.email.auth.recovery.tpl | 0 .../templates/fi.email.auth.confirm.tpl | 0 .../templates/fi.email.auth.invitation.tpl | 0 .../templates/fi.email.auth.recovery.tpl | 0 .../templates/fo.email.auth.confirm.tpl | 0 .../templates/fo.email.auth.invitation.tpl | 0 .../templates/fo.email.auth.recovery.tpl | 0 .../templates/fr.email.auth.confirm.tpl | 0 .../templates/fr.email.auth.invitation.tpl | 0 .../templates/fr.email.auth.recovery.tpl | 0 .../templates/gr.email.auth.confirm.tpl | 0 .../templates/gr.email.auth.invitation.tpl | 0 .../templates/gr.email.auth.recovery.tpl | 0 .../templates/he.email.auth.confirm.tpl | 0 .../templates/he.email.auth.invitation.tpl | 0 .../templates/he.email.auth.recovery.tpl | 0 .../templates/hi.email.auth.confirm.tpl | 0 .../templates/hi.email.auth.invitation.tpl | 0 .../templates/hi.email.auth.recovery.tpl | 0 .../templates/hu.email.auth.confirm.tpl | 0 .../templates/hu.email.auth.invitation.tpl | 0 .../templates/hu.email.auth.recovery.tpl | 0 .../templates/hy.email.auth.confirm.tpl | 0 .../templates/hy.email.auth.invitation.tpl | 0 .../templates/hy.email.auth.recovery.tpl | 0 .../templates/id.email.auth.confirm.tpl | 0 .../templates/id.email.auth.invitation.tpl | 0 .../templates/id.email.auth.recovery.tpl | 0 .../templates/is.email.auth.confirm.tpl | 0 .../templates/is.email.auth.invitation.tpl | 0 .../templates/is.email.auth.recovery.tpl | 0 .../templates/it.email.auth.confirm.tpl | 0 .../templates/it.email.auth.invitation.tpl | 0 .../templates/it.email.auth.recovery.tpl | 0 .../templates/ja.email.auth.confirm.tpl | 0 .../templates/ja.email.auth.invitation.tpl | 0 .../templates/ja.email.auth.recovery.tpl | 0 .../templates/jv.email.auth.confirm.tpl | 0 .../templates/jv.email.auth.invitation.tpl | 0 .../templates/jv.email.auth.recovery.tpl | 0 .../templates/km.email.auth.confirm.tpl | 0 .../templates/km.email.auth.invitation.tpl | 0 .../templates/km.email.auth.recovery.tpl | 0 .../templates/ko.email.auth.confirm.tpl | 0 .../templates/ko.email.auth.invitation.tpl | 0 .../templates/ko.email.auth.recovery.tpl | 0 .../templates/lt.email.auth.confirm.tpl | 0 .../templates/lt.email.auth.invitation.tpl | 0 .../templates/lt.email.auth.recovery.tpl | 0 .../templates/ml.email.auth.confirm.tpl | 0 .../templates/ml.email.auth.invitation.tpl | 0 .../templates/ml.email.auth.recovery.tpl | 0 .../templates/my.email.auth.confirm.tpl | 0 .../templates/my.email.auth.invitation.tpl | 0 .../templates/my.email.auth.recovery.tpl | 0 .../templates/nl.email.auth.confirm.tpl | 0 .../templates/nl.email.auth.invitation.tpl | 0 .../templates/nl.email.auth.recovery.tpl | 0 .../templates/no.email.auth.confirm.tpl | 0 .../templates/no.email.auth.invitation.tpl | 0 .../templates/no.email.auth.recovery.tpl | 0 .../templates/ph.email.auth.confirm.tpl | 0 .../templates/ph.email.auth.invitation.tpl | 0 .../templates/ph.email.auth.recovery.tpl | 0 .../templates/pl.email.auth.confirm.tpl | 0 .../templates/pl.email.auth.invitation.tpl | 0 .../templates/pl.email.auth.recovery.tpl | 0 .../templates/pt-br.email.auth.confirm.tpl | 0 .../templates/pt-br.email.auth.invitation.tpl | 0 .../templates/pt-br.email.auth.recovery.tpl | 0 .../templates/pt-pt.email.auth.confirm.tpl | 0 .../templates/pt-pt.email.auth.invitation.tpl | 0 .../templates/pt-pt.email.auth.recovery.tpl | 0 .../templates/ro.email.auth.confirm.tpl | 0 .../templates/ro.email.auth.invitation.tpl | 0 .../templates/ro.email.auth.recovery.tpl | 0 .../templates/ru.email.auth.confirm.tpl | 0 .../templates/ru.email.auth.invitation.tpl | 0 .../templates/ru.email.auth.recovery.tpl | 0 .../templates/si.email.auth.confirm.tpl | 0 .../templates/si.email.auth.invitation.tpl | 0 .../templates/si.email.auth.recovery.tpl | 0 .../templates/sl.email.auth.confirm.tpl | 0 .../templates/sl.email.auth.invitation.tpl | 0 .../templates/sl.email.auth.recovery.tpl | 0 .../templates/sv.email.auth.confirm.tpl | 0 .../templates/sv.email.auth.invitation.tpl | 0 .../templates/sv.email.auth.recovery.tpl | 0 .../templates/ta.email.auth.confirm.tpl | 0 .../templates/ta.email.auth.invitation.tpl | 0 .../templates/ta.email.auth.recovery.tpl | 0 .../templates/th.email.auth.confirm.tpl | 0 .../templates/th.email.auth.invitation.tpl | 0 .../templates/th.email.auth.recovery.tpl | 0 .../templates/tr.email.auth.confirm.tpl | 0 .../templates/tr.email.auth.invitation.tpl | 0 .../templates/tr.email.auth.recovery.tpl | 0 .../templates/ua.email.auth.confirm.tpl | 0 .../templates/ua.email.auth.invitation.tpl | 0 .../templates/ua.email.auth.recovery.tpl | 0 .../templates/vi.email.auth.confirm.tpl | 0 .../templates/vi.email.auth.invitation.tpl | 0 .../templates/vi.email.auth.recovery.tpl | 0 .../templates/zh-cn.email.auth.confirm.tpl | 0 .../templates/zh-cn.email.auth.invitation.tpl | 0 .../templates/zh-cn.email.auth.recovery.tpl | 0 .../templates/zh-tw.email.auth.confirm.tpl | 0 .../templates/zh-tw.email.auth.invitation.tpl | 0 .../templates/zh-tw.email.auth.recovery.tpl | 0 .../translations}/th.continents.php | 0 .../translations}/th.countries.php | 0 .../{locales => locale/translations}/th.php | 0 .../translations}/tr.continents.php | 0 .../translations}/tr.countries.php | 0 .../{locales => locale/translations}/tr.php | 0 .../translations}/ua.continents.php | 0 .../translations}/ua.countries.php | 0 .../{locales => locale/translations}/ua.php | 0 .../translations}/vi.continents.php | 0 .../translations}/vi.countries.php | 0 .../{locales => locale/translations}/vi.php | 0 .../translations}/zh-cn.continents.php | 0 .../translations}/zh-cn.countries.php | 0 .../translations}/zh-cn.php | 0 .../translations}/zh-tw.continents.php | 0 .../translations}/zh-tw.countries.php | 0 .../translations}/zh-tw.php | 0 app/controllers/api/account.php | 12 +- app/controllers/api/locale.php | 13 +- app/controllers/api/projects.php | 6 +- app/controllers/api/teams.php | 6 +- app/controllers/web/console.php | 2 +- app/init.php | 123 +++++++++--------- tests/e2e/Services/Locale/LocaleBase.php | 6 +- 285 files changed, 88 insertions(+), 84 deletions(-) rename app/config/{locales.php => locale/codes.php} (100%) rename app/config/{ => locale}/currencies.php (100%) rename app/config/{ => locale}/eu.php (100%) rename app/config/{ => locale}/languages.php (100%) rename app/config/{ => locale}/phones.php (100%) rename app/config/{locales/templates/_base.tpl => locale/templates/email-base.tpl} (100%) rename app/config/{locales/templates/_cta.tpl => locale/templates/email-cta.tpl} (100%) rename app/config/{locales => locale/translations}/af.continents.php (100%) rename app/config/{locales => locale/translations}/af.countries.php (100%) rename app/config/{locales => locale/translations}/af.php (100%) rename app/config/{locales => locale/translations}/ar.continents.php (100%) rename app/config/{locales => locale/translations}/ar.countries.php (100%) rename app/config/{locales => locale/translations}/ar.php (100%) rename app/config/{locales => locale/translations}/bn.continents.php (100%) rename app/config/{locales => locale/translations}/bn.countries.php (100%) rename app/config/{locales => locale/translations}/bn.php (100%) rename app/config/{locales => locale/translations}/cat.continents.php (100%) rename app/config/{locales => locale/translations}/cat.countries.php (100%) rename app/config/{locales => locale/translations}/cat.php (100%) rename app/config/{locales => locale/translations}/cz.continents.php (100%) rename app/config/{locales => locale/translations}/cz.countries.php (100%) rename app/config/{locales => locale/translations}/cz.php (100%) rename app/config/{locales => locale/translations}/de.continents.php (100%) rename app/config/{locales => locale/translations}/de.countries.php (100%) rename app/config/{locales => locale/translations}/de.php (100%) rename app/config/{locales => locale/translations}/en.continents.php (100%) rename app/config/{locales => locale/translations}/en.countries.php (100%) rename app/config/{locales => locale/translations}/en.php (100%) rename app/config/{locales => locale/translations}/es.continents.php (100%) rename app/config/{locales => locale/translations}/es.countries.php (100%) rename app/config/{locales => locale/translations}/es.php (100%) rename app/config/{locales => locale/translations}/fi.continents.php (100%) rename app/config/{locales => locale/translations}/fi.countries.php (100%) rename app/config/{locales => locale/translations}/fi.php (100%) rename app/config/{locales => locale/translations}/fo.continents.php (100%) rename app/config/{locales => locale/translations}/fo.countries.php (100%) rename app/config/{locales => locale/translations}/fo.php (100%) rename app/config/{locales => locale/translations}/fr.continents.php (100%) rename app/config/{locales => locale/translations}/fr.countries.php (100%) rename app/config/{locales => locale/translations}/fr.php (100%) rename app/config/{locales => locale/translations}/gr.continents.php (100%) rename app/config/{locales => locale/translations}/gr.countries.php (100%) rename app/config/{locales => locale/translations}/gr.php (100%) rename app/config/{locales => locale/translations}/he.continents.php (100%) rename app/config/{locales => locale/translations}/he.countries.php (100%) rename app/config/{locales => locale/translations}/he.php (100%) rename app/config/{locales => locale/translations}/hi.continents.php (100%) rename app/config/{locales => locale/translations}/hi.countries.php (100%) rename app/config/{locales => locale/translations}/hi.php (100%) rename app/config/{locales => locale/translations}/hu.continents.php (100%) rename app/config/{locales => locale/translations}/hu.countries.php (100%) rename app/config/{locales => locale/translations}/hu.php (100%) rename app/config/{locales => locale/translations}/hy.continents.php (100%) rename app/config/{locales => locale/translations}/hy.countries.php (100%) rename app/config/{locales => locale/translations}/hy.php (100%) rename app/config/{locales => locale/translations}/id.continents.php (100%) rename app/config/{locales => locale/translations}/id.countries.php (100%) rename app/config/{locales => locale/translations}/id.php (100%) rename app/config/{locales => locale/translations}/is.continents.php (100%) rename app/config/{locales => locale/translations}/is.countries.php (100%) rename app/config/{locales => locale/translations}/is.php (100%) rename app/config/{locales => locale/translations}/it.continents.php (100%) rename app/config/{locales => locale/translations}/it.countries.php (100%) rename app/config/{locales => locale/translations}/it.php (100%) rename app/config/{locales => locale/translations}/ja.continents.php (100%) rename app/config/{locales => locale/translations}/ja.countries.php (100%) rename app/config/{locales => locale/translations}/ja.php (100%) rename app/config/{locales => locale/translations}/jv.continents.php (100%) rename app/config/{locales => locale/translations}/jv.countries.php (100%) rename app/config/{locales => locale/translations}/jv.php (100%) rename app/config/{locales => locale/translations}/km.continents.php (100%) rename app/config/{locales => locale/translations}/km.countries.php (100%) rename app/config/{locales => locale/translations}/km.php (100%) rename app/config/{locales => locale/translations}/ko.continents.php (100%) rename app/config/{locales => locale/translations}/ko.countries.php (100%) rename app/config/{locales => locale/translations}/ko.php (100%) rename app/config/{locales => locale/translations}/lt.continents.php (100%) rename app/config/{locales => locale/translations}/lt.countries.php (100%) rename app/config/{locales => locale/translations}/lt.php (100%) rename app/config/{locales => locale/translations}/ml.continents.php (100%) rename app/config/{locales => locale/translations}/ml.countries.php (100%) rename app/config/{locales => locale/translations}/ml.php (100%) rename app/config/{locales => locale/translations}/ms.continents.php (100%) rename app/config/{locales => locale/translations}/ms.countries.php (100%) rename app/config/{locales => locale/translations}/ms.php (100%) rename app/config/{locales => locale/translations}/nl.continents.php (100%) rename app/config/{locales => locale/translations}/nl.countries.php (100%) rename app/config/{locales => locale/translations}/nl.php (100%) rename app/config/{locales => locale/translations}/no.continents.php (100%) rename app/config/{locales => locale/translations}/no.countries.php (100%) rename app/config/{locales => locale/translations}/no.php (100%) rename app/config/{locales => locale/translations}/ph.continents.php (100%) rename app/config/{locales => locale/translations}/ph.countries.php (100%) rename app/config/{locales => locale/translations}/ph.php (100%) rename app/config/{locales => locale/translations}/pl.continents.php (100%) rename app/config/{locales => locale/translations}/pl.countries.php (100%) rename app/config/{locales => locale/translations}/pl.php (100%) rename app/config/{locales => locale/translations}/pt-br.continents.php (100%) rename app/config/{locales => locale/translations}/pt-br.countries.php (100%) rename app/config/{locales => locale/translations}/pt-br.php (100%) rename app/config/{locales => locale/translations}/pt-pt.continents.php (100%) rename app/config/{locales => locale/translations}/pt-pt.countries.php (100%) rename app/config/{locales => locale/translations}/pt-pt.php (100%) rename app/config/{locales => locale/translations}/ro.continents.php (100%) rename app/config/{locales => locale/translations}/ro.countries.php (100%) rename app/config/{locales => locale/translations}/ro.php (100%) rename app/config/{locales => locale/translations}/ru.continents.php (100%) rename app/config/{locales => locale/translations}/ru.countries.php (100%) rename app/config/{locales => locale/translations}/ru.php (100%) rename app/config/{locales => locale/translations}/si.continents.php (100%) rename app/config/{locales => locale/translations}/si.countries.php (100%) rename app/config/{locales => locale/translations}/si.php (100%) rename app/config/{locales => locale/translations}/sl.continents.php (100%) rename app/config/{locales => locale/translations}/sl.countries.php (100%) rename app/config/{locales => locale/translations}/sl.php (100%) rename app/config/{locales => locale/translations}/sq.continents.php (100%) rename app/config/{locales => locale/translations}/sq.countries.php (100%) rename app/config/{locales => locale/translations}/sq.php (100%) rename app/config/{locales => locale/translations}/sv.continents.php (100%) rename app/config/{locales => locale/translations}/sv.countries.php (100%) rename app/config/{locales => locale/translations}/sv.php (100%) rename app/config/{locales => locale/translations}/ta.continents.php (100%) rename app/config/{locales => locale/translations}/ta.countries.php (100%) rename app/config/{locales => locale/translations}/ta.php (100%) rename app/config/{locales => locale/translations}/templates/af.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/af.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/af.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/alb.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/alb.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/alb.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/ar.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/ar.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/ar.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/bn.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/bn.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/bn.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/cat.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/cat.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/cat.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/cz.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/cz.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/cz.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/de.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/de.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/de.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/en.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/en.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/en.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/es.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/es.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/es.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/fi.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/fi.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/fi.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/fo.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/fo.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/fo.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/fr.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/fr.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/fr.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/gr.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/gr.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/gr.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/he.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/he.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/he.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/hi.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/hi.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/hi.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/hu.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/hu.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/hu.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/hy.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/hy.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/hy.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/id.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/id.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/id.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/is.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/is.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/is.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/it.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/it.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/it.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/ja.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/ja.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/ja.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/jv.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/jv.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/jv.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/km.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/km.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/km.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/ko.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/ko.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/ko.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/lt.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/lt.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/lt.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/ml.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/ml.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/ml.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/my.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/my.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/my.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/nl.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/nl.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/nl.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/no.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/no.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/no.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/ph.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/ph.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/ph.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/pl.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/pl.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/pl.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/pt-br.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/pt-br.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/pt-br.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/pt-pt.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/pt-pt.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/pt-pt.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/ro.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/ro.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/ro.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/ru.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/ru.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/ru.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/si.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/si.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/si.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/sl.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/sl.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/sl.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/sv.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/sv.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/sv.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/ta.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/ta.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/ta.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/th.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/th.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/th.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/tr.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/tr.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/tr.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/ua.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/ua.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/ua.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/vi.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/vi.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/vi.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/zh-cn.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/zh-cn.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/zh-cn.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/templates/zh-tw.email.auth.confirm.tpl (100%) rename app/config/{locales => locale/translations}/templates/zh-tw.email.auth.invitation.tpl (100%) rename app/config/{locales => locale/translations}/templates/zh-tw.email.auth.recovery.tpl (100%) rename app/config/{locales => locale/translations}/th.continents.php (100%) rename app/config/{locales => locale/translations}/th.countries.php (100%) rename app/config/{locales => locale/translations}/th.php (100%) rename app/config/{locales => locale/translations}/tr.continents.php (100%) rename app/config/{locales => locale/translations}/tr.countries.php (100%) rename app/config/{locales => locale/translations}/tr.php (100%) rename app/config/{locales => locale/translations}/ua.continents.php (100%) rename app/config/{locales => locale/translations}/ua.countries.php (100%) rename app/config/{locales => locale/translations}/ua.php (100%) rename app/config/{locales => locale/translations}/vi.continents.php (100%) rename app/config/{locales => locale/translations}/vi.countries.php (100%) rename app/config/{locales => locale/translations}/vi.php (100%) rename app/config/{locales => locale/translations}/zh-cn.continents.php (100%) rename app/config/{locales => locale/translations}/zh-cn.countries.php (100%) rename app/config/{locales => locale/translations}/zh-cn.php (100%) rename app/config/{locales => locale/translations}/zh-tw.continents.php (100%) rename app/config/{locales => locale/translations}/zh-tw.countries.php (100%) rename app/config/{locales => locale/translations}/zh-tw.php (100%) diff --git a/app/app.php b/app/app.php index 70c3239b44..f1d29c60f3 100644 --- a/app/app.php +++ b/app/app.php @@ -135,8 +135,8 @@ App::init(function ($utopia, $request, $response, $user, $project, $console, $we /** @var $locale Utopia\Locale\Locale */ $localeParam = $request->getParam('locale', $request->getHeader('X-Appwrite-Locale', '')); - - if (\in_array($localeParam, Config::getParam('locales'))) { + + if (\in_array($localeParam, Config::getParam('locale-codes'))) { $locale->setDefault($localeParam); }; diff --git a/app/config/locales.php b/app/config/locale/codes.php similarity index 100% rename from app/config/locales.php rename to app/config/locale/codes.php diff --git a/app/config/currencies.php b/app/config/locale/currencies.php similarity index 100% rename from app/config/currencies.php rename to app/config/locale/currencies.php diff --git a/app/config/eu.php b/app/config/locale/eu.php similarity index 100% rename from app/config/eu.php rename to app/config/locale/eu.php diff --git a/app/config/languages.php b/app/config/locale/languages.php similarity index 100% rename from app/config/languages.php rename to app/config/locale/languages.php diff --git a/app/config/phones.php b/app/config/locale/phones.php similarity index 100% rename from app/config/phones.php rename to app/config/locale/phones.php diff --git a/app/config/locales/templates/_base.tpl b/app/config/locale/templates/email-base.tpl similarity index 100% rename from app/config/locales/templates/_base.tpl rename to app/config/locale/templates/email-base.tpl diff --git a/app/config/locales/templates/_cta.tpl b/app/config/locale/templates/email-cta.tpl similarity index 100% rename from app/config/locales/templates/_cta.tpl rename to app/config/locale/templates/email-cta.tpl diff --git a/app/config/locales/af.continents.php b/app/config/locale/translations/af.continents.php similarity index 100% rename from app/config/locales/af.continents.php rename to app/config/locale/translations/af.continents.php diff --git a/app/config/locales/af.countries.php b/app/config/locale/translations/af.countries.php similarity index 100% rename from app/config/locales/af.countries.php rename to app/config/locale/translations/af.countries.php diff --git a/app/config/locales/af.php b/app/config/locale/translations/af.php similarity index 100% rename from app/config/locales/af.php rename to app/config/locale/translations/af.php diff --git a/app/config/locales/ar.continents.php b/app/config/locale/translations/ar.continents.php similarity index 100% rename from app/config/locales/ar.continents.php rename to app/config/locale/translations/ar.continents.php diff --git a/app/config/locales/ar.countries.php b/app/config/locale/translations/ar.countries.php similarity index 100% rename from app/config/locales/ar.countries.php rename to app/config/locale/translations/ar.countries.php diff --git a/app/config/locales/ar.php b/app/config/locale/translations/ar.php similarity index 100% rename from app/config/locales/ar.php rename to app/config/locale/translations/ar.php diff --git a/app/config/locales/bn.continents.php b/app/config/locale/translations/bn.continents.php similarity index 100% rename from app/config/locales/bn.continents.php rename to app/config/locale/translations/bn.continents.php diff --git a/app/config/locales/bn.countries.php b/app/config/locale/translations/bn.countries.php similarity index 100% rename from app/config/locales/bn.countries.php rename to app/config/locale/translations/bn.countries.php diff --git a/app/config/locales/bn.php b/app/config/locale/translations/bn.php similarity index 100% rename from app/config/locales/bn.php rename to app/config/locale/translations/bn.php diff --git a/app/config/locales/cat.continents.php b/app/config/locale/translations/cat.continents.php similarity index 100% rename from app/config/locales/cat.continents.php rename to app/config/locale/translations/cat.continents.php diff --git a/app/config/locales/cat.countries.php b/app/config/locale/translations/cat.countries.php similarity index 100% rename from app/config/locales/cat.countries.php rename to app/config/locale/translations/cat.countries.php diff --git a/app/config/locales/cat.php b/app/config/locale/translations/cat.php similarity index 100% rename from app/config/locales/cat.php rename to app/config/locale/translations/cat.php diff --git a/app/config/locales/cz.continents.php b/app/config/locale/translations/cz.continents.php similarity index 100% rename from app/config/locales/cz.continents.php rename to app/config/locale/translations/cz.continents.php diff --git a/app/config/locales/cz.countries.php b/app/config/locale/translations/cz.countries.php similarity index 100% rename from app/config/locales/cz.countries.php rename to app/config/locale/translations/cz.countries.php diff --git a/app/config/locales/cz.php b/app/config/locale/translations/cz.php similarity index 100% rename from app/config/locales/cz.php rename to app/config/locale/translations/cz.php diff --git a/app/config/locales/de.continents.php b/app/config/locale/translations/de.continents.php similarity index 100% rename from app/config/locales/de.continents.php rename to app/config/locale/translations/de.continents.php diff --git a/app/config/locales/de.countries.php b/app/config/locale/translations/de.countries.php similarity index 100% rename from app/config/locales/de.countries.php rename to app/config/locale/translations/de.countries.php diff --git a/app/config/locales/de.php b/app/config/locale/translations/de.php similarity index 100% rename from app/config/locales/de.php rename to app/config/locale/translations/de.php diff --git a/app/config/locales/en.continents.php b/app/config/locale/translations/en.continents.php similarity index 100% rename from app/config/locales/en.continents.php rename to app/config/locale/translations/en.continents.php diff --git a/app/config/locales/en.countries.php b/app/config/locale/translations/en.countries.php similarity index 100% rename from app/config/locales/en.countries.php rename to app/config/locale/translations/en.countries.php diff --git a/app/config/locales/en.php b/app/config/locale/translations/en.php similarity index 100% rename from app/config/locales/en.php rename to app/config/locale/translations/en.php diff --git a/app/config/locales/es.continents.php b/app/config/locale/translations/es.continents.php similarity index 100% rename from app/config/locales/es.continents.php rename to app/config/locale/translations/es.continents.php diff --git a/app/config/locales/es.countries.php b/app/config/locale/translations/es.countries.php similarity index 100% rename from app/config/locales/es.countries.php rename to app/config/locale/translations/es.countries.php diff --git a/app/config/locales/es.php b/app/config/locale/translations/es.php similarity index 100% rename from app/config/locales/es.php rename to app/config/locale/translations/es.php diff --git a/app/config/locales/fi.continents.php b/app/config/locale/translations/fi.continents.php similarity index 100% rename from app/config/locales/fi.continents.php rename to app/config/locale/translations/fi.continents.php diff --git a/app/config/locales/fi.countries.php b/app/config/locale/translations/fi.countries.php similarity index 100% rename from app/config/locales/fi.countries.php rename to app/config/locale/translations/fi.countries.php diff --git a/app/config/locales/fi.php b/app/config/locale/translations/fi.php similarity index 100% rename from app/config/locales/fi.php rename to app/config/locale/translations/fi.php diff --git a/app/config/locales/fo.continents.php b/app/config/locale/translations/fo.continents.php similarity index 100% rename from app/config/locales/fo.continents.php rename to app/config/locale/translations/fo.continents.php diff --git a/app/config/locales/fo.countries.php b/app/config/locale/translations/fo.countries.php similarity index 100% rename from app/config/locales/fo.countries.php rename to app/config/locale/translations/fo.countries.php diff --git a/app/config/locales/fo.php b/app/config/locale/translations/fo.php similarity index 100% rename from app/config/locales/fo.php rename to app/config/locale/translations/fo.php diff --git a/app/config/locales/fr.continents.php b/app/config/locale/translations/fr.continents.php similarity index 100% rename from app/config/locales/fr.continents.php rename to app/config/locale/translations/fr.continents.php diff --git a/app/config/locales/fr.countries.php b/app/config/locale/translations/fr.countries.php similarity index 100% rename from app/config/locales/fr.countries.php rename to app/config/locale/translations/fr.countries.php diff --git a/app/config/locales/fr.php b/app/config/locale/translations/fr.php similarity index 100% rename from app/config/locales/fr.php rename to app/config/locale/translations/fr.php diff --git a/app/config/locales/gr.continents.php b/app/config/locale/translations/gr.continents.php similarity index 100% rename from app/config/locales/gr.continents.php rename to app/config/locale/translations/gr.continents.php diff --git a/app/config/locales/gr.countries.php b/app/config/locale/translations/gr.countries.php similarity index 100% rename from app/config/locales/gr.countries.php rename to app/config/locale/translations/gr.countries.php diff --git a/app/config/locales/gr.php b/app/config/locale/translations/gr.php similarity index 100% rename from app/config/locales/gr.php rename to app/config/locale/translations/gr.php diff --git a/app/config/locales/he.continents.php b/app/config/locale/translations/he.continents.php similarity index 100% rename from app/config/locales/he.continents.php rename to app/config/locale/translations/he.continents.php diff --git a/app/config/locales/he.countries.php b/app/config/locale/translations/he.countries.php similarity index 100% rename from app/config/locales/he.countries.php rename to app/config/locale/translations/he.countries.php diff --git a/app/config/locales/he.php b/app/config/locale/translations/he.php similarity index 100% rename from app/config/locales/he.php rename to app/config/locale/translations/he.php diff --git a/app/config/locales/hi.continents.php b/app/config/locale/translations/hi.continents.php similarity index 100% rename from app/config/locales/hi.continents.php rename to app/config/locale/translations/hi.continents.php diff --git a/app/config/locales/hi.countries.php b/app/config/locale/translations/hi.countries.php similarity index 100% rename from app/config/locales/hi.countries.php rename to app/config/locale/translations/hi.countries.php diff --git a/app/config/locales/hi.php b/app/config/locale/translations/hi.php similarity index 100% rename from app/config/locales/hi.php rename to app/config/locale/translations/hi.php diff --git a/app/config/locales/hu.continents.php b/app/config/locale/translations/hu.continents.php similarity index 100% rename from app/config/locales/hu.continents.php rename to app/config/locale/translations/hu.continents.php diff --git a/app/config/locales/hu.countries.php b/app/config/locale/translations/hu.countries.php similarity index 100% rename from app/config/locales/hu.countries.php rename to app/config/locale/translations/hu.countries.php diff --git a/app/config/locales/hu.php b/app/config/locale/translations/hu.php similarity index 100% rename from app/config/locales/hu.php rename to app/config/locale/translations/hu.php diff --git a/app/config/locales/hy.continents.php b/app/config/locale/translations/hy.continents.php similarity index 100% rename from app/config/locales/hy.continents.php rename to app/config/locale/translations/hy.continents.php diff --git a/app/config/locales/hy.countries.php b/app/config/locale/translations/hy.countries.php similarity index 100% rename from app/config/locales/hy.countries.php rename to app/config/locale/translations/hy.countries.php diff --git a/app/config/locales/hy.php b/app/config/locale/translations/hy.php similarity index 100% rename from app/config/locales/hy.php rename to app/config/locale/translations/hy.php diff --git a/app/config/locales/id.continents.php b/app/config/locale/translations/id.continents.php similarity index 100% rename from app/config/locales/id.continents.php rename to app/config/locale/translations/id.continents.php diff --git a/app/config/locales/id.countries.php b/app/config/locale/translations/id.countries.php similarity index 100% rename from app/config/locales/id.countries.php rename to app/config/locale/translations/id.countries.php diff --git a/app/config/locales/id.php b/app/config/locale/translations/id.php similarity index 100% rename from app/config/locales/id.php rename to app/config/locale/translations/id.php diff --git a/app/config/locales/is.continents.php b/app/config/locale/translations/is.continents.php similarity index 100% rename from app/config/locales/is.continents.php rename to app/config/locale/translations/is.continents.php diff --git a/app/config/locales/is.countries.php b/app/config/locale/translations/is.countries.php similarity index 100% rename from app/config/locales/is.countries.php rename to app/config/locale/translations/is.countries.php diff --git a/app/config/locales/is.php b/app/config/locale/translations/is.php similarity index 100% rename from app/config/locales/is.php rename to app/config/locale/translations/is.php diff --git a/app/config/locales/it.continents.php b/app/config/locale/translations/it.continents.php similarity index 100% rename from app/config/locales/it.continents.php rename to app/config/locale/translations/it.continents.php diff --git a/app/config/locales/it.countries.php b/app/config/locale/translations/it.countries.php similarity index 100% rename from app/config/locales/it.countries.php rename to app/config/locale/translations/it.countries.php diff --git a/app/config/locales/it.php b/app/config/locale/translations/it.php similarity index 100% rename from app/config/locales/it.php rename to app/config/locale/translations/it.php diff --git a/app/config/locales/ja.continents.php b/app/config/locale/translations/ja.continents.php similarity index 100% rename from app/config/locales/ja.continents.php rename to app/config/locale/translations/ja.continents.php diff --git a/app/config/locales/ja.countries.php b/app/config/locale/translations/ja.countries.php similarity index 100% rename from app/config/locales/ja.countries.php rename to app/config/locale/translations/ja.countries.php diff --git a/app/config/locales/ja.php b/app/config/locale/translations/ja.php similarity index 100% rename from app/config/locales/ja.php rename to app/config/locale/translations/ja.php diff --git a/app/config/locales/jv.continents.php b/app/config/locale/translations/jv.continents.php similarity index 100% rename from app/config/locales/jv.continents.php rename to app/config/locale/translations/jv.continents.php diff --git a/app/config/locales/jv.countries.php b/app/config/locale/translations/jv.countries.php similarity index 100% rename from app/config/locales/jv.countries.php rename to app/config/locale/translations/jv.countries.php diff --git a/app/config/locales/jv.php b/app/config/locale/translations/jv.php similarity index 100% rename from app/config/locales/jv.php rename to app/config/locale/translations/jv.php diff --git a/app/config/locales/km.continents.php b/app/config/locale/translations/km.continents.php similarity index 100% rename from app/config/locales/km.continents.php rename to app/config/locale/translations/km.continents.php diff --git a/app/config/locales/km.countries.php b/app/config/locale/translations/km.countries.php similarity index 100% rename from app/config/locales/km.countries.php rename to app/config/locale/translations/km.countries.php diff --git a/app/config/locales/km.php b/app/config/locale/translations/km.php similarity index 100% rename from app/config/locales/km.php rename to app/config/locale/translations/km.php diff --git a/app/config/locales/ko.continents.php b/app/config/locale/translations/ko.continents.php similarity index 100% rename from app/config/locales/ko.continents.php rename to app/config/locale/translations/ko.continents.php diff --git a/app/config/locales/ko.countries.php b/app/config/locale/translations/ko.countries.php similarity index 100% rename from app/config/locales/ko.countries.php rename to app/config/locale/translations/ko.countries.php diff --git a/app/config/locales/ko.php b/app/config/locale/translations/ko.php similarity index 100% rename from app/config/locales/ko.php rename to app/config/locale/translations/ko.php diff --git a/app/config/locales/lt.continents.php b/app/config/locale/translations/lt.continents.php similarity index 100% rename from app/config/locales/lt.continents.php rename to app/config/locale/translations/lt.continents.php diff --git a/app/config/locales/lt.countries.php b/app/config/locale/translations/lt.countries.php similarity index 100% rename from app/config/locales/lt.countries.php rename to app/config/locale/translations/lt.countries.php diff --git a/app/config/locales/lt.php b/app/config/locale/translations/lt.php similarity index 100% rename from app/config/locales/lt.php rename to app/config/locale/translations/lt.php diff --git a/app/config/locales/ml.continents.php b/app/config/locale/translations/ml.continents.php similarity index 100% rename from app/config/locales/ml.continents.php rename to app/config/locale/translations/ml.continents.php diff --git a/app/config/locales/ml.countries.php b/app/config/locale/translations/ml.countries.php similarity index 100% rename from app/config/locales/ml.countries.php rename to app/config/locale/translations/ml.countries.php diff --git a/app/config/locales/ml.php b/app/config/locale/translations/ml.php similarity index 100% rename from app/config/locales/ml.php rename to app/config/locale/translations/ml.php diff --git a/app/config/locales/ms.continents.php b/app/config/locale/translations/ms.continents.php similarity index 100% rename from app/config/locales/ms.continents.php rename to app/config/locale/translations/ms.continents.php diff --git a/app/config/locales/ms.countries.php b/app/config/locale/translations/ms.countries.php similarity index 100% rename from app/config/locales/ms.countries.php rename to app/config/locale/translations/ms.countries.php diff --git a/app/config/locales/ms.php b/app/config/locale/translations/ms.php similarity index 100% rename from app/config/locales/ms.php rename to app/config/locale/translations/ms.php diff --git a/app/config/locales/nl.continents.php b/app/config/locale/translations/nl.continents.php similarity index 100% rename from app/config/locales/nl.continents.php rename to app/config/locale/translations/nl.continents.php diff --git a/app/config/locales/nl.countries.php b/app/config/locale/translations/nl.countries.php similarity index 100% rename from app/config/locales/nl.countries.php rename to app/config/locale/translations/nl.countries.php diff --git a/app/config/locales/nl.php b/app/config/locale/translations/nl.php similarity index 100% rename from app/config/locales/nl.php rename to app/config/locale/translations/nl.php diff --git a/app/config/locales/no.continents.php b/app/config/locale/translations/no.continents.php similarity index 100% rename from app/config/locales/no.continents.php rename to app/config/locale/translations/no.continents.php diff --git a/app/config/locales/no.countries.php b/app/config/locale/translations/no.countries.php similarity index 100% rename from app/config/locales/no.countries.php rename to app/config/locale/translations/no.countries.php diff --git a/app/config/locales/no.php b/app/config/locale/translations/no.php similarity index 100% rename from app/config/locales/no.php rename to app/config/locale/translations/no.php diff --git a/app/config/locales/ph.continents.php b/app/config/locale/translations/ph.continents.php similarity index 100% rename from app/config/locales/ph.continents.php rename to app/config/locale/translations/ph.continents.php diff --git a/app/config/locales/ph.countries.php b/app/config/locale/translations/ph.countries.php similarity index 100% rename from app/config/locales/ph.countries.php rename to app/config/locale/translations/ph.countries.php diff --git a/app/config/locales/ph.php b/app/config/locale/translations/ph.php similarity index 100% rename from app/config/locales/ph.php rename to app/config/locale/translations/ph.php diff --git a/app/config/locales/pl.continents.php b/app/config/locale/translations/pl.continents.php similarity index 100% rename from app/config/locales/pl.continents.php rename to app/config/locale/translations/pl.continents.php diff --git a/app/config/locales/pl.countries.php b/app/config/locale/translations/pl.countries.php similarity index 100% rename from app/config/locales/pl.countries.php rename to app/config/locale/translations/pl.countries.php diff --git a/app/config/locales/pl.php b/app/config/locale/translations/pl.php similarity index 100% rename from app/config/locales/pl.php rename to app/config/locale/translations/pl.php diff --git a/app/config/locales/pt-br.continents.php b/app/config/locale/translations/pt-br.continents.php similarity index 100% rename from app/config/locales/pt-br.continents.php rename to app/config/locale/translations/pt-br.continents.php diff --git a/app/config/locales/pt-br.countries.php b/app/config/locale/translations/pt-br.countries.php similarity index 100% rename from app/config/locales/pt-br.countries.php rename to app/config/locale/translations/pt-br.countries.php diff --git a/app/config/locales/pt-br.php b/app/config/locale/translations/pt-br.php similarity index 100% rename from app/config/locales/pt-br.php rename to app/config/locale/translations/pt-br.php diff --git a/app/config/locales/pt-pt.continents.php b/app/config/locale/translations/pt-pt.continents.php similarity index 100% rename from app/config/locales/pt-pt.continents.php rename to app/config/locale/translations/pt-pt.continents.php diff --git a/app/config/locales/pt-pt.countries.php b/app/config/locale/translations/pt-pt.countries.php similarity index 100% rename from app/config/locales/pt-pt.countries.php rename to app/config/locale/translations/pt-pt.countries.php diff --git a/app/config/locales/pt-pt.php b/app/config/locale/translations/pt-pt.php similarity index 100% rename from app/config/locales/pt-pt.php rename to app/config/locale/translations/pt-pt.php diff --git a/app/config/locales/ro.continents.php b/app/config/locale/translations/ro.continents.php similarity index 100% rename from app/config/locales/ro.continents.php rename to app/config/locale/translations/ro.continents.php diff --git a/app/config/locales/ro.countries.php b/app/config/locale/translations/ro.countries.php similarity index 100% rename from app/config/locales/ro.countries.php rename to app/config/locale/translations/ro.countries.php diff --git a/app/config/locales/ro.php b/app/config/locale/translations/ro.php similarity index 100% rename from app/config/locales/ro.php rename to app/config/locale/translations/ro.php diff --git a/app/config/locales/ru.continents.php b/app/config/locale/translations/ru.continents.php similarity index 100% rename from app/config/locales/ru.continents.php rename to app/config/locale/translations/ru.continents.php diff --git a/app/config/locales/ru.countries.php b/app/config/locale/translations/ru.countries.php similarity index 100% rename from app/config/locales/ru.countries.php rename to app/config/locale/translations/ru.countries.php diff --git a/app/config/locales/ru.php b/app/config/locale/translations/ru.php similarity index 100% rename from app/config/locales/ru.php rename to app/config/locale/translations/ru.php diff --git a/app/config/locales/si.continents.php b/app/config/locale/translations/si.continents.php similarity index 100% rename from app/config/locales/si.continents.php rename to app/config/locale/translations/si.continents.php diff --git a/app/config/locales/si.countries.php b/app/config/locale/translations/si.countries.php similarity index 100% rename from app/config/locales/si.countries.php rename to app/config/locale/translations/si.countries.php diff --git a/app/config/locales/si.php b/app/config/locale/translations/si.php similarity index 100% rename from app/config/locales/si.php rename to app/config/locale/translations/si.php diff --git a/app/config/locales/sl.continents.php b/app/config/locale/translations/sl.continents.php similarity index 100% rename from app/config/locales/sl.continents.php rename to app/config/locale/translations/sl.continents.php diff --git a/app/config/locales/sl.countries.php b/app/config/locale/translations/sl.countries.php similarity index 100% rename from app/config/locales/sl.countries.php rename to app/config/locale/translations/sl.countries.php diff --git a/app/config/locales/sl.php b/app/config/locale/translations/sl.php similarity index 100% rename from app/config/locales/sl.php rename to app/config/locale/translations/sl.php diff --git a/app/config/locales/sq.continents.php b/app/config/locale/translations/sq.continents.php similarity index 100% rename from app/config/locales/sq.continents.php rename to app/config/locale/translations/sq.continents.php diff --git a/app/config/locales/sq.countries.php b/app/config/locale/translations/sq.countries.php similarity index 100% rename from app/config/locales/sq.countries.php rename to app/config/locale/translations/sq.countries.php diff --git a/app/config/locales/sq.php b/app/config/locale/translations/sq.php similarity index 100% rename from app/config/locales/sq.php rename to app/config/locale/translations/sq.php diff --git a/app/config/locales/sv.continents.php b/app/config/locale/translations/sv.continents.php similarity index 100% rename from app/config/locales/sv.continents.php rename to app/config/locale/translations/sv.continents.php diff --git a/app/config/locales/sv.countries.php b/app/config/locale/translations/sv.countries.php similarity index 100% rename from app/config/locales/sv.countries.php rename to app/config/locale/translations/sv.countries.php diff --git a/app/config/locales/sv.php b/app/config/locale/translations/sv.php similarity index 100% rename from app/config/locales/sv.php rename to app/config/locale/translations/sv.php diff --git a/app/config/locales/ta.continents.php b/app/config/locale/translations/ta.continents.php similarity index 100% rename from app/config/locales/ta.continents.php rename to app/config/locale/translations/ta.continents.php diff --git a/app/config/locales/ta.countries.php b/app/config/locale/translations/ta.countries.php similarity index 100% rename from app/config/locales/ta.countries.php rename to app/config/locale/translations/ta.countries.php diff --git a/app/config/locales/ta.php b/app/config/locale/translations/ta.php similarity index 100% rename from app/config/locales/ta.php rename to app/config/locale/translations/ta.php diff --git a/app/config/locales/templates/af.email.auth.confirm.tpl b/app/config/locale/translations/templates/af.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/af.email.auth.confirm.tpl rename to app/config/locale/translations/templates/af.email.auth.confirm.tpl diff --git a/app/config/locales/templates/af.email.auth.invitation.tpl b/app/config/locale/translations/templates/af.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/af.email.auth.invitation.tpl rename to app/config/locale/translations/templates/af.email.auth.invitation.tpl diff --git a/app/config/locales/templates/af.email.auth.recovery.tpl b/app/config/locale/translations/templates/af.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/af.email.auth.recovery.tpl rename to app/config/locale/translations/templates/af.email.auth.recovery.tpl diff --git a/app/config/locales/templates/alb.email.auth.confirm.tpl b/app/config/locale/translations/templates/alb.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/alb.email.auth.confirm.tpl rename to app/config/locale/translations/templates/alb.email.auth.confirm.tpl diff --git a/app/config/locales/templates/alb.email.auth.invitation.tpl b/app/config/locale/translations/templates/alb.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/alb.email.auth.invitation.tpl rename to app/config/locale/translations/templates/alb.email.auth.invitation.tpl diff --git a/app/config/locales/templates/alb.email.auth.recovery.tpl b/app/config/locale/translations/templates/alb.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/alb.email.auth.recovery.tpl rename to app/config/locale/translations/templates/alb.email.auth.recovery.tpl diff --git a/app/config/locales/templates/ar.email.auth.confirm.tpl b/app/config/locale/translations/templates/ar.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/ar.email.auth.confirm.tpl rename to app/config/locale/translations/templates/ar.email.auth.confirm.tpl diff --git a/app/config/locales/templates/ar.email.auth.invitation.tpl b/app/config/locale/translations/templates/ar.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/ar.email.auth.invitation.tpl rename to app/config/locale/translations/templates/ar.email.auth.invitation.tpl diff --git a/app/config/locales/templates/ar.email.auth.recovery.tpl b/app/config/locale/translations/templates/ar.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/ar.email.auth.recovery.tpl rename to app/config/locale/translations/templates/ar.email.auth.recovery.tpl diff --git a/app/config/locales/templates/bn.email.auth.confirm.tpl b/app/config/locale/translations/templates/bn.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/bn.email.auth.confirm.tpl rename to app/config/locale/translations/templates/bn.email.auth.confirm.tpl diff --git a/app/config/locales/templates/bn.email.auth.invitation.tpl b/app/config/locale/translations/templates/bn.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/bn.email.auth.invitation.tpl rename to app/config/locale/translations/templates/bn.email.auth.invitation.tpl diff --git a/app/config/locales/templates/bn.email.auth.recovery.tpl b/app/config/locale/translations/templates/bn.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/bn.email.auth.recovery.tpl rename to app/config/locale/translations/templates/bn.email.auth.recovery.tpl diff --git a/app/config/locales/templates/cat.email.auth.confirm.tpl b/app/config/locale/translations/templates/cat.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/cat.email.auth.confirm.tpl rename to app/config/locale/translations/templates/cat.email.auth.confirm.tpl diff --git a/app/config/locales/templates/cat.email.auth.invitation.tpl b/app/config/locale/translations/templates/cat.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/cat.email.auth.invitation.tpl rename to app/config/locale/translations/templates/cat.email.auth.invitation.tpl diff --git a/app/config/locales/templates/cat.email.auth.recovery.tpl b/app/config/locale/translations/templates/cat.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/cat.email.auth.recovery.tpl rename to app/config/locale/translations/templates/cat.email.auth.recovery.tpl diff --git a/app/config/locales/templates/cz.email.auth.confirm.tpl b/app/config/locale/translations/templates/cz.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/cz.email.auth.confirm.tpl rename to app/config/locale/translations/templates/cz.email.auth.confirm.tpl diff --git a/app/config/locales/templates/cz.email.auth.invitation.tpl b/app/config/locale/translations/templates/cz.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/cz.email.auth.invitation.tpl rename to app/config/locale/translations/templates/cz.email.auth.invitation.tpl diff --git a/app/config/locales/templates/cz.email.auth.recovery.tpl b/app/config/locale/translations/templates/cz.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/cz.email.auth.recovery.tpl rename to app/config/locale/translations/templates/cz.email.auth.recovery.tpl diff --git a/app/config/locales/templates/de.email.auth.confirm.tpl b/app/config/locale/translations/templates/de.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/de.email.auth.confirm.tpl rename to app/config/locale/translations/templates/de.email.auth.confirm.tpl diff --git a/app/config/locales/templates/de.email.auth.invitation.tpl b/app/config/locale/translations/templates/de.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/de.email.auth.invitation.tpl rename to app/config/locale/translations/templates/de.email.auth.invitation.tpl diff --git a/app/config/locales/templates/de.email.auth.recovery.tpl b/app/config/locale/translations/templates/de.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/de.email.auth.recovery.tpl rename to app/config/locale/translations/templates/de.email.auth.recovery.tpl diff --git a/app/config/locales/templates/en.email.auth.confirm.tpl b/app/config/locale/translations/templates/en.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/en.email.auth.confirm.tpl rename to app/config/locale/translations/templates/en.email.auth.confirm.tpl diff --git a/app/config/locales/templates/en.email.auth.invitation.tpl b/app/config/locale/translations/templates/en.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/en.email.auth.invitation.tpl rename to app/config/locale/translations/templates/en.email.auth.invitation.tpl diff --git a/app/config/locales/templates/en.email.auth.recovery.tpl b/app/config/locale/translations/templates/en.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/en.email.auth.recovery.tpl rename to app/config/locale/translations/templates/en.email.auth.recovery.tpl diff --git a/app/config/locales/templates/es.email.auth.confirm.tpl b/app/config/locale/translations/templates/es.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/es.email.auth.confirm.tpl rename to app/config/locale/translations/templates/es.email.auth.confirm.tpl diff --git a/app/config/locales/templates/es.email.auth.invitation.tpl b/app/config/locale/translations/templates/es.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/es.email.auth.invitation.tpl rename to app/config/locale/translations/templates/es.email.auth.invitation.tpl diff --git a/app/config/locales/templates/es.email.auth.recovery.tpl b/app/config/locale/translations/templates/es.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/es.email.auth.recovery.tpl rename to app/config/locale/translations/templates/es.email.auth.recovery.tpl diff --git a/app/config/locales/templates/fi.email.auth.confirm.tpl b/app/config/locale/translations/templates/fi.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/fi.email.auth.confirm.tpl rename to app/config/locale/translations/templates/fi.email.auth.confirm.tpl diff --git a/app/config/locales/templates/fi.email.auth.invitation.tpl b/app/config/locale/translations/templates/fi.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/fi.email.auth.invitation.tpl rename to app/config/locale/translations/templates/fi.email.auth.invitation.tpl diff --git a/app/config/locales/templates/fi.email.auth.recovery.tpl b/app/config/locale/translations/templates/fi.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/fi.email.auth.recovery.tpl rename to app/config/locale/translations/templates/fi.email.auth.recovery.tpl diff --git a/app/config/locales/templates/fo.email.auth.confirm.tpl b/app/config/locale/translations/templates/fo.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/fo.email.auth.confirm.tpl rename to app/config/locale/translations/templates/fo.email.auth.confirm.tpl diff --git a/app/config/locales/templates/fo.email.auth.invitation.tpl b/app/config/locale/translations/templates/fo.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/fo.email.auth.invitation.tpl rename to app/config/locale/translations/templates/fo.email.auth.invitation.tpl diff --git a/app/config/locales/templates/fo.email.auth.recovery.tpl b/app/config/locale/translations/templates/fo.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/fo.email.auth.recovery.tpl rename to app/config/locale/translations/templates/fo.email.auth.recovery.tpl diff --git a/app/config/locales/templates/fr.email.auth.confirm.tpl b/app/config/locale/translations/templates/fr.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/fr.email.auth.confirm.tpl rename to app/config/locale/translations/templates/fr.email.auth.confirm.tpl diff --git a/app/config/locales/templates/fr.email.auth.invitation.tpl b/app/config/locale/translations/templates/fr.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/fr.email.auth.invitation.tpl rename to app/config/locale/translations/templates/fr.email.auth.invitation.tpl diff --git a/app/config/locales/templates/fr.email.auth.recovery.tpl b/app/config/locale/translations/templates/fr.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/fr.email.auth.recovery.tpl rename to app/config/locale/translations/templates/fr.email.auth.recovery.tpl diff --git a/app/config/locales/templates/gr.email.auth.confirm.tpl b/app/config/locale/translations/templates/gr.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/gr.email.auth.confirm.tpl rename to app/config/locale/translations/templates/gr.email.auth.confirm.tpl diff --git a/app/config/locales/templates/gr.email.auth.invitation.tpl b/app/config/locale/translations/templates/gr.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/gr.email.auth.invitation.tpl rename to app/config/locale/translations/templates/gr.email.auth.invitation.tpl diff --git a/app/config/locales/templates/gr.email.auth.recovery.tpl b/app/config/locale/translations/templates/gr.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/gr.email.auth.recovery.tpl rename to app/config/locale/translations/templates/gr.email.auth.recovery.tpl diff --git a/app/config/locales/templates/he.email.auth.confirm.tpl b/app/config/locale/translations/templates/he.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/he.email.auth.confirm.tpl rename to app/config/locale/translations/templates/he.email.auth.confirm.tpl diff --git a/app/config/locales/templates/he.email.auth.invitation.tpl b/app/config/locale/translations/templates/he.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/he.email.auth.invitation.tpl rename to app/config/locale/translations/templates/he.email.auth.invitation.tpl diff --git a/app/config/locales/templates/he.email.auth.recovery.tpl b/app/config/locale/translations/templates/he.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/he.email.auth.recovery.tpl rename to app/config/locale/translations/templates/he.email.auth.recovery.tpl diff --git a/app/config/locales/templates/hi.email.auth.confirm.tpl b/app/config/locale/translations/templates/hi.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/hi.email.auth.confirm.tpl rename to app/config/locale/translations/templates/hi.email.auth.confirm.tpl diff --git a/app/config/locales/templates/hi.email.auth.invitation.tpl b/app/config/locale/translations/templates/hi.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/hi.email.auth.invitation.tpl rename to app/config/locale/translations/templates/hi.email.auth.invitation.tpl diff --git a/app/config/locales/templates/hi.email.auth.recovery.tpl b/app/config/locale/translations/templates/hi.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/hi.email.auth.recovery.tpl rename to app/config/locale/translations/templates/hi.email.auth.recovery.tpl diff --git a/app/config/locales/templates/hu.email.auth.confirm.tpl b/app/config/locale/translations/templates/hu.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/hu.email.auth.confirm.tpl rename to app/config/locale/translations/templates/hu.email.auth.confirm.tpl diff --git a/app/config/locales/templates/hu.email.auth.invitation.tpl b/app/config/locale/translations/templates/hu.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/hu.email.auth.invitation.tpl rename to app/config/locale/translations/templates/hu.email.auth.invitation.tpl diff --git a/app/config/locales/templates/hu.email.auth.recovery.tpl b/app/config/locale/translations/templates/hu.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/hu.email.auth.recovery.tpl rename to app/config/locale/translations/templates/hu.email.auth.recovery.tpl diff --git a/app/config/locales/templates/hy.email.auth.confirm.tpl b/app/config/locale/translations/templates/hy.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/hy.email.auth.confirm.tpl rename to app/config/locale/translations/templates/hy.email.auth.confirm.tpl diff --git a/app/config/locales/templates/hy.email.auth.invitation.tpl b/app/config/locale/translations/templates/hy.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/hy.email.auth.invitation.tpl rename to app/config/locale/translations/templates/hy.email.auth.invitation.tpl diff --git a/app/config/locales/templates/hy.email.auth.recovery.tpl b/app/config/locale/translations/templates/hy.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/hy.email.auth.recovery.tpl rename to app/config/locale/translations/templates/hy.email.auth.recovery.tpl diff --git a/app/config/locales/templates/id.email.auth.confirm.tpl b/app/config/locale/translations/templates/id.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/id.email.auth.confirm.tpl rename to app/config/locale/translations/templates/id.email.auth.confirm.tpl diff --git a/app/config/locales/templates/id.email.auth.invitation.tpl b/app/config/locale/translations/templates/id.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/id.email.auth.invitation.tpl rename to app/config/locale/translations/templates/id.email.auth.invitation.tpl diff --git a/app/config/locales/templates/id.email.auth.recovery.tpl b/app/config/locale/translations/templates/id.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/id.email.auth.recovery.tpl rename to app/config/locale/translations/templates/id.email.auth.recovery.tpl diff --git a/app/config/locales/templates/is.email.auth.confirm.tpl b/app/config/locale/translations/templates/is.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/is.email.auth.confirm.tpl rename to app/config/locale/translations/templates/is.email.auth.confirm.tpl diff --git a/app/config/locales/templates/is.email.auth.invitation.tpl b/app/config/locale/translations/templates/is.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/is.email.auth.invitation.tpl rename to app/config/locale/translations/templates/is.email.auth.invitation.tpl diff --git a/app/config/locales/templates/is.email.auth.recovery.tpl b/app/config/locale/translations/templates/is.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/is.email.auth.recovery.tpl rename to app/config/locale/translations/templates/is.email.auth.recovery.tpl diff --git a/app/config/locales/templates/it.email.auth.confirm.tpl b/app/config/locale/translations/templates/it.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/it.email.auth.confirm.tpl rename to app/config/locale/translations/templates/it.email.auth.confirm.tpl diff --git a/app/config/locales/templates/it.email.auth.invitation.tpl b/app/config/locale/translations/templates/it.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/it.email.auth.invitation.tpl rename to app/config/locale/translations/templates/it.email.auth.invitation.tpl diff --git a/app/config/locales/templates/it.email.auth.recovery.tpl b/app/config/locale/translations/templates/it.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/it.email.auth.recovery.tpl rename to app/config/locale/translations/templates/it.email.auth.recovery.tpl diff --git a/app/config/locales/templates/ja.email.auth.confirm.tpl b/app/config/locale/translations/templates/ja.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/ja.email.auth.confirm.tpl rename to app/config/locale/translations/templates/ja.email.auth.confirm.tpl diff --git a/app/config/locales/templates/ja.email.auth.invitation.tpl b/app/config/locale/translations/templates/ja.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/ja.email.auth.invitation.tpl rename to app/config/locale/translations/templates/ja.email.auth.invitation.tpl diff --git a/app/config/locales/templates/ja.email.auth.recovery.tpl b/app/config/locale/translations/templates/ja.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/ja.email.auth.recovery.tpl rename to app/config/locale/translations/templates/ja.email.auth.recovery.tpl diff --git a/app/config/locales/templates/jv.email.auth.confirm.tpl b/app/config/locale/translations/templates/jv.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/jv.email.auth.confirm.tpl rename to app/config/locale/translations/templates/jv.email.auth.confirm.tpl diff --git a/app/config/locales/templates/jv.email.auth.invitation.tpl b/app/config/locale/translations/templates/jv.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/jv.email.auth.invitation.tpl rename to app/config/locale/translations/templates/jv.email.auth.invitation.tpl diff --git a/app/config/locales/templates/jv.email.auth.recovery.tpl b/app/config/locale/translations/templates/jv.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/jv.email.auth.recovery.tpl rename to app/config/locale/translations/templates/jv.email.auth.recovery.tpl diff --git a/app/config/locales/templates/km.email.auth.confirm.tpl b/app/config/locale/translations/templates/km.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/km.email.auth.confirm.tpl rename to app/config/locale/translations/templates/km.email.auth.confirm.tpl diff --git a/app/config/locales/templates/km.email.auth.invitation.tpl b/app/config/locale/translations/templates/km.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/km.email.auth.invitation.tpl rename to app/config/locale/translations/templates/km.email.auth.invitation.tpl diff --git a/app/config/locales/templates/km.email.auth.recovery.tpl b/app/config/locale/translations/templates/km.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/km.email.auth.recovery.tpl rename to app/config/locale/translations/templates/km.email.auth.recovery.tpl diff --git a/app/config/locales/templates/ko.email.auth.confirm.tpl b/app/config/locale/translations/templates/ko.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/ko.email.auth.confirm.tpl rename to app/config/locale/translations/templates/ko.email.auth.confirm.tpl diff --git a/app/config/locales/templates/ko.email.auth.invitation.tpl b/app/config/locale/translations/templates/ko.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/ko.email.auth.invitation.tpl rename to app/config/locale/translations/templates/ko.email.auth.invitation.tpl diff --git a/app/config/locales/templates/ko.email.auth.recovery.tpl b/app/config/locale/translations/templates/ko.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/ko.email.auth.recovery.tpl rename to app/config/locale/translations/templates/ko.email.auth.recovery.tpl diff --git a/app/config/locales/templates/lt.email.auth.confirm.tpl b/app/config/locale/translations/templates/lt.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/lt.email.auth.confirm.tpl rename to app/config/locale/translations/templates/lt.email.auth.confirm.tpl diff --git a/app/config/locales/templates/lt.email.auth.invitation.tpl b/app/config/locale/translations/templates/lt.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/lt.email.auth.invitation.tpl rename to app/config/locale/translations/templates/lt.email.auth.invitation.tpl diff --git a/app/config/locales/templates/lt.email.auth.recovery.tpl b/app/config/locale/translations/templates/lt.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/lt.email.auth.recovery.tpl rename to app/config/locale/translations/templates/lt.email.auth.recovery.tpl diff --git a/app/config/locales/templates/ml.email.auth.confirm.tpl b/app/config/locale/translations/templates/ml.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/ml.email.auth.confirm.tpl rename to app/config/locale/translations/templates/ml.email.auth.confirm.tpl diff --git a/app/config/locales/templates/ml.email.auth.invitation.tpl b/app/config/locale/translations/templates/ml.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/ml.email.auth.invitation.tpl rename to app/config/locale/translations/templates/ml.email.auth.invitation.tpl diff --git a/app/config/locales/templates/ml.email.auth.recovery.tpl b/app/config/locale/translations/templates/ml.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/ml.email.auth.recovery.tpl rename to app/config/locale/translations/templates/ml.email.auth.recovery.tpl diff --git a/app/config/locales/templates/my.email.auth.confirm.tpl b/app/config/locale/translations/templates/my.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/my.email.auth.confirm.tpl rename to app/config/locale/translations/templates/my.email.auth.confirm.tpl diff --git a/app/config/locales/templates/my.email.auth.invitation.tpl b/app/config/locale/translations/templates/my.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/my.email.auth.invitation.tpl rename to app/config/locale/translations/templates/my.email.auth.invitation.tpl diff --git a/app/config/locales/templates/my.email.auth.recovery.tpl b/app/config/locale/translations/templates/my.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/my.email.auth.recovery.tpl rename to app/config/locale/translations/templates/my.email.auth.recovery.tpl diff --git a/app/config/locales/templates/nl.email.auth.confirm.tpl b/app/config/locale/translations/templates/nl.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/nl.email.auth.confirm.tpl rename to app/config/locale/translations/templates/nl.email.auth.confirm.tpl diff --git a/app/config/locales/templates/nl.email.auth.invitation.tpl b/app/config/locale/translations/templates/nl.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/nl.email.auth.invitation.tpl rename to app/config/locale/translations/templates/nl.email.auth.invitation.tpl diff --git a/app/config/locales/templates/nl.email.auth.recovery.tpl b/app/config/locale/translations/templates/nl.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/nl.email.auth.recovery.tpl rename to app/config/locale/translations/templates/nl.email.auth.recovery.tpl diff --git a/app/config/locales/templates/no.email.auth.confirm.tpl b/app/config/locale/translations/templates/no.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/no.email.auth.confirm.tpl rename to app/config/locale/translations/templates/no.email.auth.confirm.tpl diff --git a/app/config/locales/templates/no.email.auth.invitation.tpl b/app/config/locale/translations/templates/no.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/no.email.auth.invitation.tpl rename to app/config/locale/translations/templates/no.email.auth.invitation.tpl diff --git a/app/config/locales/templates/no.email.auth.recovery.tpl b/app/config/locale/translations/templates/no.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/no.email.auth.recovery.tpl rename to app/config/locale/translations/templates/no.email.auth.recovery.tpl diff --git a/app/config/locales/templates/ph.email.auth.confirm.tpl b/app/config/locale/translations/templates/ph.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/ph.email.auth.confirm.tpl rename to app/config/locale/translations/templates/ph.email.auth.confirm.tpl diff --git a/app/config/locales/templates/ph.email.auth.invitation.tpl b/app/config/locale/translations/templates/ph.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/ph.email.auth.invitation.tpl rename to app/config/locale/translations/templates/ph.email.auth.invitation.tpl diff --git a/app/config/locales/templates/ph.email.auth.recovery.tpl b/app/config/locale/translations/templates/ph.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/ph.email.auth.recovery.tpl rename to app/config/locale/translations/templates/ph.email.auth.recovery.tpl diff --git a/app/config/locales/templates/pl.email.auth.confirm.tpl b/app/config/locale/translations/templates/pl.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/pl.email.auth.confirm.tpl rename to app/config/locale/translations/templates/pl.email.auth.confirm.tpl diff --git a/app/config/locales/templates/pl.email.auth.invitation.tpl b/app/config/locale/translations/templates/pl.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/pl.email.auth.invitation.tpl rename to app/config/locale/translations/templates/pl.email.auth.invitation.tpl diff --git a/app/config/locales/templates/pl.email.auth.recovery.tpl b/app/config/locale/translations/templates/pl.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/pl.email.auth.recovery.tpl rename to app/config/locale/translations/templates/pl.email.auth.recovery.tpl diff --git a/app/config/locales/templates/pt-br.email.auth.confirm.tpl b/app/config/locale/translations/templates/pt-br.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/pt-br.email.auth.confirm.tpl rename to app/config/locale/translations/templates/pt-br.email.auth.confirm.tpl diff --git a/app/config/locales/templates/pt-br.email.auth.invitation.tpl b/app/config/locale/translations/templates/pt-br.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/pt-br.email.auth.invitation.tpl rename to app/config/locale/translations/templates/pt-br.email.auth.invitation.tpl diff --git a/app/config/locales/templates/pt-br.email.auth.recovery.tpl b/app/config/locale/translations/templates/pt-br.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/pt-br.email.auth.recovery.tpl rename to app/config/locale/translations/templates/pt-br.email.auth.recovery.tpl diff --git a/app/config/locales/templates/pt-pt.email.auth.confirm.tpl b/app/config/locale/translations/templates/pt-pt.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/pt-pt.email.auth.confirm.tpl rename to app/config/locale/translations/templates/pt-pt.email.auth.confirm.tpl diff --git a/app/config/locales/templates/pt-pt.email.auth.invitation.tpl b/app/config/locale/translations/templates/pt-pt.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/pt-pt.email.auth.invitation.tpl rename to app/config/locale/translations/templates/pt-pt.email.auth.invitation.tpl diff --git a/app/config/locales/templates/pt-pt.email.auth.recovery.tpl b/app/config/locale/translations/templates/pt-pt.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/pt-pt.email.auth.recovery.tpl rename to app/config/locale/translations/templates/pt-pt.email.auth.recovery.tpl diff --git a/app/config/locales/templates/ro.email.auth.confirm.tpl b/app/config/locale/translations/templates/ro.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/ro.email.auth.confirm.tpl rename to app/config/locale/translations/templates/ro.email.auth.confirm.tpl diff --git a/app/config/locales/templates/ro.email.auth.invitation.tpl b/app/config/locale/translations/templates/ro.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/ro.email.auth.invitation.tpl rename to app/config/locale/translations/templates/ro.email.auth.invitation.tpl diff --git a/app/config/locales/templates/ro.email.auth.recovery.tpl b/app/config/locale/translations/templates/ro.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/ro.email.auth.recovery.tpl rename to app/config/locale/translations/templates/ro.email.auth.recovery.tpl diff --git a/app/config/locales/templates/ru.email.auth.confirm.tpl b/app/config/locale/translations/templates/ru.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/ru.email.auth.confirm.tpl rename to app/config/locale/translations/templates/ru.email.auth.confirm.tpl diff --git a/app/config/locales/templates/ru.email.auth.invitation.tpl b/app/config/locale/translations/templates/ru.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/ru.email.auth.invitation.tpl rename to app/config/locale/translations/templates/ru.email.auth.invitation.tpl diff --git a/app/config/locales/templates/ru.email.auth.recovery.tpl b/app/config/locale/translations/templates/ru.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/ru.email.auth.recovery.tpl rename to app/config/locale/translations/templates/ru.email.auth.recovery.tpl diff --git a/app/config/locales/templates/si.email.auth.confirm.tpl b/app/config/locale/translations/templates/si.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/si.email.auth.confirm.tpl rename to app/config/locale/translations/templates/si.email.auth.confirm.tpl diff --git a/app/config/locales/templates/si.email.auth.invitation.tpl b/app/config/locale/translations/templates/si.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/si.email.auth.invitation.tpl rename to app/config/locale/translations/templates/si.email.auth.invitation.tpl diff --git a/app/config/locales/templates/si.email.auth.recovery.tpl b/app/config/locale/translations/templates/si.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/si.email.auth.recovery.tpl rename to app/config/locale/translations/templates/si.email.auth.recovery.tpl diff --git a/app/config/locales/templates/sl.email.auth.confirm.tpl b/app/config/locale/translations/templates/sl.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/sl.email.auth.confirm.tpl rename to app/config/locale/translations/templates/sl.email.auth.confirm.tpl diff --git a/app/config/locales/templates/sl.email.auth.invitation.tpl b/app/config/locale/translations/templates/sl.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/sl.email.auth.invitation.tpl rename to app/config/locale/translations/templates/sl.email.auth.invitation.tpl diff --git a/app/config/locales/templates/sl.email.auth.recovery.tpl b/app/config/locale/translations/templates/sl.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/sl.email.auth.recovery.tpl rename to app/config/locale/translations/templates/sl.email.auth.recovery.tpl diff --git a/app/config/locales/templates/sv.email.auth.confirm.tpl b/app/config/locale/translations/templates/sv.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/sv.email.auth.confirm.tpl rename to app/config/locale/translations/templates/sv.email.auth.confirm.tpl diff --git a/app/config/locales/templates/sv.email.auth.invitation.tpl b/app/config/locale/translations/templates/sv.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/sv.email.auth.invitation.tpl rename to app/config/locale/translations/templates/sv.email.auth.invitation.tpl diff --git a/app/config/locales/templates/sv.email.auth.recovery.tpl b/app/config/locale/translations/templates/sv.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/sv.email.auth.recovery.tpl rename to app/config/locale/translations/templates/sv.email.auth.recovery.tpl diff --git a/app/config/locales/templates/ta.email.auth.confirm.tpl b/app/config/locale/translations/templates/ta.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/ta.email.auth.confirm.tpl rename to app/config/locale/translations/templates/ta.email.auth.confirm.tpl diff --git a/app/config/locales/templates/ta.email.auth.invitation.tpl b/app/config/locale/translations/templates/ta.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/ta.email.auth.invitation.tpl rename to app/config/locale/translations/templates/ta.email.auth.invitation.tpl diff --git a/app/config/locales/templates/ta.email.auth.recovery.tpl b/app/config/locale/translations/templates/ta.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/ta.email.auth.recovery.tpl rename to app/config/locale/translations/templates/ta.email.auth.recovery.tpl diff --git a/app/config/locales/templates/th.email.auth.confirm.tpl b/app/config/locale/translations/templates/th.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/th.email.auth.confirm.tpl rename to app/config/locale/translations/templates/th.email.auth.confirm.tpl diff --git a/app/config/locales/templates/th.email.auth.invitation.tpl b/app/config/locale/translations/templates/th.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/th.email.auth.invitation.tpl rename to app/config/locale/translations/templates/th.email.auth.invitation.tpl diff --git a/app/config/locales/templates/th.email.auth.recovery.tpl b/app/config/locale/translations/templates/th.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/th.email.auth.recovery.tpl rename to app/config/locale/translations/templates/th.email.auth.recovery.tpl diff --git a/app/config/locales/templates/tr.email.auth.confirm.tpl b/app/config/locale/translations/templates/tr.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/tr.email.auth.confirm.tpl rename to app/config/locale/translations/templates/tr.email.auth.confirm.tpl diff --git a/app/config/locales/templates/tr.email.auth.invitation.tpl b/app/config/locale/translations/templates/tr.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/tr.email.auth.invitation.tpl rename to app/config/locale/translations/templates/tr.email.auth.invitation.tpl diff --git a/app/config/locales/templates/tr.email.auth.recovery.tpl b/app/config/locale/translations/templates/tr.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/tr.email.auth.recovery.tpl rename to app/config/locale/translations/templates/tr.email.auth.recovery.tpl diff --git a/app/config/locales/templates/ua.email.auth.confirm.tpl b/app/config/locale/translations/templates/ua.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/ua.email.auth.confirm.tpl rename to app/config/locale/translations/templates/ua.email.auth.confirm.tpl diff --git a/app/config/locales/templates/ua.email.auth.invitation.tpl b/app/config/locale/translations/templates/ua.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/ua.email.auth.invitation.tpl rename to app/config/locale/translations/templates/ua.email.auth.invitation.tpl diff --git a/app/config/locales/templates/ua.email.auth.recovery.tpl b/app/config/locale/translations/templates/ua.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/ua.email.auth.recovery.tpl rename to app/config/locale/translations/templates/ua.email.auth.recovery.tpl diff --git a/app/config/locales/templates/vi.email.auth.confirm.tpl b/app/config/locale/translations/templates/vi.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/vi.email.auth.confirm.tpl rename to app/config/locale/translations/templates/vi.email.auth.confirm.tpl diff --git a/app/config/locales/templates/vi.email.auth.invitation.tpl b/app/config/locale/translations/templates/vi.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/vi.email.auth.invitation.tpl rename to app/config/locale/translations/templates/vi.email.auth.invitation.tpl diff --git a/app/config/locales/templates/vi.email.auth.recovery.tpl b/app/config/locale/translations/templates/vi.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/vi.email.auth.recovery.tpl rename to app/config/locale/translations/templates/vi.email.auth.recovery.tpl diff --git a/app/config/locales/templates/zh-cn.email.auth.confirm.tpl b/app/config/locale/translations/templates/zh-cn.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/zh-cn.email.auth.confirm.tpl rename to app/config/locale/translations/templates/zh-cn.email.auth.confirm.tpl diff --git a/app/config/locales/templates/zh-cn.email.auth.invitation.tpl b/app/config/locale/translations/templates/zh-cn.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/zh-cn.email.auth.invitation.tpl rename to app/config/locale/translations/templates/zh-cn.email.auth.invitation.tpl diff --git a/app/config/locales/templates/zh-cn.email.auth.recovery.tpl b/app/config/locale/translations/templates/zh-cn.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/zh-cn.email.auth.recovery.tpl rename to app/config/locale/translations/templates/zh-cn.email.auth.recovery.tpl diff --git a/app/config/locales/templates/zh-tw.email.auth.confirm.tpl b/app/config/locale/translations/templates/zh-tw.email.auth.confirm.tpl similarity index 100% rename from app/config/locales/templates/zh-tw.email.auth.confirm.tpl rename to app/config/locale/translations/templates/zh-tw.email.auth.confirm.tpl diff --git a/app/config/locales/templates/zh-tw.email.auth.invitation.tpl b/app/config/locale/translations/templates/zh-tw.email.auth.invitation.tpl similarity index 100% rename from app/config/locales/templates/zh-tw.email.auth.invitation.tpl rename to app/config/locale/translations/templates/zh-tw.email.auth.invitation.tpl diff --git a/app/config/locales/templates/zh-tw.email.auth.recovery.tpl b/app/config/locale/translations/templates/zh-tw.email.auth.recovery.tpl similarity index 100% rename from app/config/locales/templates/zh-tw.email.auth.recovery.tpl rename to app/config/locale/translations/templates/zh-tw.email.auth.recovery.tpl diff --git a/app/config/locales/th.continents.php b/app/config/locale/translations/th.continents.php similarity index 100% rename from app/config/locales/th.continents.php rename to app/config/locale/translations/th.continents.php diff --git a/app/config/locales/th.countries.php b/app/config/locale/translations/th.countries.php similarity index 100% rename from app/config/locales/th.countries.php rename to app/config/locale/translations/th.countries.php diff --git a/app/config/locales/th.php b/app/config/locale/translations/th.php similarity index 100% rename from app/config/locales/th.php rename to app/config/locale/translations/th.php diff --git a/app/config/locales/tr.continents.php b/app/config/locale/translations/tr.continents.php similarity index 100% rename from app/config/locales/tr.continents.php rename to app/config/locale/translations/tr.continents.php diff --git a/app/config/locales/tr.countries.php b/app/config/locale/translations/tr.countries.php similarity index 100% rename from app/config/locales/tr.countries.php rename to app/config/locale/translations/tr.countries.php diff --git a/app/config/locales/tr.php b/app/config/locale/translations/tr.php similarity index 100% rename from app/config/locales/tr.php rename to app/config/locale/translations/tr.php diff --git a/app/config/locales/ua.continents.php b/app/config/locale/translations/ua.continents.php similarity index 100% rename from app/config/locales/ua.continents.php rename to app/config/locale/translations/ua.continents.php diff --git a/app/config/locales/ua.countries.php b/app/config/locale/translations/ua.countries.php similarity index 100% rename from app/config/locales/ua.countries.php rename to app/config/locale/translations/ua.countries.php diff --git a/app/config/locales/ua.php b/app/config/locale/translations/ua.php similarity index 100% rename from app/config/locales/ua.php rename to app/config/locale/translations/ua.php diff --git a/app/config/locales/vi.continents.php b/app/config/locale/translations/vi.continents.php similarity index 100% rename from app/config/locales/vi.continents.php rename to app/config/locale/translations/vi.continents.php diff --git a/app/config/locales/vi.countries.php b/app/config/locale/translations/vi.countries.php similarity index 100% rename from app/config/locales/vi.countries.php rename to app/config/locale/translations/vi.countries.php diff --git a/app/config/locales/vi.php b/app/config/locale/translations/vi.php similarity index 100% rename from app/config/locales/vi.php rename to app/config/locale/translations/vi.php diff --git a/app/config/locales/zh-cn.continents.php b/app/config/locale/translations/zh-cn.continents.php similarity index 100% rename from app/config/locales/zh-cn.continents.php rename to app/config/locale/translations/zh-cn.continents.php diff --git a/app/config/locales/zh-cn.countries.php b/app/config/locale/translations/zh-cn.countries.php similarity index 100% rename from app/config/locales/zh-cn.countries.php rename to app/config/locale/translations/zh-cn.countries.php diff --git a/app/config/locales/zh-cn.php b/app/config/locale/translations/zh-cn.php similarity index 100% rename from app/config/locales/zh-cn.php rename to app/config/locale/translations/zh-cn.php diff --git a/app/config/locales/zh-tw.continents.php b/app/config/locale/translations/zh-tw.continents.php similarity index 100% rename from app/config/locales/zh-tw.continents.php rename to app/config/locale/translations/zh-tw.continents.php diff --git a/app/config/locales/zh-tw.countries.php b/app/config/locale/translations/zh-tw.countries.php similarity index 100% rename from app/config/locales/zh-tw.countries.php rename to app/config/locale/translations/zh-tw.countries.php diff --git a/app/config/locales/zh-tw.php b/app/config/locale/translations/zh-tw.php similarity index 100% rename from app/config/locales/zh-tw.php rename to app/config/locale/translations/zh-tw.php diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 3931599f95..fac11c5d8f 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1157,9 +1157,9 @@ App::post('/v1/account/recovery') $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret]); $url = Template::unParseURL($url); - $body = new Template(__DIR__.'/../../config/locales/templates/_base.tpl'); - $content = new Template(__DIR__.'/../../config/locales/templates/'.$locale->getText('account.emails.recovery.body')); - $cta = new Template(__DIR__.'/../../config/locales/templates/_cta.tpl'); + $body = new Template(__DIR__.'/../../config/locale/templates/email-base.tpl'); + $content = new Template(__DIR__.'/../../config/locale/translations/templates/'.$locale->getText('account.emails.recovery.body')); + $cta = new Template(__DIR__.'/../../config/locale/templates/email-cta.tpl'); $body ->setParam('{{content}}', $content->render()) @@ -1323,9 +1323,9 @@ App::post('/v1/account/verification') $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $verificationSecret]); $url = Template::unParseURL($url); - $body = new Template(__DIR__.'/../../config/locales/templates/_base.tpl'); - $content = new Template(__DIR__.'/../../config/locales/templates/'.$locale->getText('account.emails.verification.body')); - $cta = new Template(__DIR__.'/../../config/locales/templates/_cta.tpl'); + $body = new Template(__DIR__.'/../../config/locale/templates/email-base.tpl'); + $content = new Template(__DIR__.'/../../config/locale/translations/templates/'.$locale->getText('account.emails.verification.body')); + $cta = new Template(__DIR__.'/../../config/locale/templates/email-cta.tpl'); $body ->setParam('{{content}}', $content->render()) diff --git a/app/controllers/api/locale.php b/app/controllers/api/locale.php index 2ae098abb0..bf60ebac6f 100644 --- a/app/controllers/api/locale.php +++ b/app/controllers/api/locale.php @@ -2,6 +2,7 @@ use Utopia\App; use GeoIp2\Database\Reader; +use Utopia\Config\Config; App::get('/v1/locale') ->desc('Get User Locale') @@ -16,8 +17,8 @@ App::get('/v1/locale') /** @var Utopia\Response $response */ /** @var Utopia\Locale\Locale $locale */ - $eu = include __DIR__.'/../../config/eu.php'; - $currencies = include __DIR__.'/../../config/currencies.php'; + $eu = Config::getParam('locale-eu'); + $currencies = Config::getParam('locale-currencies'); $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb'); $output = []; $ip = $request->getIP(); @@ -96,7 +97,7 @@ App::get('/v1/locale/countries/eu') /** @var Utopia\Locale\Locale $locale */ $countries = $locale->getText('countries'); /* @var $countries array */ - $eu = include __DIR__.'/../../config/eu.php'; + $eu = Config::getParam('locale-eu'); $list = []; foreach ($eu as $code) { @@ -122,7 +123,7 @@ App::get('/v1/locale/countries/phones') /** @var Utopia\Response $response */ /** @var Utopia\Locale\Locale $locale */ - $list = include __DIR__.'/../../config/phones.php'; /* @var $list array */ + $list = Config::getParam('locale-phones'); /* @var $list array */ $countries = $locale->getText('countries'); /* @var $countries array */ @@ -168,7 +169,7 @@ App::get('/v1/locale/currencies') ->action(function ($response) { /** @var Utopia\Response $response */ - $currencies = include __DIR__.'/../../config/currencies.php'; + $currencies = Config::getParam('locale-currencies'); $response->json($currencies); }, ['response']); @@ -185,7 +186,7 @@ App::get('/v1/locale/languages') ->action(function ($response) { /** @var Utopia\Response $response */ - $languages = include __DIR__.'/../../config/languages.php'; + $languages = Config::getParam('locale-languages'); $response->json($languages); }, ['response']); \ No newline at end of file diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index ab3dd77624..6068984ea4 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -20,8 +20,6 @@ use Appwrite\OpenSSL\OpenSSL; use Appwrite\Network\Validator\CNAME; use Cron\CronExpression; -$scopes = include __DIR__.'/../../../app/config/scopes.php'; - App::post('/v1/projects') ->desc('Create Project') ->groups(['api', 'projects']) @@ -672,7 +670,7 @@ App::post('/v1/projects/:projectId/keys') ->label('sdk.method', 'createKey') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('name', null, function () { return new Text(256); }, 'Key name.') - ->param('scopes', null, function () use ($scopes) { return new ArrayList(new WhiteList($scopes)); }, 'Key scopes list.') + ->param('scopes', null, function () { return new ArrayList(new WhiteList(Config::getParam('scopes'))); }, 'Key scopes list.') ->action( function ($projectId, $name, $scopes) use ($response, $consoleDB) { $project = $consoleDB->getDocument($projectId); @@ -765,7 +763,7 @@ App::put('/v1/projects/:projectId/keys/:keyId') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('keyId', null, function () { return new UID(); }, 'Key unique ID.') ->param('name', null, function () { return new Text(256); }, 'Key name.') - ->param('scopes', null, function () use ($scopes) { return new ArrayList(new WhiteList($scopes)); }, 'Key scopes list') + ->param('scopes', null, function () { return new ArrayList(new WhiteList(Config::getParam('scopes'))); }, 'Key scopes list') ->action( function ($projectId, $keyId, $name, $scopes) use ($response, $consoleDB) { $project = $consoleDB->getDocument($projectId); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index c63e52550f..250dfd0baa 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -324,9 +324,9 @@ App::post('/v1/teams/:teamId/memberships') $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['inviteId' => $membership->getId(), 'teamId' => $team->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId]); $url = Template::unParseURL($url); - $body = new Template(__DIR__.'/../../config/locales/templates/_base.tpl'); - $content = new Template(__DIR__.'/../../config/locales/templates/'.Locale::getText('account.emails.invitation.body')); - $cta = new Template(__DIR__.'/../../config/locales/templates/_cta.tpl'); + $body = new Template(__DIR__.'/../../config/locale/templates/email-base.tpl'); + $content = new Template(__DIR__.'/../../config/locale/translations/templates/'.Locale::getText('account.emails.invitation.body')); + $cta = new Template(__DIR__.'/../../config/locale/templates/email-cta.tpl'); $body ->setParam('{{content}}', $content->render()) diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php index 3369d7011b..1c6db92975 100644 --- a/app/controllers/web/console.php +++ b/app/controllers/web/console.php @@ -171,7 +171,7 @@ App::get('/console/keys') ->action(function ($layout) { /** @var Utopia\View $layout */ - $scopes = include __DIR__.'/../../../app/config/scopes.php'; + $scopes = Config::getParam('scopes'); $page = new View(__DIR__.'/../../views/console/keys/index.phtml'); $page->setParam('scopes', $scopes); diff --git a/app/init.php b/app/init.php index 55e7db5467..5683a5d127 100644 --- a/app/init.php +++ b/app/init.php @@ -52,20 +52,25 @@ App::setMode(App::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION)); /* * ENV vars */ -Config::load('events', __DIR__.'/../app/config/events.php'); -Config::load('providers', __DIR__.'/../app/config/providers.php'); -Config::load('platforms', __DIR__.'/../app/config/platforms.php'); -Config::load('locales', __DIR__.'/../app/config/locales.php'); -Config::load('collections', __DIR__.'/../app/config/collections.php'); -Config::load('roles', __DIR__.'/../app/config/roles.php'); // User roles and scopes -Config::load('services', __DIR__.'/../app/config/services.php'); // List of services -Config::load('avatar-browsers', __DIR__.'/../app/config/avatars/browsers.php'); -Config::load('avatar-credit-cards', __DIR__.'/../app/config/avatars/credit-cards.php'); -Config::load('avatar-flags', __DIR__.'/../app/config/avatars/flags.php'); -Config::load('storage-logos', __DIR__.'/../app/config/storage/logos.php'); -Config::load('storage-mimes', __DIR__.'/../app/config/storage/mimes.php'); -Config::load('storage-inputs', __DIR__.'/../app/config/storage/inputs.php'); -Config::load('storage-outputs', __DIR__.'/../app/config/storage/outputs.php'); +Config::load('events', __DIR__.'/config/events.php'); +Config::load('providers', __DIR__.'/config/providers.php'); +Config::load('platforms', __DIR__.'/config/platforms.php'); +Config::load('collections', __DIR__.'/config/collections.php'); +Config::load('roles', __DIR__.'/config/roles.php'); // User roles and scopes +Config::load('scopes', __DIR__.'/config/scopes.php'); // User roles and scopes +Config::load('services', __DIR__.'/config/services.php'); // List of services +Config::load('avatar-browsers', __DIR__.'/config/avatars/browsers.php'); +Config::load('avatar-credit-cards', __DIR__.'/config/avatars/credit-cards.php'); +Config::load('avatar-flags', __DIR__.'/config/avatars/flags.php'); +Config::load('locale-codes', __DIR__.'/config/locale/codes.php'); +Config::load('locale-currencies', __DIR__.'/config/locale/currencies.php'); +Config::load('locale-eu', __DIR__.'/config/locale/eu.php'); +Config::load('locale-languages', __DIR__.'/config/locale/languages.php'); +Config::load('locale-phones', __DIR__.'/config/locale/phones.php'); +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'); Resque::setBackend(App::getEnv('_APP_REDIS_HOST', '') .':'.App::getEnv('_APP_REDIS_PORT', '')); @@ -167,51 +172,51 @@ $register->set('queue-deletes', function () { * Localization */ Locale::$exceptions = false; -Locale::setLanguage('af', include __DIR__.'/config/locales/af.php'); -Locale::setLanguage('ar', include __DIR__.'/config/locales/ar.php'); -Locale::setLanguage('bn', include __DIR__.'/config/locales/bn.php'); -Locale::setLanguage('cat', include __DIR__.'/config/locales/cat.php'); -Locale::setLanguage('cz', include __DIR__.'/config/locales/cz.php'); -Locale::setLanguage('de', include __DIR__.'/config/locales/de.php'); -Locale::setLanguage('en', include __DIR__.'/config/locales/en.php'); -Locale::setLanguage('es', include __DIR__.'/config/locales/es.php'); -Locale::setLanguage('fi', include __DIR__.'/config/locales/fi.php'); -Locale::setLanguage('fo', include __DIR__.'/config/locales/fo.php'); -Locale::setLanguage('fr', include __DIR__.'/config/locales/fr.php'); -Locale::setLanguage('gr', include __DIR__.'/config/locales/gr.php'); -Locale::setLanguage('he', include __DIR__.'/config/locales/he.php'); -Locale::setLanguage('hi', include __DIR__.'/config/locales/hi.php'); -Locale::setLanguage('hu', include __DIR__.'/config/locales/hu.php'); -Locale::setLanguage('hy', include __DIR__.'/config/locales/hy.php'); -Locale::setLanguage('id', include __DIR__.'/config/locales/id.php'); -Locale::setLanguage('is', include __DIR__.'/config/locales/is.php'); -Locale::setLanguage('it', include __DIR__.'/config/locales/it.php'); -Locale::setLanguage('ja', include __DIR__.'/config/locales/ja.php'); -Locale::setLanguage('jv', include __DIR__.'/config/locales/jv.php'); -Locale::setLanguage('km', include __DIR__.'/config/locales/km.php'); -Locale::setLanguage('ko', include __DIR__.'/config/locales/ko.php'); -Locale::setLanguage('lt', include __DIR__.'/config/locales/lt.php'); -Locale::setLanguage('ml', include __DIR__.'/config/locales/ml.php'); -Locale::setLanguage('ms', include __DIR__.'/config/locales/ms.php'); -Locale::setLanguage('nl', include __DIR__.'/config/locales/nl.php'); -Locale::setLanguage('no', include __DIR__.'/config/locales/no.php'); -Locale::setLanguage('ph', include __DIR__.'/config/locales/ph.php'); -Locale::setLanguage('pl', include __DIR__.'/config/locales/pl.php'); -Locale::setLanguage('pt-br', include __DIR__.'/config/locales/pt-br.php'); -Locale::setLanguage('pt-pt', include __DIR__.'/config/locales/pt-pt.php'); -Locale::setLanguage('ro', include __DIR__.'/config/locales/ro.php'); -Locale::setLanguage('ru', include __DIR__ . '/config/locales/ru.php'); -Locale::setLanguage('si', include __DIR__ . '/config/locales/si.php'); -Locale::setLanguage('sl', include __DIR__ . '/config/locales/sl.php'); -Locale::setLanguage('sq', include __DIR__ . '/config/locales/sq.php'); -Locale::setLanguage('sv', include __DIR__ . '/config/locales/sv.php'); -Locale::setLanguage('ta', include __DIR__ . '/config/locales/ta.php'); -Locale::setLanguage('th', include __DIR__.'/config/locales/th.php'); -Locale::setLanguage('tr', include __DIR__.'/config/locales/tr.php'); -Locale::setLanguage('ua', include __DIR__.'/config/locales/ua.php'); -Locale::setLanguage('vi', include __DIR__.'/config/locales/vi.php'); -Locale::setLanguage('zh-cn', include __DIR__.'/config/locales/zh-cn.php'); -Locale::setLanguage('zh-tw', include __DIR__.'/config/locales/zh-tw.php'); +Locale::setLanguage('af', include __DIR__.'/config/locale/translations/af.php'); +Locale::setLanguage('ar', include __DIR__.'/config/locale/translations/ar.php'); +Locale::setLanguage('bn', include __DIR__.'/config/locale/translations/bn.php'); +Locale::setLanguage('cat', include __DIR__.'/config/locale/translations/cat.php'); +Locale::setLanguage('cz', include __DIR__.'/config/locale/translations/cz.php'); +Locale::setLanguage('de', include __DIR__.'/config/locale/translations/de.php'); +Locale::setLanguage('en', include __DIR__.'/config/locale/translations/en.php'); +Locale::setLanguage('es', include __DIR__.'/config/locale/translations/es.php'); +Locale::setLanguage('fi', include __DIR__.'/config/locale/translations/fi.php'); +Locale::setLanguage('fo', include __DIR__.'/config/locale/translations/fo.php'); +Locale::setLanguage('fr', include __DIR__.'/config/locale/translations/fr.php'); +Locale::setLanguage('gr', include __DIR__.'/config/locale/translations/gr.php'); +Locale::setLanguage('he', include __DIR__.'/config/locale/translations/he.php'); +Locale::setLanguage('hi', include __DIR__.'/config/locale/translations/hi.php'); +Locale::setLanguage('hu', include __DIR__.'/config/locale/translations/hu.php'); +Locale::setLanguage('hy', include __DIR__.'/config/locale/translations/hy.php'); +Locale::setLanguage('id', include __DIR__.'/config/locale/translations/id.php'); +Locale::setLanguage('is', include __DIR__.'/config/locale/translations/is.php'); +Locale::setLanguage('it', include __DIR__.'/config/locale/translations/it.php'); +Locale::setLanguage('ja', include __DIR__.'/config/locale/translations/ja.php'); +Locale::setLanguage('jv', include __DIR__.'/config/locale/translations/jv.php'); +Locale::setLanguage('km', include __DIR__.'/config/locale/translations/km.php'); +Locale::setLanguage('ko', include __DIR__.'/config/locale/translations/ko.php'); +Locale::setLanguage('lt', include __DIR__.'/config/locale/translations/lt.php'); +Locale::setLanguage('ml', include __DIR__.'/config/locale/translations/ml.php'); +Locale::setLanguage('ms', include __DIR__.'/config/locale/translations/ms.php'); +Locale::setLanguage('nl', include __DIR__.'/config/locale/translations/nl.php'); +Locale::setLanguage('no', include __DIR__.'/config/locale/translations/no.php'); +Locale::setLanguage('ph', include __DIR__.'/config/locale/translations/ph.php'); +Locale::setLanguage('pl', include __DIR__.'/config/locale/translations/pl.php'); +Locale::setLanguage('pt-br', include __DIR__.'/config/locale/translations/pt-br.php'); +Locale::setLanguage('pt-pt', include __DIR__.'/config/locale/translations/pt-pt.php'); +Locale::setLanguage('ro', include __DIR__.'/config/locale/translations/ro.php'); +Locale::setLanguage('ru', include __DIR__ . '/config/locale/translations/ru.php'); +Locale::setLanguage('si', include __DIR__ . '/config/locale/translations/si.php'); +Locale::setLanguage('sl', include __DIR__ . '/config/locale/translations/sl.php'); +Locale::setLanguage('sq', include __DIR__ . '/config/locale/translations/sq.php'); +Locale::setLanguage('sv', include __DIR__ . '/config/locale/translations/sv.php'); +Locale::setLanguage('ta', include __DIR__ . '/config/locale/translations/ta.php'); +Locale::setLanguage('th', include __DIR__.'/config/locale/translations/th.php'); +Locale::setLanguage('tr', include __DIR__.'/config/locale/translations/tr.php'); +Locale::setLanguage('ua', include __DIR__.'/config/locale/translations/ua.php'); +Locale::setLanguage('vi', include __DIR__.'/config/locale/translations/vi.php'); +Locale::setLanguage('zh-cn', include __DIR__.'/config/locale/translations/zh-cn.php'); +Locale::setLanguage('zh-tw', include __DIR__.'/config/locale/translations/zh-tw.php'); \stream_context_set_default([ // Set global user agent and http settings 'http' => [ diff --git a/tests/e2e/Services/Locale/LocaleBase.php b/tests/e2e/Services/Locale/LocaleBase.php index 20fb91b25b..b8b1866285 100644 --- a/tests/e2e/Services/Locale/LocaleBase.php +++ b/tests/e2e/Services/Locale/LocaleBase.php @@ -217,9 +217,9 @@ trait LocaleBase /** * Test for SUCCESS */ - $languages = require('app/config/locales.php'); - $defaultCountries = require('app/config/locales/en.countries.php'); - $defaultContinents = require('app/config/locales/en.continents.php'); + $languages = require('app/config/locale/codes.php'); + $defaultCountries = require('app/config/locale/translations/en.countries.php'); + $defaultContinents = require('app/config/locale/translations/en.continents.php'); foreach ($languages as $lang) { $response = $this->client->call(Client::METHOD_GET, '/locale/countries', [ From 6824cf560fc59640718c1de09e876c91014c5be3 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 30 Jun 2020 14:09:28 +0300 Subject: [PATCH 10/17] Added env vars and new request methods --- app/app.php | 27 +- app/controllers/api/account.php | 45 +- app/controllers/api/avatars.php | 2 +- app/controllers/api/storage.php | 501 +++++++++--------- app/controllers/api/teams.php | 893 ++++++++++++++++---------------- app/controllers/api/users.php | 717 ++++++++++++------------- app/controllers/shared/web.php | 12 +- app/controllers/web/console.php | 2 +- app/controllers/web/home.php | 800 ++++++++++++++-------------- app/init.php | 2 +- app/workers/tasks.php | 2 +- app/workers/usage.php | 4 +- app/workers/webhooks.php | 3 +- composer.json | 2 +- composer.lock | 22 +- 15 files changed, 1561 insertions(+), 1473 deletions(-) diff --git a/app/app.php b/app/app.php index f1d29c60f3..fd9188ac8e 100644 --- a/app/app.php +++ b/app/app.php @@ -17,7 +17,6 @@ use Appwrite\Network\Validator\Origin; // Config::setParam('domain', $request->getServer('HTTP_HOST', '')); // Config::setParam('domainVerification', false); -// Config::setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN')); // Config::setParam('protocol', $request->getServer('HTTP_X_FORWARDED_PROTO', $request->getServer('REQUEST_SCHEME', 'https'))); // Config::setParam('port', (string) \parse_url(Config::getParam('protocol').'://'.$request->getServer('HTTP_HOST', ''), PHP_URL_PORT)); // Config::setParam('hostname', \parse_url(Config::getParam('protocol').'://'.$request->getServer('HTTP_HOST', null), PHP_URL_HOST)); @@ -131,10 +130,22 @@ use Appwrite\Network\Validator\Origin; // return false; // })))); -App::init(function ($utopia, $request, $response, $user, $project, $console, $webhooks, $audits, $usage, $clients, $locale) { - - /** @var $locale Utopia\Locale\Locale */ - $localeParam = $request->getParam('locale', $request->getHeader('X-Appwrite-Locale', '')); +App::init(function ($utopia, $request, $response, $console, $project, $user, $locale, $webhooks, $audits, $usage, $clients) { + /** @var Utopia\Request $request */ + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $console */ + /** @var Appwrite\Database\Document $project */ + /** @var Appwrite\Database\Document $user */ + /** @var Utopia\Locale\Locale $locale */ + /** @var Appwrite\Event\Event $webhook */ + /** @var Appwrite\Event\Event $audit */ + /** @var Appwrite\Event\Event $usage */ + /** @var Appwrite\Event\Event $mail */ + /** @var Appwrite\Event\Event $deletes */ + /** @var bool $mode */ + /** @var array $clients */ + + $localeParam = (string)$request->getParam('locale', $request->getHeader('X-Appwrite-Locale', '')); if (\in_array($localeParam, Config::getParam('locale-codes'))) { $locale->setDefault($localeParam); @@ -175,7 +186,7 @@ App::init(function ($utopia, $request, $response, $user, $project, $console, $we * @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers */ if (App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS - if(Config::getParam('protocol') !== 'https') { + if($request->getProtocol() !== 'https') { return $response->redirect('https://' . Config::getParam('domain').$request->getServer('REQUEST_URI')); } @@ -317,7 +328,7 @@ App::init(function ($utopia, $request, $response, $user, $project, $console, $we ->setParam('response', 0) ->setParam('storage', 0) ; -}, ['utopia', 'request', 'response', 'user', 'project', 'console', 'webhook', 'audit', 'usage', 'clients', 'locale']); +}, ['utopia', 'request', 'response', 'console', 'project', 'user', 'locale', 'webhook', 'audit', 'usage', 'clients']); App::shutdown(function ($utopia, $response, $request, $webhook, $audit, $usage, $deletes, $mode, $project) { /* @@ -363,7 +374,7 @@ App::options(function ($request, $response) { App::error(function ($error, $utopia, $request, $response, $project) { /** @var Exception $error */ - $version = Config::getParam('version'); + $version = App::getEnv('_APP_VERSION', 'UNKNOWN'); switch ($error->getCode()) { case 400: // Error allowed publicly diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index fac11c5d8f..6596bc7537 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -167,7 +167,7 @@ App::post('/v1/account/sessions') /** @var Appwrite\Event\Event $webhook */ /** @var Appwrite\Event\Event $audit */ - $protocol = Config::getParam('protocol'); + $protocol = $request->getProtocol(); $profile = $projectDB->getCollectionFirst([ // Get user by email address 'limit' => 1, 'filters' => [ @@ -264,7 +264,7 @@ App::get('/v1/account/sessions/oauth2/:provider') /** @var Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ - $protocol = Config::getParam('protocol'); + $protocol = $request->getProtocol(); $callback = $protocol.'://'.$request->getServer('HTTP_HOST').'/v1/account/sessions/oauth2/callback/'.$provider.'/'.$project->getId(); $appId = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Appid', ''); $appSecret = $project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}'); @@ -304,16 +304,19 @@ App::get('/v1/account/sessions/oauth2/callback/:provider/:projectId') ->param('provider', '', function () { return new WhiteList(\array_keys(Config::getParam('providers'))); }, 'OAuth2 provider.') ->param('code', '', function () { return new Text(1024); }, 'OAuth2 code.') ->param('state', '', function () { return new Text(2048); }, 'Login state params.', true) - ->action(function ($projectId, $provider, $code, $state, $response) { + ->action(function ($projectId, $provider, $code, $state, $request, $response) { + /** @var Utopia\Request $request */ + /** @var Utopia\Response $response */ + $domain = Config::getParam('domain'); - $protocol = Config::getParam('protocol'); + $protocol = $request->getProtocol(); $response ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') ->addHeader('Pragma', 'no-cache') ->redirect($protocol.'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?' .\http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state])); - }, ['response']); + }, ['request', 'response']); App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId') ->desc('OAuth2 Callback') @@ -326,16 +329,19 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId') ->param('provider', '', function () { return new WhiteList(\array_keys(Config::getParam('providers'))); }, 'OAuth2 provider.') ->param('code', '', function () { return new Text(1024); }, 'OAuth2 code.') ->param('state', '', function () { return new Text(2048); }, 'Login state params.', true) - ->action(function ($projectId, $provider, $code, $state, $response) { + ->action(function ($projectId, $provider, $code, $state, $request, $response) { + /** @var Utopia\Request $request */ + /** @var Utopia\Response $response */ + $domain = Config::getParam('domain'); - $protocol = Config::getParam('protocol'); + $protocol = $request->getProtocol(); $response ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') ->addHeader('Pragma', 'no-cache') ->redirect($protocol.'://'.$domain.'/v1/account/sessions/oauth2/'.$provider.'/redirect?' .\http_build_query(['project' => $projectId, 'code' => $code, 'state' => $state])); - }, ['response']); + }, ['request', 'response']); App::get('/v1/account/sessions/oauth2/:provider/redirect') ->desc('OAuth2 Redirect') @@ -357,7 +363,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $audit */ - $protocol = Config::getParam('protocol'); + $protocol = $request->getProtocol(); $callback = $protocol.'://'.$request->getServer('HTTP_HOST').'/v1/account/sessions/oauth2/callback/'.$provider.'/'.$project->getId(); $defaultState = ['success' => $project->getAttribute('url', ''), 'failure' => '']; $validateURL = new URL(); @@ -923,14 +929,15 @@ App::delete('/v1/account') ->label('sdk.namespace', 'account') ->label('sdk.method', 'delete') ->label('sdk.description', '/docs/references/account/delete.md') - ->action(function ($response, $user, $projectDB, $audit, $webhook) { + ->action(function ($request, $response, $user, $projectDB, $audit, $webhook) { + /** @var Utopia\Request $request */ /** @var Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $audit */ /** @var Appwrite\Event\Event $webhook */ - $protocol = Config::getParam('protocol'); + $protocol = $request->getProtocol(); $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ 'status' => Auth::USER_STATUS_BLOCKED, ])); @@ -972,7 +979,7 @@ App::delete('/v1/account') ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) ->noContent() ; - }, ['response', 'user', 'projectDB', 'audit', 'webhook']); + }, ['request', 'response', 'user', 'projectDB', 'audit', 'webhook']); App::delete('/v1/account/sessions/:sessionId') ->desc('Delete Account Session') @@ -985,14 +992,15 @@ App::delete('/v1/account/sessions/:sessionId') ->label('sdk.description', '/docs/references/account/delete-session.md') ->label('abuse-limit', 100) ->param('sessionId', null, function () { return new UID(); }, 'Session unique ID. Use the string \'current\' to delete the current device session.') - ->action(function ($sessionId, $response, $user, $projectDB, $audit, $webhook) { + ->action(function ($sessionId, $request, $response, $user, $projectDB, $audit, $webhook) { + /** @var Utopia\Request $request */ /** @var Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $audit */ /** @var Appwrite\Event\Event $webhook */ - $protocol = Config::getParam('protocol'); + $protocol = $request->getProtocol(); $sessionId = ($sessionId === 'current') ? Auth::tokenVerify($user->getAttribute('tokens'), Auth::TOKEN_TYPE_LOGIN, Auth::$secret) : $sessionId; @@ -1036,7 +1044,7 @@ App::delete('/v1/account/sessions/:sessionId') } throw new Exception('Session not found', 404); - }, ['response', 'user', 'projectDB', 'audit', 'webhook']); + }, ['request', 'response', 'user', 'projectDB', 'audit', 'webhook']); App::delete('/v1/account/sessions') ->desc('Delete All Account Sessions') @@ -1048,14 +1056,15 @@ App::delete('/v1/account/sessions') ->label('sdk.method', 'deleteSessions') ->label('sdk.description', '/docs/references/account/delete-sessions.md') ->label('abuse-limit', 100) - ->action(function ($response, $user, $projectDB, $audit, $webhook) { + ->action(function ($request, $response, $user, $projectDB, $audit, $webhook) { + /** @var Utopia\Request $request */ /** @var Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $audit */ /** @var Appwrite\Event\Event $webhook */ - $protocol = Config::getParam('protocol'); + $protocol = $request->getProtocol(); $tokens = $user->getAttribute('tokens', []); foreach ($tokens as $token) { /* @var $token Document */ @@ -1091,7 +1100,7 @@ App::delete('/v1/account/sessions') } $response->noContent(); - }, ['response', 'user', 'projectDB', 'audit', 'webhook']); + }, ['request', 'response', 'user', 'projectDB', 'audit', 'webhook']); App::post('/v1/account/recovery') ->desc('Create Password Recovery') diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index 850a3a3ed2..9972cfa169 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -246,7 +246,7 @@ App::get('/v1/avatars/favicon') CURLOPT_MAXREDIRS => 3, CURLOPT_URL => $url, CURLOPT_USERAGENT => \sprintf(APP_USERAGENT, - Config::getParam('version'), + App::getEnv('_APP_VERSION', 'UNKNOWN'), App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY) ), ]); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index aba5663fa6..656bed297b 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -242,116 +242,119 @@ App::get('/v1/storage/files/:fileId/preview') ->param('quality', 100, function () { return new Range(0, 100); }, 'Preview image quality. Pass an integer between 0 to 100. Defaults to 100.', true) ->param('background', '', function () { return new HexColor(); }, 'Preview image background color. Only works with transparent images (png). Use a valid HEX color, no # is needed for prefix.', true) ->param('output', null, function () { return new WhiteList(\array_merge(\array_keys(Config::getParam('storage-outputs')), [null])); }, 'Output format type (jpeg, jpg, png, gif and webp).', true) - ->action( - function ($fileId, $width, $height, $quality, $background, $output) use ($request, $response, $projectDB, $project) { - $storage = 'local'; + ->action(function ($fileId, $width, $height, $quality, $background, $output, $request, $response, $project, $projectDB) { + /** @var Utopia\Request $request */ + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $project */ + /** @var Appwrite\Database\Database $projectDB */ - if (!\extension_loaded('imagick')) { - throw new Exception('Imagick extension is missing', 500); - } + $storage = 'local'; - if (!Storage::exists($storage)) { - throw new Exception('No such storage device', 400); - } + if (!\extension_loaded('imagick')) { + throw new Exception('Imagick extension is missing', 500); + } - if ((\strpos($request->getServer('HTTP_ACCEPT'), 'image/webp') === false) && ('webp' == $output)) { // Fallback webp to jpeg when no browser support - $output = 'jpg'; - } + if (!Storage::exists($storage)) { + throw new Exception('No such storage device', 400); + } - $inputs = Config::getParam('storage-inputs'); - $outputs = Config::getParam('storage-outputs'); - $fileLogos = Config::getParam('storage-logos'); + if ((\strpos($request->getServer('HTTP_ACCEPT'), 'image/webp') === false) && ('webp' == $output)) { // Fallback webp to jpeg when no browser support + $output = 'jpg'; + } - $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache - $key = \md5($fileId.$width.$height.$quality.$background.$storage.$output); + $inputs = Config::getParam('storage-inputs'); + $outputs = Config::getParam('storage-outputs'); + $fileLogos = Config::getParam('storage-logos'); - $file = $projectDB->getDocument($fileId); + $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache + $key = \md5($fileId.$width.$height.$quality.$background.$storage.$output); - if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { - throw new Exception('File not found', 404); - } + $file = $projectDB->getDocument($fileId); - $path = $file->getAttribute('path'); + if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { + throw new Exception('File not found', 404); + } + + $path = $file->getAttribute('path'); + $type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION)); + $algorithm = $file->getAttribute('algorithm'); + $cipher = $file->getAttribute('fileOpenSSLCipher'); + $mime = $file->getAttribute('mimeType'); + + if (!\in_array($mime, $inputs)) { + $path = (\array_key_exists($mime, $fileLogos)) ? $fileLogos[$mime] : $fileLogos['default']; + $algorithm = null; + $cipher = null; + $background = (empty($background)) ? 'eceff1' : $background; $type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION)); - $algorithm = $file->getAttribute('algorithm'); - $cipher = $file->getAttribute('fileOpenSSLCipher'); - $mime = $file->getAttribute('mimeType'); + $key = \md5($path.$width.$height.$quality.$background.$storage.$output); + } - if (!\in_array($mime, $inputs)) { - $path = (\array_key_exists($mime, $fileLogos)) ? $fileLogos[$mime] : $fileLogos['default']; - $algorithm = null; - $cipher = null; - $background = (empty($background)) ? 'eceff1' : $background; - $type = \strtolower(\pathinfo($path, PATHINFO_EXTENSION)); - $key = \md5($path.$width.$height.$quality.$background.$storage.$output); - } + $compressor = new GZIP(); + $device = Storage::getDevice('local'); - $compressor = new GZIP(); - $device = Storage::getDevice('local'); + if (!\file_exists($path)) { + throw new Exception('File not found', 404); + } - if (!\file_exists($path)) { - throw new Exception('File not found', 404); - } - - $cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-'.$project->getId())); // Limit file number or size - $data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */); - - if ($data) { - $output = (empty($output)) ? $type : $output; - - $response - ->setContentType((\in_array($output, $outputs)) ? $outputs[$output] : $outputs['jpg']) - ->addHeader('Expires', $date) - ->addHeader('X-Appwrite-Cache', 'hit') - ->send($data) - ; - - return; - } - - $source = $device->read($path); - - if (!empty($cipher)) { // Decrypt - $source = OpenSSL::decrypt( - $source, - $file->getAttribute('fileOpenSSLCipher'), - App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')), - 0, - \hex2bin($file->getAttribute('fileOpenSSLIV')), - \hex2bin($file->getAttribute('fileOpenSSLTag')) - ); - } - - if (!empty($algorithm)) { - $source = $compressor->decompress($source); - } - - $resize = new Resize($source); - - $resize->crop((int) $width, (int) $height); - - if (!empty($background)) { - $resize->setBackground('#'.$background); - } + $cache = new Cache(new Filesystem(APP_STORAGE_CACHE.'/app-'.$project->getId())); // Limit file number or size + $data = $cache->load($key, 60 * 60 * 24 * 30 * 3 /* 3 months */); + if ($data) { $output = (empty($output)) ? $type : $output; $response - ->setContentType($outputs[$output]) + ->setContentType((\in_array($output, $outputs)) ? $outputs[$output] : $outputs['jpg']) ->addHeader('Expires', $date) - ->addHeader('X-Appwrite-Cache', 'miss') - ->send('') + ->addHeader('X-Appwrite-Cache', 'hit') + ->send($data) ; - $data = $resize->output($output, $quality); - - $cache->save($key, $data); - - echo $data; - - unset($resize); + return; } - ); + + $source = $device->read($path); + + if (!empty($cipher)) { // Decrypt + $source = OpenSSL::decrypt( + $source, + $file->getAttribute('fileOpenSSLCipher'), + App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')), + 0, + \hex2bin($file->getAttribute('fileOpenSSLIV')), + \hex2bin($file->getAttribute('fileOpenSSLTag')) + ); + } + + if (!empty($algorithm)) { + $source = $compressor->decompress($source); + } + + $resize = new Resize($source); + + $resize->crop((int) $width, (int) $height); + + if (!empty($background)) { + $resize->setBackground('#'.$background); + } + + $output = (empty($output)) ? $type : $output; + + $response + ->setContentType($outputs[$output]) + ->addHeader('Expires', $date) + ->addHeader('X-Appwrite-Cache', 'miss') + ->send('') + ; + + $data = $resize->output($output, $quality); + + $cache->save($key, $data); + + echo $data; + + unset($resize); + }, ['request', 'response', 'project', 'projectDB']); App::get('/v1/storage/files/:fileId/download') ->desc('Get File for Download') @@ -364,48 +367,49 @@ App::get('/v1/storage/files/:fileId/download') ->label('sdk.response.type', '*') ->label('sdk.methodType', 'location') ->param('fileId', '', function () { return new UID(); }, 'File unique ID.') - ->action( - function ($fileId) use ($response, $projectDB) { - $file = $projectDB->getDocument($fileId); + ->action(function ($fileId, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { - throw new Exception('File not found', 404); - } + $file = $projectDB->getDocument($fileId); - $path = $file->getAttribute('path', ''); - - if (!\file_exists($path)) { - throw new Exception('File not found in '.$path, 404); - } - - $compressor = new GZIP(); - $device = Storage::getDevice('local'); - - $source = $device->read($path); - - if (!empty($file->getAttribute('fileOpenSSLCipher'))) { // Decrypt - $source = OpenSSL::decrypt( - $source, - $file->getAttribute('fileOpenSSLCipher'), - App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')), - 0, - \hex2bin($file->getAttribute('fileOpenSSLIV')), - \hex2bin($file->getAttribute('fileOpenSSLTag')) - ); - } - - $source = $compressor->decompress($source); - - // Response - $response - ->setContentType($file->getAttribute('mimeType')) - ->addHeader('Content-Disposition', 'attachment; filename="'.$file->getAttribute('name', '').'"') - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache - ->addHeader('X-Peak', \memory_get_peak_usage()) - ->send($source) - ; + if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { + throw new Exception('File not found', 404); } - ); + + $path = $file->getAttribute('path', ''); + + if (!\file_exists($path)) { + throw new Exception('File not found in '.$path, 404); + } + + $compressor = new GZIP(); + $device = Storage::getDevice('local'); + + $source = $device->read($path); + + if (!empty($file->getAttribute('fileOpenSSLCipher'))) { // Decrypt + $source = OpenSSL::decrypt( + $source, + $file->getAttribute('fileOpenSSLCipher'), + App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')), + 0, + \hex2bin($file->getAttribute('fileOpenSSLIV')), + \hex2bin($file->getAttribute('fileOpenSSLTag')) + ); + } + + $source = $compressor->decompress($source); + + // Response + $response + ->setContentType($file->getAttribute('mimeType')) + ->addHeader('Content-Disposition', 'attachment; filename="'.$file->getAttribute('name', '').'"') + ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache + ->addHeader('X-Peak', \memory_get_peak_usage()) + ->send($source) + ; + }, ['response', 'projectDB']); App::get('/v1/storage/files/:fileId/view') ->desc('Get File for View') @@ -419,65 +423,66 @@ App::get('/v1/storage/files/:fileId/view') ->label('sdk.methodType', 'location') ->param('fileId', '', function () { return new UID(); }, 'File unique ID.') ->param('as', '', function () { return new WhiteList(['pdf', /*'html',*/ 'text']); }, 'Choose a file format to convert your file to. Currently you can only convert word and pdf files to pdf or txt. This option is currently experimental only, use at your own risk.', true) - ->action( - function ($fileId, $as) use ($response, $projectDB) { - $file = $projectDB->getDocument($fileId); - $mimes = Config::getParam('storage-mimes'); + ->action(function ($fileId, $as, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { - throw new Exception('File not found', 404); - } + $file = $projectDB->getDocument($fileId); + $mimes = Config::getParam('storage-mimes'); - $path = $file->getAttribute('path', ''); - - if (!\file_exists($path)) { - throw new Exception('File not found in '.$path, 404); - } - - $compressor = new GZIP(); - $device = Storage::getDevice('local'); - - $contentType = 'text/plain'; - - if (\in_array($file->getAttribute('mimeType'), $mimes)) { - $contentType = $file->getAttribute('mimeType'); - } - - $source = $device->read($path); - - if (!empty($file->getAttribute('fileOpenSSLCipher'))) { // Decrypt - $source = OpenSSL::decrypt( - $source, - $file->getAttribute('fileOpenSSLCipher'), - App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')), - 0, - \hex2bin($file->getAttribute('fileOpenSSLIV')), - \hex2bin($file->getAttribute('fileOpenSSLTag')) - ); - } - - $output = $compressor->decompress($source); - $fileName = $file->getAttribute('name', ''); - - $contentTypes = [ - 'pdf' => 'application/pdf', - 'text' => 'text/plain', - ]; - - $contentType = (\array_key_exists($as, $contentTypes)) ? $contentTypes[$as] : $contentType; - - // Response - $response - ->setContentType($contentType) - ->addHeader('Content-Security-Policy', 'script-src none;') - ->addHeader('X-Content-Type-Options', 'nosniff') - ->addHeader('Content-Disposition', 'inline; filename="'.$fileName.'"') - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache - ->addHeader('X-Peak', \memory_get_peak_usage()) - ->send($output) - ; + if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { + throw new Exception('File not found', 404); } - ); + + $path = $file->getAttribute('path', ''); + + if (!\file_exists($path)) { + throw new Exception('File not found in '.$path, 404); + } + + $compressor = new GZIP(); + $device = Storage::getDevice('local'); + + $contentType = 'text/plain'; + + if (\in_array($file->getAttribute('mimeType'), $mimes)) { + $contentType = $file->getAttribute('mimeType'); + } + + $source = $device->read($path); + + if (!empty($file->getAttribute('fileOpenSSLCipher'))) { // Decrypt + $source = OpenSSL::decrypt( + $source, + $file->getAttribute('fileOpenSSLCipher'), + App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')), + 0, + \hex2bin($file->getAttribute('fileOpenSSLIV')), + \hex2bin($file->getAttribute('fileOpenSSLTag')) + ); + } + + $output = $compressor->decompress($source); + $fileName = $file->getAttribute('name', ''); + + $contentTypes = [ + 'pdf' => 'application/pdf', + 'text' => 'text/plain', + ]; + + $contentType = (\array_key_exists($as, $contentTypes)) ? $contentTypes[$as] : $contentType; + + // Response + $response + ->setContentType($contentType) + ->addHeader('Content-Security-Policy', 'script-src none;') + ->addHeader('X-Content-Type-Options', 'nosniff') + ->addHeader('Content-Disposition', 'inline; filename="'.$fileName.'"') + ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT') // 45 days cache + ->addHeader('X-Peak', \memory_get_peak_usage()) + ->send($output) + ; + }, ['response', 'projectDB']); App::put('/v1/storage/files/:fileId') ->desc('Update File') @@ -491,39 +496,41 @@ App::put('/v1/storage/files/:fileId') ->param('fileId', '', function () { return new UID(); }, 'File unique ID.') ->param('read', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') ->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') - //->param('folderId', '', function () { return new UID(); }, 'Folder to associate files with.', true) - ->action( - function ($fileId, $read, $write, $folderId = '') use ($response, $projectDB, $audit, $webhook) { - $file = $projectDB->getDocument($fileId); + ->action(function ($fileId, $read, $write, $response, $projectDB, $webhook, $audit) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $webhook */ + /** @var Appwrite\Event\Event $audit */ - if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { - throw new Exception('File not found', 404); - } + $file = $projectDB->getDocument($fileId); - $file = $projectDB->updateDocument(\array_merge($file->getArrayCopy(), [ - '$permissions' => [ - 'read' => $read, - 'write' => $write, - ], - 'folderId' => $folderId, - ])); - - if (false === $file) { - throw new Exception('Failed saving file to DB', 500); - } - - $webhook - ->setParam('payload', $file->getArrayCopy()) - ; - - $audit - ->setParam('event', 'storage.files.update') - ->setParam('resource', 'storage/files/'.$file->getId()) - ; - - $response->json($file->getArrayCopy()); + if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { + throw new Exception('File not found', 404); } - ); + + $file = $projectDB->updateDocument(\array_merge($file->getArrayCopy(), [ + '$permissions' => [ + 'read' => $read, + 'write' => $write, + ], + 'folderId' => '', + ])); + + if (false === $file) { + throw new Exception('Failed saving file to DB', 500); + } + + $webhook + ->setParam('payload', $file->getArrayCopy()) + ; + + $audit + ->setParam('event', 'storage.files.update') + ->setParam('resource', 'storage/files/'.$file->getId()) + ; + + $response->json($file->getArrayCopy()); + }, ['response', 'projectDB', 'webhook', 'audit']); App::delete('/v1/storage/files/:fileId') ->desc('Delete File') @@ -535,38 +542,42 @@ App::delete('/v1/storage/files/:fileId') ->label('sdk.method', 'deleteFile') ->label('sdk.description', '/docs/references/storage/delete-file.md') ->param('fileId', '', function () { return new UID(); }, 'File unique ID.') - ->action( - function ($fileId) use ($response, $projectDB, $webhook, $audit, $usage) { - $file = $projectDB->getDocument($fileId); + ->action(function ($fileId, $response, $projectDB, $webhook, $audit, $usage) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $webhook */ + /** @var Appwrite\Event\Event $audit */ + /** @var Appwrite\Event\Event $usage */ + + $file = $projectDB->getDocument($fileId); - if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { - throw new Exception('File not found', 404); - } - - $device = Storage::getDevice('local'); - - if ($device->delete($file->getAttribute('path', ''))) { - if (!$projectDB->deleteDocument($fileId)) { - throw new Exception('Failed to remove file from DB', 500); - } - } - - $webhook - ->setParam('payload', $file->getArrayCopy()) - ; - - $audit - ->setParam('event', 'storage.files.delete') - ->setParam('resource', 'storage/files/'.$file->getId()) - ; - - $usage - ->setParam('storage', $file->getAttribute('size', 0) * -1) - ; - - $response->noContent(); + if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { + throw new Exception('File not found', 404); } - ); + + $device = Storage::getDevice('local'); + + if ($device->delete($file->getAttribute('path', ''))) { + if (!$projectDB->deleteDocument($fileId)) { + throw new Exception('Failed to remove file from DB', 500); + } + } + + $webhook + ->setParam('payload', $file->getArrayCopy()) + ; + + $audit + ->setParam('event', 'storage.files.delete') + ->setParam('resource', 'storage/files/'.$file->getId()) + ; + + $usage + ->setParam('storage', $file->getAttribute('size', 0) * -1) + ; + + $response->noContent(); + }, ['fileId', 'response', 'projectDB', 'webhook', 'audit', 'usage']); // App::get('/v1/storage/files/:fileId/scan') // ->desc('Scan Storage') diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 250dfd0baa..4af413f62e 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -10,7 +10,6 @@ use Utopia\Validator\Host; use Utopia\Validator\Range; use Utopia\Validator\ArrayList; use Utopia\Validator\WhiteList; -use Utopia\Locale\Locale; use Appwrite\Auth\Auth; use Appwrite\Database\Database; use Appwrite\Database\Document; @@ -29,59 +28,62 @@ App::post('/v1/teams') ->label('sdk.description', '/docs/references/teams/create-team.md') ->param('name', null, function () { return new Text(100); }, 'Team name.') ->param('roles', ['owner'], function () { return new ArrayList(new Text(128)); }, 'Array of strings. Use this param to set the roles in the team for the user who created it. The default role is **owner**. A role can be any string. Learn more about [roles and permissions](/docs/permissions).', true) - ->action( - function ($name, $roles) use ($response, $projectDB, $user, $mode) { - Authorization::disable(); + ->action(function ($name, $roles, $response, $user, $projectDB, $mode) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $user */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var bool $mode */ - $team = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_TEAMS, + Authorization::disable(); + + $team = $projectDB->createDocument([ + '$collection' => Database::SYSTEM_COLLECTION_TEAMS, + '$permissions' => [ + 'read' => ['team:{self}'], + 'write' => ['team:{self}/owner'], + ], + 'name' => $name, + 'sum' => ($mode !== APP_MODE_ADMIN && $user->getId()) ? 1 : 0, + 'dateCreated' => \time(), + ]); + + Authorization::reset(); + + if (false === $team) { + throw new Exception('Failed saving team to DB', 500); + } + + if ($mode !== APP_MODE_ADMIN && $user->getId()) { // Don't add user on server mode + $membership = new Document([ + '$collection' => Database::SYSTEM_COLLECTION_MEMBERSHIPS, '$permissions' => [ - 'read' => ['team:{self}'], - 'write' => ['team:{self}/owner'], + 'read' => ['user:'.$user->getId(), 'team:'.$team->getId()], + 'write' => ['user:'.$user->getId(), 'team:'.$team->getId().'/owner'], ], - 'name' => $name, - 'sum' => ($mode !== APP_MODE_ADMIN && $user->getId()) ? 1 : 0, - 'dateCreated' => \time(), + 'userId' => $user->getId(), + 'teamId' => $team->getId(), + 'roles' => $roles, + 'invited' => \time(), + 'joined' => \time(), + 'confirm' => true, + 'secret' => '', ]); - Authorization::reset(); + // Attach user to team + $user->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND); - if (false === $team) { - throw new Exception('Failed saving team to DB', 500); + $user = $projectDB->updateDocument($user->getArrayCopy()); + + if (false === $user) { + throw new Exception('Failed saving user to DB', 500); } - - if ($mode !== APP_MODE_ADMIN && $user->getId()) { // Don't add user on server mode - $membership = new Document([ - '$collection' => Database::SYSTEM_COLLECTION_MEMBERSHIPS, - '$permissions' => [ - 'read' => ['user:'.$user->getId(), 'team:'.$team->getId()], - 'write' => ['user:'.$user->getId(), 'team:'.$team->getId().'/owner'], - ], - 'userId' => $user->getId(), - 'teamId' => $team->getId(), - 'roles' => $roles, - 'invited' => \time(), - 'joined' => \time(), - 'confirm' => true, - 'secret' => '', - ]); - - // Attach user to team - $user->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND); - - $user = $projectDB->updateDocument($user->getArrayCopy()); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($team->getArrayCopy()) - ; } - ); + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->json($team->getArrayCopy()) + ; + }, ['response', 'user', 'projectDB', 'mode']); App::get('/v1/teams') ->desc('List Teams') @@ -95,23 +97,24 @@ App::get('/v1/teams') ->param('limit', 25, function () { return new Range(0, 100); }, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('offset', 0, function () { return new Range(0, 2000); }, 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', function () { return new WhiteList(['ASC', 'DESC']); }, 'Order result by ASC or DESC order.', true) - ->action( - function ($search, $limit, $offset, $orderType) use ($response, $projectDB) { - $results = $projectDB->getCollection([ - 'limit' => $limit, - 'offset' => $offset, - 'orderField' => 'dateCreated', - 'orderType' => $orderType, - 'orderCast' => 'int', - 'search' => $search, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_TEAMS, - ], - ]); + ->action(function ($search, $limit, $offset, $orderType, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - $response->json(['sum' => $projectDB->getSum(), 'teams' => $results]); - } - ); + $results = $projectDB->getCollection([ + 'limit' => $limit, + 'offset' => $offset, + 'orderField' => 'dateCreated', + 'orderType' => $orderType, + 'orderCast' => 'int', + 'search' => $search, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_TEAMS, + ], + ]); + + $response->json(['sum' => $projectDB->getSum(), 'teams' => $results]); + }, ['response', 'projectDB']); App::get('/v1/teams/:teamId') ->desc('Get Team') @@ -122,17 +125,18 @@ App::get('/v1/teams/:teamId') ->label('sdk.method', 'get') ->label('sdk.description', '/docs/references/teams/get-team.md') ->param('teamId', '', function () { return new UID(); }, 'Team unique ID.') - ->action( - function ($teamId) use ($response, $projectDB) { - $team = $projectDB->getDocument($teamId); + ->action(function ($teamId, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { - throw new Exception('Team not found', 404); - } + $team = $projectDB->getDocument($teamId); - $response->json($team->getArrayCopy([])); + if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { + throw new Exception('Team not found', 404); } - ); + + $response->json($team->getArrayCopy([])); + }, ['response', 'projectDB']); App::put('/v1/teams/:teamId') ->desc('Update Team') @@ -144,25 +148,26 @@ App::put('/v1/teams/:teamId') ->label('sdk.description', '/docs/references/teams/update-team.md') ->param('teamId', '', function () { return new UID(); }, 'Team unique ID.') ->param('name', null, function () { return new Text(100); }, 'Team name.') - ->action( - function ($teamId, $name) use ($response, $projectDB) { - $team = $projectDB->getDocument($teamId); + ->action(function ($teamId, $name, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { - throw new Exception('Team not found', 404); - } + $team = $projectDB->getDocument($teamId); - $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [ - 'name' => $name, - ])); - - if (false === $team) { - throw new Exception('Failed saving team to DB', 500); - } - - $response->json($team->getArrayCopy()); + if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { + throw new Exception('Team not found', 404); } - ); + + $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [ + 'name' => $name, + ])); + + if (false === $team) { + throw new Exception('Failed saving team to DB', 500); + } + + $response->json($team->getArrayCopy()); + }, ['response', 'projectDB']); App::delete('/v1/teams/:teamId') ->desc('Delete Team') @@ -173,36 +178,37 @@ App::delete('/v1/teams/:teamId') ->label('sdk.method', 'delete') ->label('sdk.description', '/docs/references/teams/delete-team.md') ->param('teamId', '', function () { return new UID(); }, 'Team unique ID.') - ->action( - function ($teamId) use ($response, $projectDB) { - $team = $projectDB->getDocument($teamId); + ->action(function ($teamId, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { - throw new Exception('Team not found', 404); - } + $team = $projectDB->getDocument($teamId); - $memberships = $projectDB->getCollection([ - 'limit' => 2000, // TODO add members limit - 'offset' => 0, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS, - 'teamId='.$teamId, - ], - ]); - - foreach ($memberships as $member) { - if (!$projectDB->deleteDocument($member->getId())) { - throw new Exception('Failed to remove membership for team from DB', 500); - } - } - - if (!$projectDB->deleteDocument($teamId)) { - throw new Exception('Failed to remove team from DB', 500); - } - - $response->noContent(); + if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { + throw new Exception('Team not found', 404); } - ); + + $memberships = $projectDB->getCollection([ + 'limit' => 2000, // TODO add members limit + 'offset' => 0, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS, + 'teamId='.$teamId, + ], + ]); + + foreach ($memberships as $member) { + if (!$projectDB->deleteDocument($member->getId())) { + throw new Exception('Failed to remove membership for team from DB', 500); + } + } + + if (!$projectDB->deleteDocument($teamId)) { + throw new Exception('Failed to remove team from DB', 500); + } + + $response->noContent(); + }, ['response', 'projectDB']); App::post('/v1/teams/:teamId/memberships') ->desc('Create Team Membership') @@ -216,169 +222,175 @@ App::post('/v1/teams/:teamId/memberships') ->param('email', '', function () { return new Email(); }, 'New team member email.') ->param('name', '', function () { return new Text(100); }, 'New team member name.', true) ->param('roles', [], function () { return new ArrayList(new Text(128)); }, 'Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](/docs/permissions).') - ->param('url', '', function () use ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the invitation email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.') // TODO add our own built-in confirm page - ->action( - function ($teamId, $email, $name, $roles, $url) use ($response, $mail, $project, $user, $audit, $projectDB, $mode) { - $name = (empty($name)) ? $email : $name; - $team = $projectDB->getDocument($teamId); + ->param('url', '', function ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the invitation email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients']) // TODO add our own built-in confirm page + ->action(function ($teamId, $email, $name, $roles, $url, $response, $project, $user, $projectDB, $locale, $audit, $mail, $mode) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $project */ + /** @var Appwrite\Database\Document $user */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $audit */ + /** @var Appwrite\Event\Event $mail */ + /** @var bool $mode */ - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { - throw new Exception('Team not found', 404); - } + $name = (empty($name)) ? $email : $name; + $team = $projectDB->getDocument($teamId); - $memberships = $projectDB->getCollection([ - 'limit' => 50, - 'offset' => 0, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS, - 'teamId='.$team->getId(), - ], - ]); + if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { + throw new Exception('Team not found', 404); + } - $invitee = $projectDB->getCollectionFirst([ // Get user by email address - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'email='.$email, - ], - ]); + $memberships = $projectDB->getCollection([ + 'limit' => 50, + 'offset' => 0, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS, + 'teamId='.$team->getId(), + ], + ]); - if (empty($invitee)) { // Create new user if no user with same email found + $invitee = $projectDB->getCollectionFirst([ // Get user by email address + 'limit' => 1, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_USERS, + 'email='.$email, + ], + ]); - Authorization::disable(); + if (empty($invitee)) { // Create new user if no user with same email found - try { - $invitee = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_USERS, - '$permissions' => [ - 'read' => ['user:{self}', '*'], - 'write' => ['user:{self}'], - ], - 'email' => $email, - 'emailVerification' => false, - 'status' => Auth::USER_STATUS_UNACTIVATED, - 'password' => Auth::passwordHash(Auth::passwordGenerator()), - 'password-update' => \time(), - 'registration' => \time(), - 'reset' => false, - 'name' => $name, - 'tokens' => [], - ], ['email' => $email]); - } catch (Duplicate $th) { - throw new Exception('Account already exists', 409); - } + Authorization::disable(); - Authorization::reset(); - - if (false === $invitee) { - throw new Exception('Failed saving user to DB', 500); - } - } - - $isOwner = false; - - foreach ($memberships as $member) { - if ($member->getAttribute('userId') == $invitee->getId()) { - throw new Exception('User has already been invited or is already a member of this team', 409); - } - - if ($member->getAttribute('userId') == $user->getId() && \in_array('owner', $member->getAttribute('roles', []))) { - $isOwner = true; - } - } - - if (!$isOwner && (APP_MODE_ADMIN !== $mode)) { - throw new Exception('User is not allowed to send invitations for this team', 401); - } - - $secret = Auth::tokenGenerator(); - - $membership = new Document([ - '$collection' => Database::SYSTEM_COLLECTION_MEMBERSHIPS, - '$permissions' => [ - 'read' => ['*'], - 'write' => ['user:'.$invitee->getId(), 'team:'.$team->getId().'/owner'], - ], - 'userId' => $invitee->getId(), - 'teamId' => $team->getId(), - 'roles' => $roles, - 'invited' => \time(), - 'joined' => 0, - 'confirm' => (APP_MODE_ADMIN === $mode), - 'secret' => Auth::hash($secret), - ]); - - if (APP_MODE_ADMIN === $mode) { // Allow admin to create membership - Authorization::disable(); - $membership = $projectDB->createDocument($membership->getArrayCopy()); - Authorization::reset(); - } else { - $membership = $projectDB->createDocument($membership->getArrayCopy()); - } - - if (false === $membership) { - throw new Exception('Failed saving membership to DB', 500); - } - - $url = Template::parseURL($url); - $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['inviteId' => $membership->getId(), 'teamId' => $team->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId]); - $url = Template::unParseURL($url); - - $body = new Template(__DIR__.'/../../config/locale/templates/email-base.tpl'); - $content = new Template(__DIR__.'/../../config/locale/translations/templates/'.Locale::getText('account.emails.invitation.body')); - $cta = new Template(__DIR__.'/../../config/locale/templates/email-cta.tpl'); - - $body - ->setParam('{{content}}', $content->render()) - ->setParam('{{cta}}', $cta->render()) - ->setParam('{{title}}', Locale::getText('account.emails.invitation.title')) - ->setParam('{{direction}}', Locale::getText('settings.direction')) - ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]'])) - ->setParam('{{team}}', $team->getAttribute('name', '[TEAM-NAME]')) - ->setParam('{{owner}}', $user->getAttribute('name', '')) - ->setParam('{{redirect}}', $url) - ->setParam('{{bg-body}}', '#f6f6f6') - ->setParam('{{bg-content}}', '#ffffff') - ->setParam('{{bg-cta}}', '#3498db') - ->setParam('{{bg-cta-hover}}', '#34495e') - ->setParam('{{text-content}}', '#000000') - ->setParam('{{text-cta}}', '#ffffff') - ; - - if (APP_MODE_ADMIN !== $mode) { // No need in comfirmation when in admin mode - $mail - ->setParam('event', 'teams.membership.create') - ->setParam('recipient', $email) - ->setParam('name', $name) - ->setParam('subject', \sprintf(Locale::getText('account.emails.invitation.title'), $team->getAttribute('name', '[TEAM-NAME]'), $project->getAttribute('name', ['[APP-NAME]']))) - ->setParam('body', $body->render()) - ->trigger(); - ; - } - - $audit - ->setParam('userId', $invitee->getId()) - ->setParam('event', 'teams.membership.create') - ->setParam('resource', 'teams/'.$teamId) - ; - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) // TODO change response of this endpoint - ->json(\array_merge($membership->getArrayCopy([ - '$id', - 'userId', - 'teamId', - 'roles', - 'invited', - 'joined', - 'confirm', - ]), [ + try { + $invitee = $projectDB->createDocument([ + '$collection' => Database::SYSTEM_COLLECTION_USERS, + '$permissions' => [ + 'read' => ['user:{self}', '*'], + 'write' => ['user:{self}'], + ], 'email' => $email, + 'emailVerification' => false, + 'status' => Auth::USER_STATUS_UNACTIVATED, + 'password' => Auth::passwordHash(Auth::passwordGenerator()), + 'password-update' => \time(), + 'registration' => \time(), + 'reset' => false, 'name' => $name, - ])) + 'tokens' => [], + ], ['email' => $email]); + } catch (Duplicate $th) { + throw new Exception('Account already exists', 409); + } + + Authorization::reset(); + + if (false === $invitee) { + throw new Exception('Failed saving user to DB', 500); + } + } + + $isOwner = false; + + foreach ($memberships as $member) { + if ($member->getAttribute('userId') == $invitee->getId()) { + throw new Exception('User has already been invited or is already a member of this team', 409); + } + + if ($member->getAttribute('userId') == $user->getId() && \in_array('owner', $member->getAttribute('roles', []))) { + $isOwner = true; + } + } + + if (!$isOwner && (APP_MODE_ADMIN !== $mode)) { + throw new Exception('User is not allowed to send invitations for this team', 401); + } + + $secret = Auth::tokenGenerator(); + + $membership = new Document([ + '$collection' => Database::SYSTEM_COLLECTION_MEMBERSHIPS, + '$permissions' => [ + 'read' => ['*'], + 'write' => ['user:'.$invitee->getId(), 'team:'.$team->getId().'/owner'], + ], + 'userId' => $invitee->getId(), + 'teamId' => $team->getId(), + 'roles' => $roles, + 'invited' => \time(), + 'joined' => 0, + 'confirm' => (APP_MODE_ADMIN === $mode), + 'secret' => Auth::hash($secret), + ]); + + if (APP_MODE_ADMIN === $mode) { // Allow admin to create membership + Authorization::disable(); + $membership = $projectDB->createDocument($membership->getArrayCopy()); + Authorization::reset(); + } else { + $membership = $projectDB->createDocument($membership->getArrayCopy()); + } + + if (false === $membership) { + throw new Exception('Failed saving membership to DB', 500); + } + + $url = Template::parseURL($url); + $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['inviteId' => $membership->getId(), 'teamId' => $team->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId]); + $url = Template::unParseURL($url); + + $body = new Template(__DIR__.'/../../config/locale/templates/email-base.tpl'); + $content = new Template(__DIR__.'/../../config/locale/translations/templates/'.$locale->getText('account.emails.invitation.body')); + $cta = new Template(__DIR__.'/../../config/locale/templates/email-cta.tpl'); + + $body + ->setParam('{{content}}', $content->render()) + ->setParam('{{cta}}', $cta->render()) + ->setParam('{{title}}', $locale->getText('account.emails.invitation.title')) + ->setParam('{{direction}}', $locale->getText('settings.direction')) + ->setParam('{{project}}', $project->getAttribute('name', ['[APP-NAME]'])) + ->setParam('{{team}}', $team->getAttribute('name', '[TEAM-NAME]')) + ->setParam('{{owner}}', $user->getAttribute('name', '')) + ->setParam('{{redirect}}', $url) + ->setParam('{{bg-body}}', '#f6f6f6') + ->setParam('{{bg-content}}', '#ffffff') + ->setParam('{{bg-cta}}', '#3498db') + ->setParam('{{bg-cta-hover}}', '#34495e') + ->setParam('{{text-content}}', '#000000') + ->setParam('{{text-cta}}', '#ffffff') + ; + + if (APP_MODE_ADMIN !== $mode) { // No need in comfirmation when in admin mode + $mail + ->setParam('event', 'teams.membership.create') + ->setParam('recipient', $email) + ->setParam('name', $name) + ->setParam('subject', \sprintf($locale->getText('account.emails.invitation.title'), $team->getAttribute('name', '[TEAM-NAME]'), $project->getAttribute('name', ['[APP-NAME]']))) + ->setParam('body', $body->render()) + ->trigger(); ; } - ); + + $audit + ->setParam('userId', $invitee->getId()) + ->setParam('event', 'teams.membership.create') + ->setParam('resource', 'teams/'.$teamId) + ; + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) // TODO change response of this endpoint + ->json(\array_merge($membership->getArrayCopy([ + '$id', + 'userId', + 'teamId', + 'roles', + 'invited', + 'joined', + 'confirm', + ]), [ + 'email' => $email, + 'name' => $name, + ])) + ; + }, ['response', 'project', 'user', 'projectDB', 'locale', 'audit', 'mail', 'mode']); App::get('/v1/teams/:teamId/memberships') ->desc('Get Team Memberships') @@ -393,50 +405,51 @@ App::get('/v1/teams/:teamId/memberships') ->param('limit', 25, function () { return new Range(0, 100); }, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('offset', 0, function () { return new Range(0, 2000); }, 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', function () { return new WhiteList(['ASC', 'DESC']); }, 'Order result by ASC or DESC order.', true) - ->action( - function ($teamId, $search, $limit, $offset, $orderType) use ($response, $projectDB) { - $team = $projectDB->getDocument($teamId); + ->action(function ($teamId, $search, $limit, $offset, $orderType, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { - throw new Exception('Team not found', 404); - } + $team = $projectDB->getDocument($teamId); - $memberships = $projectDB->getCollection([ - 'limit' => $limit, - 'offset' => $offset, - 'orderField' => 'joined', - 'orderType' => $orderType, - 'orderCast' => 'int', - 'search' => $search, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS, - 'teamId='.$teamId, - ], - ]); - - $users = []; - - foreach ($memberships as $membership) { - if (empty($membership->getAttribute('userId', null))) { - continue; - } - - $temp = $projectDB->getDocument($membership->getAttribute('userId', null))->getArrayCopy(['email', 'name']); - - $users[] = \array_merge($temp, $membership->getArrayCopy([ - '$id', - 'userId', - 'teamId', - 'roles', - 'invited', - 'joined', - 'confirm', - ])); - } - - $response->json(['sum' => $projectDB->getSum(), 'memberships' => $users]); + if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { + throw new Exception('Team not found', 404); } - ); + + $memberships = $projectDB->getCollection([ + 'limit' => $limit, + 'offset' => $offset, + 'orderField' => 'joined', + 'orderType' => $orderType, + 'orderCast' => 'int', + 'search' => $search, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS, + 'teamId='.$teamId, + ], + ]); + + $users = []; + + foreach ($memberships as $membership) { + if (empty($membership->getAttribute('userId', null))) { + continue; + } + + $temp = $projectDB->getDocument($membership->getAttribute('userId', null))->getArrayCopy(['email', 'name']); + + $users[] = \array_merge($temp, $membership->getArrayCopy([ + '$id', + 'userId', + 'teamId', + 'roles', + 'invited', + 'joined', + 'confirm', + ])); + } + + $response->json(['sum' => $projectDB->getSum(), 'memberships' => $users]); + }, ['response', 'projectDB']); App::patch('/v1/teams/:teamId/memberships/:inviteId/status') ->desc('Update Team Membership Status') @@ -450,125 +463,129 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') ->param('inviteId', '', function () { return new UID(); }, 'Invite unique ID.') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->param('secret', '', function () { return new Text(256); }, 'Secret key.') - ->action( - function ($teamId, $inviteId, $userId, $secret) use ($response, $request, $user, $audit, $projectDB) { - $protocol = Config::getParam('protocol'); - $membership = $projectDB->getDocument($inviteId); + ->action(function ($teamId, $inviteId, $userId, $secret, $request, $response, $user, $projectDB, $audit) { + /** @var Utopia\Request $request */ + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $user */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $audit */ - if (empty($membership->getId()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) { - throw new Exception('Invite not found', 404); - } + $protocol = $request->getProtocol(); + $membership = $projectDB->getDocument($inviteId); - if ($membership->getAttribute('teamId') !== $teamId) { - throw new Exception('Team IDs don\'t match', 404); - } + if (empty($membership->getId()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) { + throw new Exception('Invite not found', 404); + } - Authorization::disable(); + if ($membership->getAttribute('teamId') !== $teamId) { + throw new Exception('Team IDs don\'t match', 404); + } - $team = $projectDB->getDocument($teamId); - - Authorization::reset(); + Authorization::disable(); - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { - throw new Exception('Team not found', 404); - } + $team = $projectDB->getDocument($teamId); + + Authorization::reset(); - if (Auth::hash($secret) !== $membership->getAttribute('secret')) { - throw new Exception('Secret key not valid', 401); - } + if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { + throw new Exception('Team not found', 404); + } - if ($userId != $membership->getAttribute('userId')) { - throw new Exception('Invite not belong to current user ('.$user->getAttribute('email').')', 401); - } + if (Auth::hash($secret) !== $membership->getAttribute('secret')) { + throw new Exception('Secret key not valid', 401); + } - if (empty($user->getId())) { - $user = $projectDB->getCollectionFirst([ // Get user - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - '$id='.$userId, - ], - ]); - } + if ($userId != $membership->getAttribute('userId')) { + throw new Exception('Invite not belong to current user ('.$user->getAttribute('email').')', 401); + } - if ($membership->getAttribute('userId') !== $user->getId()) { - throw new Exception('Invite not belong to current user ('.$user->getAttribute('email').')', 401); - } + if (empty($user->getId())) { + $user = $projectDB->getCollectionFirst([ // Get user + 'limit' => 1, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_USERS, + '$id='.$userId, + ], + ]); + } - $membership // Attach user to team - ->setAttribute('joined', \time()) - ->setAttribute('confirm', true) - ; + if ($membership->getAttribute('userId') !== $user->getId()) { + throw new Exception('Invite not belong to current user ('.$user->getAttribute('email').')', 401); + } - $user - ->setAttribute('emailVerification', true) - ->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND) - ; + $membership // Attach user to team + ->setAttribute('joined', \time()) + ->setAttribute('confirm', true) + ; - // Log user in - $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; - $secret = Auth::tokenGenerator(); + $user + ->setAttribute('emailVerification', true) + ->setAttribute('memberships', $membership, Document::SET_TYPE_APPEND) + ; - $user->setAttribute('tokens', new Document([ - '$collection' => Database::SYSTEM_COLLECTION_TOKENS, - '$permissions' => ['read' => ['user:'.$user->getId()], 'write' => ['user:'.$user->getId()]], - 'type' => Auth::TOKEN_TYPE_LOGIN, - 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak - 'expire' => $expiry, - 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), - 'ip' => $request->getIP(), - ]), Document::SET_TYPE_APPEND); + // Log user in + $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; + $secret = Auth::tokenGenerator(); - Authorization::setRole('user:'.$userId); + $user->setAttribute('tokens', new Document([ + '$collection' => Database::SYSTEM_COLLECTION_TOKENS, + '$permissions' => ['read' => ['user:'.$user->getId()], 'write' => ['user:'.$user->getId()]], + 'type' => Auth::TOKEN_TYPE_LOGIN, + 'secret' => Auth::hash($secret), // On way hash encryption to protect DB leak + 'expire' => $expiry, + 'userAgent' => $request->getServer('HTTP_USER_AGENT', 'UNKNOWN'), + 'ip' => $request->getIP(), + ]), Document::SET_TYPE_APPEND); - $user = $projectDB->updateDocument($user->getArrayCopy()); + Authorization::setRole('user:'.$userId); - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } + $user = $projectDB->updateDocument($user->getArrayCopy()); - Authorization::disable(); + if (false === $user) { + throw new Exception('Failed saving user to DB', 500); + } - $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [ - 'sum' => $team->getAttribute('sum', 0) + 1, - ])); + Authorization::disable(); - Authorization::reset(); + $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [ + 'sum' => $team->getAttribute('sum', 0) + 1, + ])); - if (false === $team) { - throw new Exception('Failed saving team to DB', 500); - } + Authorization::reset(); - $audit - ->setParam('userId', $user->getId()) - ->setParam('event', 'teams.membership.update') - ->setParam('resource', 'teams/'.$teamId) - ; + if (false === $team) { + throw new Exception('Failed saving team to DB', 500); + } - if (!Config::getParam('domainVerification')) { - $response - ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])) - ; - } + $audit + ->setParam('userId', $user->getId()) + ->setParam('event', 'teams.membership.update') + ->setParam('resource', 'teams/'.$teamId) + ; + if (!Config::getParam('domainVerification')) { $response - ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) - ->json(\array_merge($membership->getArrayCopy([ - '$id', - 'userId', - 'teamId', - 'roles', - 'invited', - 'joined', - 'confirm', - ]), [ - 'email' => $user->getAttribute('email'), - 'name' => $user->getAttribute('name'), - ])) + ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])) ; } - ); + + $response + ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) + ->json(\array_merge($membership->getArrayCopy([ + '$id', + 'userId', + 'teamId', + 'roles', + 'invited', + 'joined', + 'confirm', + ]), [ + 'email' => $user->getAttribute('email'), + 'name' => $user->getAttribute('name'), + ])) + ; + }, ['request', 'response', 'user', 'projectDB', 'audit']); App::delete('/v1/teams/:teamId/memberships/:inviteId') ->desc('Delete Team Membership') @@ -580,44 +597,46 @@ App::delete('/v1/teams/:teamId/memberships/:inviteId') ->label('sdk.description', '/docs/references/teams/delete-team-membership.md') ->param('teamId', '', function () { return new UID(); }, 'Team unique ID.') ->param('inviteId', '', function () { return new UID(); }, 'Invite unique ID.') - ->action( - function ($teamId, $inviteId) use ($response, $projectDB, $audit) { - $membership = $projectDB->getDocument($inviteId); + ->action(function ($teamId, $inviteId, $response, $projectDB, $audit) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $audit */ - if (empty($membership->getId()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) { - throw new Exception('Invite not found', 404); - } + $membership = $projectDB->getDocument($inviteId); - if ($membership->getAttribute('teamId') !== $teamId) { - throw new Exception('Team IDs don\'t match', 404); - } - - $team = $projectDB->getDocument($teamId); - - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { - throw new Exception('Team not found', 404); - } - - if (!$projectDB->deleteDocument($membership->getId())) { - throw new Exception('Failed to remove membership from DB', 500); - } - - if ($membership->getAttribute('confirm')) { // Count only confirmed members - $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [ - 'sum' => $team->getAttribute('sum', 0) - 1, - ])); - } - - if (false === $team) { - throw new Exception('Failed saving team to DB', 500); - } - - $audit - ->setParam('userId', $membership->getAttribute('userId')) - ->setParam('event', 'teams.membership.delete') - ->setParam('resource', 'teams/'.$teamId) - ; - - $response->noContent(); + if (empty($membership->getId()) || Database::SYSTEM_COLLECTION_MEMBERSHIPS != $membership->getCollection()) { + throw new Exception('Invite not found', 404); } - ); + + if ($membership->getAttribute('teamId') !== $teamId) { + throw new Exception('Team IDs don\'t match', 404); + } + + $team = $projectDB->getDocument($teamId); + + if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { + throw new Exception('Team not found', 404); + } + + if (!$projectDB->deleteDocument($membership->getId())) { + throw new Exception('Failed to remove membership from DB', 500); + } + + if ($membership->getAttribute('confirm')) { // Count only confirmed members + $team = $projectDB->updateDocument(\array_merge($team->getArrayCopy(), [ + 'sum' => $team->getAttribute('sum', 0) - 1, + ])); + } + + if (false === $team) { + throw new Exception('Failed saving team to DB', 500); + } + + $audit + ->setParam('userId', $membership->getAttribute('userId')) + ->setParam('event', 'teams.membership.delete') + ->setParam('resource', 'teams/'.$teamId) + ; + + $response->noContent(); + }, ['response', 'projectDB', 'audit']); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 31552e8548..18d6f314a5 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -11,7 +11,6 @@ use Utopia\Validator\Range; use Utopia\Audit\Audit; use Utopia\Audit\Adapters\MySQL as AuditAdapter; use Utopia\Config\Config; -use Utopia\Locale\Locale; use Appwrite\Auth\Auth; use Appwrite\Auth\Validator\Password; use Appwrite\Database\Database; @@ -31,64 +30,65 @@ App::post('/v1/users') ->param('email', '', function () { return new Email(); }, 'User email.') ->param('password', '', function () { return new Password(); }, 'User password. Must be between 6 to 32 chars.') ->param('name', '', function () { return new Text(100); }, 'User name.', true) - ->action( - function ($email, $password, $name) use ($response, $projectDB) { - $profile = $projectDB->getCollectionFirst([ // Get user by email address - 'limit' => 1, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - 'email='.$email, - ], - ]); + ->action(function ($email, $password, $name, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - if (!empty($profile)) { - throw new Exception('User already registered', 409); - } + $profile = $projectDB->getCollectionFirst([ // Get user by email address + 'limit' => 1, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_USERS, + 'email='.$email, + ], + ]); - try { - $user = $projectDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_USERS, - '$permissions' => [ - 'read' => ['*'], - 'write' => ['user:{self}'], - ], - 'email' => $email, - 'emailVerification' => false, - 'status' => Auth::USER_STATUS_UNACTIVATED, - 'password' => Auth::passwordHash($password), - 'password-update' => \time(), - 'registration' => \time(), - 'reset' => false, - 'name' => $name, - ], ['email' => $email]); - } catch (Duplicate $th) { - throw new Exception('Account already exists', 409); - } - - $oauth2Keys = []; - - foreach (Config::getParam('providers') as $key => $provider) { - if (!$provider['enabled']) { - continue; - } - - $oauth2Keys[] = 'oauth2'.\ucfirst($key); - $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken'; - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json(\array_merge($user->getArrayCopy(\array_merge([ - '$id', - 'status', - 'email', - 'registration', - 'emailVerification', - 'name', - ], $oauth2Keys)), ['roles' => []])); + if (!empty($profile)) { + throw new Exception('User already registered', 409); } - ); - + + try { + $user = $projectDB->createDocument([ + '$collection' => Database::SYSTEM_COLLECTION_USERS, + '$permissions' => [ + 'read' => ['*'], + 'write' => ['user:{self}'], + ], + 'email' => $email, + 'emailVerification' => false, + 'status' => Auth::USER_STATUS_UNACTIVATED, + 'password' => Auth::passwordHash($password), + 'password-update' => \time(), + 'registration' => \time(), + 'reset' => false, + 'name' => $name, + ], ['email' => $email]); + } catch (Duplicate $th) { + throw new Exception('Account already exists', 409); + } + + $oauth2Keys = []; + + foreach (Config::getParam('providers') as $key => $provider) { + if (!$provider['enabled']) { + continue; + } + + $oauth2Keys[] = 'oauth2'.\ucfirst($key); + $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken'; + } + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->json(\array_merge($user->getArrayCopy(\array_merge([ + '$id', + 'status', + 'email', + 'registration', + 'emailVerification', + 'name', + ], $oauth2Keys)), ['roles' => []])); + }, ['response', 'projectDB']); + App::get('/v1/users') ->desc('List Users') ->groups(['api', 'users']) @@ -101,48 +101,49 @@ App::get('/v1/users') ->param('limit', 25, function () { return new Range(0, 100); }, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true) ->param('offset', 0, function () { return new Range(0, 2000); }, 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', function () { return new WhiteList(['ASC', 'DESC']); }, 'Order result by ASC or DESC order.', true) - ->action( - function ($search, $limit, $offset, $orderType) use ($response, $projectDB) { - $results = $projectDB->getCollection([ - 'limit' => $limit, - 'offset' => $offset, - 'orderField' => 'registration', - 'orderType' => $orderType, - 'orderCast' => 'int', - 'search' => $search, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - ], - ]); + ->action(function ($search, $limit, $offset, $orderType, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - $oauth2Keys = []; + $results = $projectDB->getCollection([ + 'limit' => $limit, + 'offset' => $offset, + 'orderField' => 'registration', + 'orderType' => $orderType, + 'orderCast' => 'int', + 'search' => $search, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_USERS, + ], + ]); - foreach (Config::getParam('providers') as $key => $provider) { - if (!$provider['enabled']) { - continue; - } + $oauth2Keys = []; - $oauth2Keys[] = 'oauth2'.\ucfirst($key); - $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken'; + foreach (Config::getParam('providers') as $key => $provider) { + if (!$provider['enabled']) { + continue; } - $results = \array_map(function ($value) use ($oauth2Keys) { /* @var $value \Database\Document */ - return $value->getArrayCopy(\array_merge( - [ - '$id', - 'status', - 'email', - 'registration', - 'emailVerification', - 'name', - ], - $oauth2Keys - )); - }, $results); - - $response->json(['sum' => $projectDB->getSum(), 'users' => $results]); + $oauth2Keys[] = 'oauth2'.\ucfirst($key); + $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken'; } - ); + + $results = \array_map(function ($value) use ($oauth2Keys) { /* @var $value \Database\Document */ + return $value->getArrayCopy(\array_merge( + [ + '$id', + 'status', + 'email', + 'registration', + 'emailVerification', + 'name', + ], + $oauth2Keys + )); + }, $results); + + $response->json(['sum' => $projectDB->getSum(), 'users' => $results]); + }, ['response', 'projectDB']); App::get('/v1/users/:userId') ->desc('Get User') @@ -153,38 +154,39 @@ App::get('/v1/users/:userId') ->label('sdk.method', 'get') ->label('sdk.description', '/docs/references/users/get-user.md') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') - ->action( - function ($userId) use ($response, $projectDB) { - $user = $projectDB->getDocument($userId); + ->action(function ($userId, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { - throw new Exception('User not found', 404); - } + $user = $projectDB->getDocument($userId); - $oauth2Keys = []; - - foreach (Config::getParam('providers') as $key => $provider) { - if (!$provider['enabled']) { - continue; - } - - $oauth2Keys[] = 'oauth2'.\ucfirst($key); - $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken'; - } - - $response->json(\array_merge($user->getArrayCopy(\array_merge( - [ - '$id', - 'status', - 'email', - 'registration', - 'emailVerification', - 'name', - ], - $oauth2Keys - )), ['roles' => []])); + if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { + throw new Exception('User not found', 404); } - ); + + $oauth2Keys = []; + + foreach (Config::getParam('providers') as $key => $provider) { + if (!$provider['enabled']) { + continue; + } + + $oauth2Keys[] = 'oauth2'.\ucfirst($key); + $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken'; + } + + $response->json(\array_merge($user->getArrayCopy(\array_merge( + [ + '$id', + 'status', + 'email', + 'registration', + 'emailVerification', + 'name', + ], + $oauth2Keys + )), ['roles' => []])); + }, ['response', 'projectDB']); App::get('/v1/users/:userId/prefs') ->desc('Get User Preferences') @@ -195,26 +197,27 @@ App::get('/v1/users/:userId/prefs') ->label('sdk.method', 'getPrefs') ->label('sdk.description', '/docs/references/users/get-user-prefs.md') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') - ->action( - function ($userId) use ($response, $projectDB) { - $user = $projectDB->getDocument($userId); + ->action(function ($userId, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { - throw new Exception('User not found', 404); - } + $user = $projectDB->getDocument($userId); - $prefs = $user->getAttribute('prefs', ''); - - try { - $prefs = \json_decode($prefs, true); - $prefs = ($prefs) ? $prefs : []; - } catch (\Exception $error) { - throw new Exception('Failed to parse prefs', 500); - } - - $response->json($prefs); + if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { + throw new Exception('User not found', 404); } - ); + + $prefs = $user->getAttribute('prefs', ''); + + try { + $prefs = \json_decode($prefs, true); + $prefs = ($prefs) ? $prefs : []; + } catch (\Exception $error) { + throw new Exception('Failed to parse prefs', 500); + } + + $response->json($prefs); + }, ['response', 'projectDB']); App::get('/v1/users/:userId/sessions') ->desc('Get User Sessions') @@ -225,60 +228,62 @@ App::get('/v1/users/:userId/sessions') ->label('sdk.method', 'getSessions') ->label('sdk.description', '/docs/references/users/get-user-sessions.md') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') - ->action( - function ($userId) use ($response, $projectDB) { - $user = $projectDB->getDocument($userId); + ->action(function ($userId, $response, $projectDB, $locale) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Locale\Locale $locale */ - if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { - throw new Exception('User not found', 404); - } + $user = $projectDB->getDocument($userId); - $tokens = $user->getAttribute('tokens', []); - $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb'); - $sessions = []; - $index = 0; - $countries = Locale::getText('countries'); - - foreach ($tokens as $token) { /* @var $token Document */ - if (Auth::TOKEN_TYPE_LOGIN != $token->getAttribute('type')) { - continue; - } - - $userAgent = (!empty($token->getAttribute('userAgent'))) ? $token->getAttribute('userAgent') : 'UNKNOWN'; - - $dd = new DeviceDetector($userAgent); - - // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then) - // $dd->skipBotDetection(); - - $dd->parse(); - - $sessions[$index] = [ - '$id' => $token->getId(), - 'OS' => $dd->getOs(), - 'client' => $dd->getClient(), - 'device' => $dd->getDevice(), - 'brand' => $dd->getBrand(), - 'model' => $dd->getModel(), - 'ip' => $token->getAttribute('ip', ''), - 'geo' => [], - ]; - - try { - $record = $reader->country($token->getAttribute('ip', '')); - $sessions[$index]['geo']['isoCode'] = \strtolower($record->country->isoCode); - $sessions[$index]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown'); - } catch (\Exception $e) { - $sessions[$index]['geo']['isoCode'] = '--'; - $sessions[$index]['geo']['country'] = Locale::getText('locale.country.unknown'); - } - - ++$index; - } - - $response->json($sessions); + if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { + throw new Exception('User not found', 404); } - ); + + $tokens = $user->getAttribute('tokens', []); + $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb'); + $sessions = []; + $index = 0; + $countries = $locale->getText('countries'); + + foreach ($tokens as $token) { /* @var $token Document */ + if (Auth::TOKEN_TYPE_LOGIN != $token->getAttribute('type')) { + continue; + } + + $userAgent = (!empty($token->getAttribute('userAgent'))) ? $token->getAttribute('userAgent') : 'UNKNOWN'; + + $dd = new DeviceDetector($userAgent); + + // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then) + // $dd->skipBotDetection(); + + $dd->parse(); + + $sessions[$index] = [ + '$id' => $token->getId(), + 'OS' => $dd->getOs(), + 'client' => $dd->getClient(), + 'device' => $dd->getDevice(), + 'brand' => $dd->getBrand(), + 'model' => $dd->getModel(), + 'ip' => $token->getAttribute('ip', ''), + 'geo' => [], + ]; + + try { + $record = $reader->country($token->getAttribute('ip', '')); + $sessions[$index]['geo']['isoCode'] = \strtolower($record->country->isoCode); + $sessions[$index]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : $locale->getText('locale.country.unknown'); + } catch (\Exception $e) { + $sessions[$index]['geo']['isoCode'] = '--'; + $sessions[$index]['geo']['country'] = $locale->getText('locale.country.unknown'); + } + + ++$index; + } + + $response->json($sessions); + }, ['response', 'projectDB', 'locale']); App::get('/v1/users/:userId/logs') ->desc('Get User Logs') @@ -289,77 +294,81 @@ App::get('/v1/users/:userId/logs') ->label('sdk.method', 'getLogs') ->label('sdk.description', '/docs/references/users/get-user-logs.md') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') - ->action( - function ($userId) use ($response, $register, $projectDB, $project) { - $user = $projectDB->getDocument($userId); + ->action(function ($userId, $response, $register, $project, $projectDB, $locale) { + /** @var Utopia\Response $response */ + /** @var Utopia\Registry\Registry $register */ + /** @var Appwrite\Database\Document $project */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Locale\Locale $locale */ + + $user = $projectDB->getDocument($userId); - if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { - throw new Exception('User not found', 404); - } - - $adapter = new AuditAdapter($register->get('db')); - $adapter->setNamespace('app_'.$project->getId()); - - $audit = new Audit($adapter); - - $countries = Locale::getText('countries'); - - $logs = $audit->getLogsByUserAndActions($user->getId(), [ - 'account.create', - 'account.delete', - 'account.update.name', - 'account.update.email', - 'account.update.password', - 'account.update.prefs', - 'account.sessions.create', - 'account.sessions.delete', - 'account.recovery.create', - 'account.recovery.update', - 'account.verification.create', - 'account.verification.update', - 'teams.membership.create', - 'teams.membership.update', - 'teams.membership.delete', - ]); - - $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb'); - $output = []; - - foreach ($logs as $i => &$log) { - $log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN'; - - $dd = new DeviceDetector($log['userAgent']); - - $dd->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then) - - $dd->parse(); - - $output[$i] = [ - 'event' => $log['event'], - 'ip' => $log['ip'], - 'time' => \strtotime($log['time']), - 'OS' => $dd->getOs(), - 'client' => $dd->getClient(), - 'device' => $dd->getDevice(), - 'brand' => $dd->getBrand(), - 'model' => $dd->getModel(), - 'geo' => [], - ]; - - try { - $record = $reader->country($log['ip']); - $output[$i]['geo']['isoCode'] = \strtolower($record->country->isoCode); - $output[$i]['geo']['country'] = $record->country->name; - $output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown'); - } catch (\Exception $e) { - $output[$i]['geo']['isoCode'] = '--'; - $output[$i]['geo']['country'] = Locale::getText('locale.country.unknown'); - } - } - - $response->json($output); + if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { + throw new Exception('User not found', 404); } - ); + + $adapter = new AuditAdapter($register->get('db')); + $adapter->setNamespace('app_'.$project->getId()); + + $audit = new Audit($adapter); + + $countries = $locale->getText('countries'); + + $logs = $audit->getLogsByUserAndActions($user->getId(), [ + 'account.create', + 'account.delete', + 'account.update.name', + 'account.update.email', + 'account.update.password', + 'account.update.prefs', + 'account.sessions.create', + 'account.sessions.delete', + 'account.recovery.create', + 'account.recovery.update', + 'account.verification.create', + 'account.verification.update', + 'teams.membership.create', + 'teams.membership.update', + 'teams.membership.delete', + ]); + + $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb'); + $output = []; + + foreach ($logs as $i => &$log) { + $log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN'; + + $dd = new DeviceDetector($log['userAgent']); + + $dd->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then) + + $dd->parse(); + + $output[$i] = [ + 'event' => $log['event'], + 'ip' => $log['ip'], + 'time' => \strtotime($log['time']), + 'OS' => $dd->getOs(), + 'client' => $dd->getClient(), + 'device' => $dd->getDevice(), + 'brand' => $dd->getBrand(), + 'model' => $dd->getModel(), + 'geo' => [], + ]; + + try { + $record = $reader->country($log['ip']); + $output[$i]['geo']['isoCode'] = \strtolower($record->country->isoCode); + $output[$i]['geo']['country'] = $record->country->name; + $output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : $locale->getText('locale.country.unknown'); + } catch (\Exception $e) { + $output[$i]['geo']['isoCode'] = '--'; + $output[$i]['geo']['country'] = $locale->getText('locale.country.unknown'); + } + } + + $response->json($output); + }, ['response', 'register', 'project', 'projectDB', 'locale']); App::patch('/v1/users/:userId/status') ->desc('Update User Status') @@ -371,44 +380,45 @@ App::patch('/v1/users/:userId/status') ->label('sdk.description', '/docs/references/users/update-user-status.md') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->param('status', '', function () { return new WhiteList([Auth::USER_STATUS_ACTIVATED, Auth::USER_STATUS_BLOCKED, Auth::USER_STATUS_UNACTIVATED]); }, 'User Status code. To activate the user pass '.Auth::USER_STATUS_ACTIVATED.', to block the user pass '.Auth::USER_STATUS_BLOCKED.' and for disabling the user pass '.Auth::USER_STATUS_UNACTIVATED) - ->action( - function ($userId, $status) use ($response, $projectDB) { - $user = $projectDB->getDocument($userId); + ->action(function ($userId, $status, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { - throw new Exception('User not found', 404); - } + $user = $projectDB->getDocument($userId); - $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ - 'status' => (int)$status, - ])); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } - - $oauth2Keys = []; - - foreach (Config::getParam('providers') as $key => $provider) { - if (!$provider['enabled']) { - continue; - } - - $oauth2Keys[] = 'oauth2'.\ucfirst($key); - $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken'; - } - - $response - ->json(\array_merge($user->getArrayCopy(\array_merge([ - '$id', - 'status', - 'email', - 'registration', - 'emailVerification', - 'name', - ], $oauth2Keys)), ['roles' => []])); + if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { + throw new Exception('User not found', 404); } - ); + + $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ + 'status' => (int)$status, + ])); + + if (false === $user) { + throw new Exception('Failed saving user to DB', 500); + } + + $oauth2Keys = []; + + foreach (Config::getParam('providers') as $key => $provider) { + if (!$provider['enabled']) { + continue; + } + + $oauth2Keys[] = 'oauth2'.\ucfirst($key); + $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken'; + } + + $response + ->json(\array_merge($user->getArrayCopy(\array_merge([ + '$id', + 'status', + 'email', + 'registration', + 'emailVerification', + 'name', + ], $oauth2Keys)), ['roles' => []])); + }, ['response', 'projectDB']); App::patch('/v1/users/:userId/prefs') ->desc('Update User Preferences') @@ -420,37 +430,38 @@ App::patch('/v1/users/:userId/prefs') ->label('sdk.description', '/docs/references/users/update-user-prefs.md') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->param('prefs', '', function () { return new Assoc();}, 'Prefs key-value JSON object.') - ->action( - function ($userId, $prefs) use ($response, $projectDB) { - $user = $projectDB->getDocument($userId); + ->action(function ($userId, $prefs, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { - throw new Exception('User not found', 404); - } + $user = $projectDB->getDocument($userId); - $old = \json_decode($user->getAttribute('prefs', '{}'), true); - $old = ($old) ? $old : []; - - $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ - 'prefs' => \json_encode(\array_merge($old, $prefs)), - ])); - - if (false === $user) { - throw new Exception('Failed saving user to DB', 500); - } - - $prefs = $user->getAttribute('prefs', ''); - - try { - $prefs = \json_decode($prefs, true); - $prefs = ($prefs) ? $prefs : []; - } catch (\Exception $error) { - throw new Exception('Failed to parse prefs', 500); - } - - $response->json($prefs); + if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { + throw new Exception('User not found', 404); } - ); + + $old = \json_decode($user->getAttribute('prefs', '{}'), true); + $old = ($old) ? $old : []; + + $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ + 'prefs' => \json_encode(\array_merge($old, $prefs)), + ])); + + if (false === $user) { + throw new Exception('Failed saving user to DB', 500); + } + + $prefs = $user->getAttribute('prefs', ''); + + try { + $prefs = \json_decode($prefs, true); + $prefs = ($prefs) ? $prefs : []; + } catch (\Exception $error) { + throw new Exception('Failed to parse prefs', 500); + } + + $response->json($prefs); + }, ['response', 'projectDB']); App::delete('/v1/users/:userId/sessions/:sessionId') @@ -464,27 +475,28 @@ App::delete('/v1/users/:userId/sessions/:sessionId') ->label('abuse-limit', 100) ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->param('sessionId', null, function () { return new UID(); }, 'User unique session ID.') - ->action( - function ($userId, $sessionId) use ($response, $request, $projectDB) { - $user = $projectDB->getDocument($userId); + ->action(function ($userId, $sessionId, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { - throw new Exception('User not found', 404); - } + $user = $projectDB->getDocument($userId); - $tokens = $user->getAttribute('tokens', []); + if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { + throw new Exception('User not found', 404); + } - foreach ($tokens as $token) { /* @var $token Document */ - if ($sessionId == $token->getId()) { - if (!$projectDB->deleteDocument($token->getId())) { - throw new Exception('Failed to remove token from DB', 500); - } + $tokens = $user->getAttribute('tokens', []); + + foreach ($tokens as $token) { /* @var $token Document */ + if ($sessionId == $token->getId()) { + if (!$projectDB->deleteDocument($token->getId())) { + throw new Exception('Failed to remove token from DB', 500); } } - - $response->json(array('result' => 'success')); } - ); + + $response->json(array('result' => 'success')); + }, ['response', 'projectDB']); App::delete('/v1/users/:userId/sessions') ->desc('Delete User Sessions') @@ -496,22 +508,23 @@ App::delete('/v1/users/:userId/sessions') ->label('sdk.description', '/docs/references/users/delete-user-sessions.md') ->label('abuse-limit', 100) ->param('userId', '', function () { return new UID(); }, 'User unique ID.') - ->action( - function ($userId) use ($response, $request, $projectDB) { - $user = $projectDB->getDocument($userId); + ->action(function ($userId, $response, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ - if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { - throw new Exception('User not found', 404); - } + $user = $projectDB->getDocument($userId); - $tokens = $user->getAttribute('tokens', []); - - foreach ($tokens as $token) { /* @var $token Document */ - if (!$projectDB->deleteDocument($token->getId())) { - throw new Exception('Failed to remove token from DB', 500); - } - } - - $response->json(array('result' => 'success')); + if (empty($user->getId()) || Database::SYSTEM_COLLECTION_USERS != $user->getCollection()) { + throw new Exception('User not found', 404); } - ); + + $tokens = $user->getAttribute('tokens', []); + + foreach ($tokens as $token) { /* @var $token Document */ + if (!$projectDB->deleteDocument($token->getId())) { + throw new Exception('Failed to remove token from DB', 500); + } + } + + $response->json(array('result' => 'success')); + }, ['response', 'projectDB']); diff --git a/app/controllers/shared/web.php b/app/controllers/shared/web.php index 9ff3612290..e5eb16c489 100644 --- a/app/controllers/shared/web.php +++ b/app/controllers/shared/web.php @@ -4,7 +4,11 @@ use Utopia\App; use Utopia\View; use Utopia\Config\Config; -App::init(function ($utopia, $response, $request, $layout) { +App::init(function ($utopia, $request, $response, $layout) { + /** @var Utopia\App $utopia */ + /** @var Utopia\Request $request */ + /** @var Utopia\Response $response */ + /** @var Utopia\View $layout */ /* AJAX check */ if (!empty($request->getQuery('version', ''))) { @@ -12,7 +16,7 @@ App::init(function ($utopia, $response, $request, $layout) { } $layout ->setParam('title', APP_NAME) - ->setParam('protocol', Config::getParam('protocol')) + ->setParam('protocol', $request->getProtocol()) ->setParam('domain', Config::getParam('domain')) ->setParam('home', App::getEnv('_APP_HOME')) ->setParam('setup', App::getEnv('_APP_SETUP')) @@ -36,8 +40,8 @@ App::init(function ($utopia, $response, $request, $layout) { $route = $utopia->match($request); $scope = $route->getLabel('scope', ''); $layout - ->setParam('version', Config::getParam('version')) + ->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN')) ->setParam('isDev', App::isDevelopment()) ->setParam('class', $scope) ; -}, ['utopia', 'response', 'request', 'layout'], 'web'); +}, ['utopia', 'request', 'response', 'layout'], 'web'); diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php index 1c6db92975..962373bd53 100644 --- a/app/controllers/web/console.php +++ b/app/controllers/web/console.php @@ -29,7 +29,7 @@ App::shutdown(function ($response, $layout) { $footer ->setParam('home', App::getEnv('_APP_HOME', '')) - ->setParam('version', Config::getParam('version')) + ->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN')) ; $layout diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index b9e75ca52d..9cbfef4e47 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -9,11 +9,13 @@ use Utopia\Validator\WhiteList; use Utopia\Validator\Range; App::init(function ($layout) { + /** @var Utopia\View $layout */ + $header = new View(__DIR__.'/../../views/home/comps/header.phtml'); $footer = new View(__DIR__.'/../../views/home/comps/footer.phtml'); $footer - ->setParam('version', Config::getParam('version')) + ->setParam('version', App::getEnv('_APP_VERSION', 'UNKNOWN')) ; $layout @@ -27,6 +29,9 @@ App::init(function ($layout) { }, ['layout'], 'home'); App::shutdown(function ($response, $layout) { + /** @var Utopia\Response $response */ + /** @var Utopia\View $layout */ + $response->send($layout->render()); }, ['response', 'layout'], 'home'); @@ -34,90 +39,102 @@ App::get('/') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') - ->action( - function () use ($response) { - $response->redirect('/auth/signin'); - } - ); + ->action(function ($response) { + /** @var Utopia\Response $response */ + + $response->redirect('/auth/signin'); + }, ['response']); App::get('/auth/signin') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') - ->action(function () use ($layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ + $page = new View(__DIR__.'/../../views/home/auth/signin.phtml'); $layout ->setParam('title', 'Sign In - '.APP_NAME) ->setParam('body', $page); - }); + }, ['layout']); App::get('/auth/signup') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') - ->action(function () use ($layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ $page = new View(__DIR__.'/../../views/home/auth/signup.phtml'); $layout ->setParam('title', 'Sign Up - '.APP_NAME) ->setParam('body', $page); - }); + }, ['layout']); App::get('/auth/recovery') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') - ->action(function () use ($request, $layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ + $page = new View(__DIR__.'/../../views/home/auth/recovery.phtml'); $layout ->setParam('title', 'Password Recovery - '.APP_NAME) ->setParam('body', $page); - }); + }, ['layout']); App::get('/auth/confirm') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') - ->action(function () use ($layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ + $page = new View(__DIR__.'/../../views/home/auth/confirm.phtml'); $layout ->setParam('title', 'Account Confirmation - '.APP_NAME) ->setParam('body', $page); - }); + }, ['layout']); App::get('/auth/join') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') - ->action(function () use ($layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ + $page = new View(__DIR__.'/../../views/home/auth/join.phtml'); $layout ->setParam('title', 'Invitation - '.APP_NAME) ->setParam('body', $page); - }); + }, ['layout']); App::get('/auth/recovery/reset') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') - ->action(function () use ($layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ + $page = new View(__DIR__.'/../../views/home/auth/recovery/reset.phtml'); $layout ->setParam('title', 'Password Reset - '.APP_NAME) ->setParam('body', $page); - }); - + }, ['layout']); App::get('/auth/oauth2/success') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') - ->action(function () use ($layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ + $page = new View(__DIR__.'/../../views/home/auth/oauth2.phtml'); $layout @@ -126,13 +143,15 @@ App::get('/auth/oauth2/success') ->setParam('header', []) ->setParam('footer', []) ; - }); + }, ['layout']); App::get('/auth/oauth2/failure') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') - ->action(function () use ($layout) { + ->action(function ($layout) { + /** @var Utopia\View $layout */ + $page = new View(__DIR__.'/../../views/home/auth/oauth2.phtml'); $layout @@ -141,14 +160,16 @@ App::get('/auth/oauth2/failure') ->setParam('header', []) ->setParam('footer', []) ; - }); + }, ['layout']); App::get('/error/:code') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') ->param('code', null, new \Utopia\Validator\Numeric(), 'Valid status code number', false) - ->action(function ($code) use ($layout) { + ->action(function ($code, $layout) { + /** @var Utopia\View $layout */ + $page = new View(__DIR__.'/../../views/error.phtml'); $page @@ -158,7 +179,7 @@ App::get('/error/:code') $layout ->setParam('title', 'Error'.' - '.APP_NAME) ->setParam('body', $page); - }); + }, ['layout']); App::get('/open-api-2.json') ->groups(['web', 'home']) @@ -167,401 +188,402 @@ App::get('/open-api-2.json') ->param('platform', APP_PLATFORM_CLIENT, function () {return new WhiteList([APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER, APP_PLATFORM_CONSOLE]);}, 'Choose target platform.', true) ->param('extensions', 0, function () {return new Range(0, 1);}, 'Show extra data.', true) ->param('tests', 0, function () {return new Range(0, 1);}, 'Include only test services.', true) - ->action( - function ($platform, $extensions, $tests) use ($response, $utopia) { - $services = Config::getParam('services', []); + ->action(function ($platform, $extensions, $tests, $utopia, $response) { + /** @var Utopia\App $utopia */ + /** @var Utopia\Response $response */ + + $services = Config::getParam('services', []); + + function fromCamelCase($input) + { + \preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $input, $matches); + $ret = $matches[0]; + foreach ($ret as &$match) { + $match = $match == \strtoupper($match) ? \strtolower($match) : \lcfirst($match); + } + + return \implode('_', $ret); + } + + function fromCamelCaseToDash($input) + { + return \str_replace([' ', '_'], '-', \strtolower(\preg_replace('/([a-zA-Z])(?=[A-Z])/', '$1-', $input))); + } + + foreach ($services as $service) { /* @noinspection PhpIncludeInspection */ + if ($tests && !isset($service['tests'])) { + continue; + } + + if ($tests && !$service['tests']) { + continue; + } - function fromCamelCase($input) - { - \preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $input, $matches); - $ret = $matches[0]; - foreach ($ret as &$match) { - $match = $match == \strtoupper($match) ? \strtolower($match) : \lcfirst($match); - } + if (!$tests && !$service['sdk']) { + continue; + } + + /** @noinspection PhpIncludeInspection */ + include_once \realpath(__DIR__.'/../../'.$service['controller']); + } - return \implode('_', $ret); + $security = [ + APP_PLATFORM_CLIENT => ['Project' => []], + APP_PLATFORM_SERVER => ['Project' => [], 'Key' => []], + APP_PLATFORM_CONSOLE => ['Project' => [], 'Key' => []], + ]; + + $platforms = [ + 'client' => APP_PLATFORM_CLIENT, + 'server' => APP_PLATFORM_SERVER, + 'all' => APP_PLATFORM_CONSOLE, + ]; + + $keys = [ + APP_PLATFORM_CLIENT => [ + 'Project' => [ + 'type' => 'apiKey', + 'name' => 'X-Appwrite-Project', + 'description' => 'Your project ID', + 'in' => 'header', + ], + 'Locale' => [ + 'type' => 'apiKey', + 'name' => 'X-Appwrite-Locale', + 'description' => '', + 'in' => 'header', + ], + ], + APP_PLATFORM_SERVER => [ + 'Project' => [ + 'type' => 'apiKey', + 'name' => 'X-Appwrite-Project', + 'description' => 'Your project ID', + 'in' => 'header', + ], + 'Key' => [ + 'type' => 'apiKey', + 'name' => 'X-Appwrite-Key', + 'description' => 'Your secret API key', + 'in' => 'header', + ], + 'Locale' => [ + 'type' => 'apiKey', + 'name' => 'X-Appwrite-Locale', + 'description' => '', + 'in' => 'header', + ], + ], + APP_PLATFORM_CONSOLE => [ + 'Project' => [ + 'type' => 'apiKey', + 'name' => 'X-Appwrite-Project', + 'description' => 'Your project ID', + 'in' => 'header', + ], + 'Key' => [ + 'type' => 'apiKey', + 'name' => 'X-Appwrite-Key', + 'description' => 'Your secret API key', + 'in' => 'header', + ], + 'Locale' => [ + 'type' => 'apiKey', + 'name' => 'X-Appwrite-Locale', + 'description' => '', + 'in' => 'header', + ], + 'Mode' => [ + 'type' => 'apiKey', + 'name' => 'X-Appwrite-Mode', + 'description' => '', + 'in' => 'header', + ], + ], + ]; + + /* + * Specifications (v3.0.0): + * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md + */ + $output = [ + 'swagger' => '2.0', + 'info' => [ + 'version' => APP_VERSION_STABLE, + 'title' => APP_NAME, + 'description' => 'Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)', + 'termsOfService' => 'https://appwrite.io/policy/terms', + 'contact' => [ + 'name' => 'Appwrite Team', + 'url' => 'https://appwrite.io/support', + 'email' => App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), + ], + 'license' => [ + 'name' => 'BSD-3-Clause', + 'url' => 'https://raw.githubusercontent.com/appwrite/appwrite/master/LICENSE', + ], + ], + 'host' => \parse_url(App::getEnv('_APP_HOME', Config::getParam('domain')), PHP_URL_HOST), + 'basePath' => '/v1', + 'schemes' => ['https'], + 'consumes' => ['application/json', 'multipart/form-data'], + 'produces' => ['application/json'], + 'securityDefinitions' => $keys[$platform], + 'paths' => [], + 'definitions' => [ + // 'Pet' => [ + // 'required' => ['id', 'name'], + // 'properties' => [ + // 'id' => [ + // 'type' => 'integer', + // 'format' => 'int64', + // ], + // 'name' => [ + // 'type' => 'string', + // ], + // 'tag' => [ + // 'type' => 'string', + // ], + // ], + // ], + // 'Pets' => array( + // 'type' => 'array', + // 'items' => array( + // '$ref' => '#/definitions/Pet', + // ), + // ), + 'Error' => array( + 'required' => array( + 0 => 'code', + 1 => 'message', + ), + 'properties' => array( + 'code' => array( + 'type' => 'integer', + 'format' => 'int32', + ), + 'message' => array( + 'type' => 'string', + ), + ), + ), + ], + 'externalDocs' => [ + 'description' => 'Full API docs, specs and tutorials', + 'url' => Config::getParam('protocol').'://'.Config::getParam('domain').'/docs', + ], + ]; + + if ($extensions) { + if (isset($output['securityDefinitions']['Project'])) { + $output['securityDefinitions']['Project']['extensions'] = ['demo' => '5df5acd0d48c2']; + } + + if (isset($output['securityDefinitions']['Key'])) { + $output['securityDefinitions']['Key']['extensions'] = ['demo' => '919c2d18fb5d4...a2ae413da83346ad2']; + } + + if (isset($output['securityDefinitions']['Locale'])) { + $output['securityDefinitions']['Locale']['extensions'] = ['demo' => 'en']; } - function fromCamelCaseToDash($input) - { - return \str_replace([' ', '_'], '-', \strtolower(\preg_replace('/([a-zA-Z])(?=[A-Z])/', '$1-', $input))); + if (isset($output['securityDefinitions']['Mode'])) { + $output['securityDefinitions']['Mode']['extensions'] = ['demo' => '']; } + } - foreach ($services as $service) { /* @noinspection PhpIncludeInspection */ - if ($tests && !isset($service['tests'])) { + foreach ($utopia->getRoutes() as $key => $method) { + foreach ($method as $route) { /* @var $route \Utopia\Route */ + if (!$route->getLabel('docs', true)) { continue; } - if ($tests && !$service['tests']) { + if (empty($route->getLabel('sdk.namespace', null))) { continue; } - - if (!$tests && !$service['sdk']) { + + if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $route->getLabel('sdk.platform', []))) { continue; } - - /** @noinspection PhpIncludeInspection */ - include_once \realpath(__DIR__.'/../../'.$service['controller']); - } - $security = [ - APP_PLATFORM_CLIENT => ['Project' => []], - APP_PLATFORM_SERVER => ['Project' => [], 'Key' => []], - APP_PLATFORM_CONSOLE => ['Project' => [], 'Key' => []], - ]; + $url = \str_replace('/v1', '', $route->getURL()); + $scope = $route->getLabel('scope', ''); + $hide = $route->getLabel('sdk.hide', false); + $consumes = ['application/json']; - $platforms = [ - 'client' => APP_PLATFORM_CLIENT, - 'server' => APP_PLATFORM_SERVER, - 'all' => APP_PLATFORM_CONSOLE, - ]; + if ($hide) { + continue; + } - $keys = [ - APP_PLATFORM_CLIENT => [ - 'Project' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-Project', - 'description' => 'Your project ID', - 'in' => 'header', - ], - 'Locale' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-Locale', - 'description' => '', - 'in' => 'header', - ], - ], - APP_PLATFORM_SERVER => [ - 'Project' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-Project', - 'description' => 'Your project ID', - 'in' => 'header', - ], - 'Key' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-Key', - 'description' => 'Your secret API key', - 'in' => 'header', - ], - 'Locale' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-Locale', - 'description' => '', - 'in' => 'header', - ], - ], - APP_PLATFORM_CONSOLE => [ - 'Project' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-Project', - 'description' => 'Your project ID', - 'in' => 'header', - ], - 'Key' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-Key', - 'description' => 'Your secret API key', - 'in' => 'header', - ], - 'Locale' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-Locale', - 'description' => '', - 'in' => 'header', - ], - 'Mode' => [ - 'type' => 'apiKey', - 'name' => 'X-Appwrite-Mode', - 'description' => '', - 'in' => 'header', - ], - ], - ]; - - /* - * Specifications (v3.0.0): - * https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md - */ - $output = [ - 'swagger' => '2.0', - 'info' => [ - 'version' => APP_VERSION_STABLE, - 'title' => APP_NAME, - 'description' => 'Appwrite backend as a service cuts up to 70% of the time and costs required for building a modern application. We abstract and simplify common development tasks behind a REST APIs, to help you develop your app in a fast and secure way. For full API documentation and tutorials go to [https://appwrite.io/docs](https://appwrite.io/docs)', - 'termsOfService' => 'https://appwrite.io/policy/terms', - 'contact' => [ - 'name' => 'Appwrite Team', - 'url' => 'https://appwrite.io/support', - 'email' => App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), - ], - 'license' => [ - 'name' => 'BSD-3-Clause', - 'url' => 'https://raw.githubusercontent.com/appwrite/appwrite/master/LICENSE', - ], - ], - 'host' => \parse_url(App::getEnv('_APP_HOME', Config::getParam('domain')), PHP_URL_HOST), - 'basePath' => '/v1', - 'schemes' => ['https'], - 'consumes' => ['application/json', 'multipart/form-data'], - 'produces' => ['application/json'], - 'securityDefinitions' => $keys[$platform], - 'paths' => [], - 'definitions' => [ - // 'Pet' => [ - // 'required' => ['id', 'name'], - // 'properties' => [ - // 'id' => [ - // 'type' => 'integer', - // 'format' => 'int64', - // ], - // 'name' => [ - // 'type' => 'string', - // ], - // 'tag' => [ - // 'type' => 'string', + $desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath('../'.$route->getLabel('sdk.description', '')) : null; + + $temp = [ + 'summary' => $route->getDesc(), + 'operationId' => $route->getLabel('sdk.method', \uniqid()), + 'consumes' => [], + 'tags' => [$route->getLabel('sdk.namespace', 'default')], + 'description' => ($desc) ? \file_get_contents($desc) : '', + + // 'responses' => [ + // 200 => [ + // 'description' => 'An paged array of pets', + // 'schema' => [ + // '$ref' => '#/definitions/Pet', // ], // ], // ], - // 'Pets' => array( - // 'type' => 'array', - // 'items' => array( - // '$ref' => '#/definitions/Pet', - // ), - // ), - 'Error' => array( - 'required' => array( - 0 => 'code', - 1 => 'message', - ), - 'properties' => array( - 'code' => array( - 'type' => 'integer', - 'format' => 'int32', - ), - 'message' => array( - 'type' => 'string', - ), - ), - ), - ], - 'externalDocs' => [ - 'description' => 'Full API docs, specs and tutorials', - 'url' => Config::getParam('protocol').'://'.Config::getParam('domain').'/docs', - ], - ]; + ]; - if ($extensions) { - if (isset($output['securityDefinitions']['Project'])) { - $output['securityDefinitions']['Project']['extensions'] = ['demo' => '5df5acd0d48c2']; - } - - if (isset($output['securityDefinitions']['Key'])) { - $output['securityDefinitions']['Key']['extensions'] = ['demo' => '919c2d18fb5d4...a2ae413da83346ad2']; - } - - if (isset($output['securityDefinitions']['Locale'])) { - $output['securityDefinitions']['Locale']['extensions'] = ['demo' => 'en']; - } + if ($extensions) { + $platformList = $route->getLabel('sdk.platform', []); - if (isset($output['securityDefinitions']['Mode'])) { - $output['securityDefinitions']['Mode']['extensions'] = ['demo' => '']; - } - } - - foreach ($utopia->getRoutes() as $key => $method) { - foreach ($method as $route) { /* @var $route \Utopia\Route */ - if (!$route->getLabel('docs', true)) { - continue; - } - - if (empty($route->getLabel('sdk.namespace', null))) { - continue; - } - - if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $route->getLabel('sdk.platform', []))) { - continue; - } - - $url = \str_replace('/v1', '', $route->getURL()); - $scope = $route->getLabel('scope', ''); - $hide = $route->getLabel('sdk.hide', false); - $consumes = ['application/json']; - - if ($hide) { - continue; - } - - $desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath('../'.$route->getLabel('sdk.description', '')) : null; - - $temp = [ - 'summary' => $route->getDesc(), - 'operationId' => $route->getLabel('sdk.method', \uniqid()), - 'consumes' => [], - 'tags' => [$route->getLabel('sdk.namespace', 'default')], - 'description' => ($desc) ? \file_get_contents($desc) : '', - - // 'responses' => [ - // 200 => [ - // 'description' => 'An paged array of pets', - // 'schema' => [ - // '$ref' => '#/definitions/Pet', - // ], - // ], - // ], + $temp['extensions'] = [ + 'weight' => $route->getOrder(), + 'cookies' => $route->getLabel('sdk.cookies', false), + 'type' => $route->getLabel('sdk.methodType', ''), + 'demo' => 'docs/examples/'.fromCamelCaseToDash($route->getLabel('sdk.namespace', 'default')).'/'.fromCamelCaseToDash($temp['operationId']).'.md', + 'edit' => 'https://github.com/appwrite/appwrite/edit/master' . $route->getLabel('sdk.description', ''), + 'rate-limit' => $route->getLabel('abuse-limit', 0), + 'rate-time' => $route->getLabel('abuse-time', 3600), + 'rate-key' => $route->getLabel('abuse-key', 'url:{url},ip:{ip}'), + 'scope' => $route->getLabel('scope', ''), + 'platforms' => $platformList, ]; + } - if ($extensions) { - $platformList = $route->getLabel('sdk.platform', []); + if ((!empty($scope))) { // && 'public' != $scope + $temp['security'][] = $route->getLabel('sdk.security', $security[$platform]); + } - $temp['extensions'] = [ - 'weight' => $route->getOrder(), - 'cookies' => $route->getLabel('sdk.cookies', false), - 'type' => $route->getLabel('sdk.methodType', ''), - 'demo' => 'docs/examples/'.fromCamelCaseToDash($route->getLabel('sdk.namespace', 'default')).'/'.fromCamelCaseToDash($temp['operationId']).'.md', - 'edit' => 'https://github.com/appwrite/appwrite/edit/master' . $route->getLabel('sdk.description', ''), - 'rate-limit' => $route->getLabel('abuse-limit', 0), - 'rate-time' => $route->getLabel('abuse-time', 3600), - 'rate-key' => $route->getLabel('abuse-key', 'url:{url},ip:{ip}'), - 'scope' => $route->getLabel('scope', ''), - 'platforms' => $platformList, - ]; - } - - if ((!empty($scope))) { // && 'public' != $scope - $temp['security'][] = $route->getLabel('sdk.security', $security[$platform]); - } - - $requestBody = [ - 'content' => [ - 'application/x-www-form-urlencoded' => [ - 'schema' => [ - 'type' => 'object', - 'properties' => [], - ], - 'required' => [], + $requestBody = [ + 'content' => [ + 'application/x-www-form-urlencoded' => [ + 'schema' => [ + 'type' => 'object', + 'properties' => [], ], + 'required' => [], ], + ], + ]; + + foreach ($route->getParams() as $name => $param) { + $validator = (\is_callable($param['validator'])) ? call_user_func_array($param['validator'], $utopia->getResources($param['resources'])) : $param['validator']; /* @var $validator \Utopia\Validator */ + + $node = [ + 'name' => $name, + 'description' => $param['description'], + 'required' => !$param['optional'], ]; - foreach ($route->getParams() as $name => $param) { - $validator = (\is_callable($param['validator'])) ? $param['validator']() : $param['validator']; /* @var $validator \Utopia\Validator */ - - $node = [ - 'name' => $name, - 'description' => $param['description'], - 'required' => !$param['optional'], - ]; - - switch ((!empty($validator)) ? \get_class($validator) : '') { - case 'Utopia\Validator\Text': - $node['type'] = 'string'; - $node['x-example'] = '['.\strtoupper(fromCamelCase($node['name'])).']'; - break; - case 'Utopia\Validator\Boolean': - $node['type'] = 'boolean'; - $node['x-example'] = false; - break; - case 'Appwrite\Database\Validator\UID': - $node['type'] = 'string'; - $node['x-example'] = '['.\strtoupper(fromCamelCase($node['name'])).']'; - break; - case 'Utopia\Validator\Email': - $node['type'] = 'string'; - $node['format'] = 'email'; - $node['x-example'] = 'email@example.com'; - break; - case 'Utopia\Validator\URL': - $node['type'] = 'string'; - $node['format'] = 'url'; - $node['x-example'] = 'https://example.com'; - break; - case 'Utopia\Validator\JSON': - case 'Utopia\Validator\Mock': - case 'Utopia\Validator\Assoc': - $node['type'] = 'object'; - $node['type'] = 'object'; - $node['x-example'] = '{}'; - //$node['format'] = 'json'; - break; - case 'Appwrite\Storage\Validator\File': - $consumes = ['multipart/form-data']; - $node['type'] = 'file'; - break; - case 'Utopia\Validator\ArrayList': - $node['type'] = 'array'; - $node['collectionFormat'] = 'multi'; - $node['items'] = [ - 'type' => 'string', - ]; - break; - case 'Appwrite\Auth\Validator\Password': - $node['type'] = 'string'; - $node['format'] = 'format'; - $node['x-example'] = 'password'; - break; - case 'Utopia\Validator\Range': /* @var $validator \Utopia\Validator\Range */ - $node['type'] = 'integer'; - $node['format'] = 'int32'; - $node['x-example'] = $validator->getMin(); - break; - case 'Utopia\Validator\Numeric': - $node['type'] = 'integer'; - $node['format'] = 'int32'; - break; - case 'Utopia\Validator\Length': - $node['type'] = 'string'; - break; - case 'Utopia\Validator\Host': - $node['type'] = 'string'; - $node['format'] = 'url'; - $node['x-example'] = 'https://example.com'; - break; - case 'Utopia\Validator\WhiteList': /* @var $validator \Utopia\Validator\WhiteList */ - $node['type'] = 'string'; - $node['x-example'] = $validator->getList()[0]; - break; - default: - $node['type'] = 'string'; - break; - } - - if ($param['optional'] && !\is_null($param['default'])) { // Param has default value - $node['default'] = $param['default']; - } - - if (false !== \strpos($url, ':'.$name)) { // Param is in URL path - $node['in'] = 'path'; - $temp['parameters'][] = $node; - } elseif ($key == 'GET') { // Param is in query - $node['in'] = 'query'; - $temp['parameters'][] = $node; - } else { // Param is in payload - $node['in'] = 'formData'; - $temp['parameters'][] = $node; - $requestBody['content']['application/x-www-form-urlencoded']['schema']['properties'][] = $node; - - if (!$param['optional']) { - $requestBody['content']['application/x-www-form-urlencoded']['required'][] = $name; - } - } - - $url = \str_replace(':'.$name, '{'.$name.'}', $url); + switch ((!empty($validator)) ? \get_class($validator) : '') { + case 'Utopia\Validator\Text': + $node['type'] = 'string'; + $node['x-example'] = '['.\strtoupper(fromCamelCase($node['name'])).']'; + break; + case 'Utopia\Validator\Boolean': + $node['type'] = 'boolean'; + $node['x-example'] = false; + break; + case 'Appwrite\Database\Validator\UID': + $node['type'] = 'string'; + $node['x-example'] = '['.\strtoupper(fromCamelCase($node['name'])).']'; + break; + case 'Utopia\Validator\Email': + $node['type'] = 'string'; + $node['format'] = 'email'; + $node['x-example'] = 'email@example.com'; + break; + case 'Utopia\Validator\URL': + $node['type'] = 'string'; + $node['format'] = 'url'; + $node['x-example'] = 'https://example.com'; + break; + case 'Utopia\Validator\JSON': + case 'Utopia\Validator\Mock': + case 'Utopia\Validator\Assoc': + $node['type'] = 'object'; + $node['type'] = 'object'; + $node['x-example'] = '{}'; + //$node['format'] = 'json'; + break; + case 'Appwrite\Storage\Validator\File': + $consumes = ['multipart/form-data']; + $node['type'] = 'file'; + break; + case 'Utopia\Validator\ArrayList': + $node['type'] = 'array'; + $node['collectionFormat'] = 'multi'; + $node['items'] = [ + 'type' => 'string', + ]; + break; + case 'Appwrite\Auth\Validator\Password': + $node['type'] = 'string'; + $node['format'] = 'format'; + $node['x-example'] = 'password'; + break; + case 'Utopia\Validator\Range': /* @var $validator \Utopia\Validator\Range */ + $node['type'] = 'integer'; + $node['format'] = 'int32'; + $node['x-example'] = $validator->getMin(); + break; + case 'Utopia\Validator\Numeric': + $node['type'] = 'integer'; + $node['format'] = 'int32'; + break; + case 'Utopia\Validator\Length': + $node['type'] = 'string'; + break; + case 'Utopia\Validator\Host': + $node['type'] = 'string'; + $node['format'] = 'url'; + $node['x-example'] = 'https://example.com'; + break; + case 'Utopia\Validator\WhiteList': /* @var $validator \Utopia\Validator\WhiteList */ + $node['type'] = 'string'; + $node['x-example'] = $validator->getList()[0]; + break; + default: + $node['type'] = 'string'; + break; } - $temp['consumes'] = $consumes; + if ($param['optional'] && !\is_null($param['default'])) { // Param has default value + $node['default'] = $param['default']; + } - $output['paths'][$url][\strtolower($route->getMethod())] = $temp; + if (false !== \strpos($url, ':'.$name)) { // Param is in URL path + $node['in'] = 'path'; + $temp['parameters'][] = $node; + } elseif ($key == 'GET') { // Param is in query + $node['in'] = 'query'; + $temp['parameters'][] = $node; + } else { // Param is in payload + $node['in'] = 'formData'; + $temp['parameters'][] = $node; + $requestBody['content']['application/x-www-form-urlencoded']['schema']['properties'][] = $node; + + if (!$param['optional']) { + $requestBody['content']['application/x-www-form-urlencoded']['required'][] = $name; + } + } + + $url = \str_replace(':'.$name, '{'.$name.'}', $url); } + + $temp['consumes'] = $consumes; + + $output['paths'][$url][\strtolower($route->getMethod())] = $temp; } - - /*foreach ($consoleDB->getMocks() as $mock) { - var_dump($mock['name']); - }*/ - - \ksort($output['paths']); - - $response - ->json($output); } - ); \ No newline at end of file + + /*foreach ($consoleDB->getMocks() as $mock) { + var_dump($mock['name']); + }*/ + + \ksort($output['paths']); + + $response + ->json($output); + }, ['utopia', 'response']); \ No newline at end of file diff --git a/app/init.php b/app/init.php index 5683a5d127..d02ded1951 100644 --- a/app/init.php +++ b/app/init.php @@ -222,7 +222,7 @@ Locale::setLanguage('zh-tw', include __DIR__.'/config/locale/translations/zh-tw. 'http' => [ 'method' => 'GET', 'user_agent' => \sprintf(APP_USERAGENT, - Config::getParam('version'), + App::getEnv('_APP_VERSION', 'UNKNOWN'), App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)), 'timeout' => 2, ], diff --git a/app/workers/tasks.php b/app/workers/tasks.php index 625bcfed6f..4d65605bc7 100644 --- a/app/workers/tasks.php +++ b/app/workers/tasks.php @@ -96,7 +96,7 @@ class TasksV1 \curl_setopt($ch, CURLOPT_HEADER, 0); \curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); \curl_setopt($ch, CURLOPT_USERAGENT, \sprintf(APP_USERAGENT, - Config::getParam('version'), + App::getEnv('_APP_VERSION', 'UNKNOWN'), App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY) )); \curl_setopt( diff --git a/app/workers/usage.php b/app/workers/usage.php index 0d0811c33f..a53b261137 100644 --- a/app/workers/usage.php +++ b/app/workers/usage.php @@ -1,6 +1,6 @@ get('statsd', true); - $tags = ",project={$projectId},version=".Config::getParam('version').''; + $tags = ",project={$projectId},version=".App::getEnv('_APP_VERSION', 'UNKNOWN').''; // the global namespace is prepended to every key (optional) $statsd->setNamespace('appwrite.usage'); diff --git a/app/workers/webhooks.php b/app/workers/webhooks.php index ab861dfd46..f07818724f 100644 --- a/app/workers/webhooks.php +++ b/app/workers/webhooks.php @@ -6,7 +6,6 @@ require_once __DIR__.'/../init.php'; echo APP_NAME.' webhooks worker v1 has started'; -use Utopia\Config\Config; use Appwrite\Database\Database; use Appwrite\Database\Validator\Authorization; use Utopia\App; @@ -61,7 +60,7 @@ class WebhooksV1 \curl_setopt($ch, CURLOPT_HEADER, 0); \curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); \curl_setopt($ch, CURLOPT_USERAGENT, \sprintf(APP_USERAGENT, - Config::getParam('version'), + App::getEnv('_APP_VERSION', 'UNKNOWN'), App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY) )); \curl_setopt( diff --git a/composer.json b/composer.json index b0eec296e0..1608977bbd 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "appwrite/php-clamav": "1.0.*", - "utopia-php/framework": "0.7.1", + "utopia-php/framework": "0.7.2", "utopia-php/abuse": "0.2.*", "utopia-php/audit": "0.3.*", "utopia-php/cache": "0.2.*", diff --git a/composer.lock b/composer.lock index 2e2ad49cf8..c94e5ea63b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b8ee06f97c395bc83a05f92939679724", + "content-hash": "2693761ec4a5bb1305ac226bffd1555d", "packages": [ { "name": "appwrite/php-clamav", @@ -1596,16 +1596,16 @@ }, { "name": "utopia-php/framework", - "version": "0.7.1", + "version": "0.7.2", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "3810789c1caf16a9ad7811fd38067a35249e75f8" + "reference": "e592b7bdea5eeb48a3bf7ae18bc6d3e622e54cf3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/3810789c1caf16a9ad7811fd38067a35249e75f8", - "reference": "3810789c1caf16a9ad7811fd38067a35249e75f8", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/e592b7bdea5eeb48a3bf7ae18bc6d3e622e54cf3", + "reference": "e592b7bdea5eeb48a3bf7ae18bc6d3e622e54cf3", "shasum": "" }, "require": { @@ -1636,20 +1636,20 @@ "php", "upf" ], - "time": "2020-06-29T16:02:35+00:00" + "time": "2020-06-30T09:43:41+00:00" }, { "name": "utopia-php/locale", - "version": "0.3.0", + "version": "0.3.2", "source": { "type": "git", "url": "https://github.com/utopia-php/locale.git", - "reference": "32c32a3bf5c295f3de93569cead7f412fa29ad13" + "reference": "89c488fbff65fc87c048786c3d76b6003fbaa833" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/locale/zipball/32c32a3bf5c295f3de93569cead7f412fa29ad13", - "reference": "32c32a3bf5c295f3de93569cead7f412fa29ad13", + "url": "https://api.github.com/repos/utopia-php/locale/zipball/89c488fbff65fc87c048786c3d76b6003fbaa833", + "reference": "89c488fbff65fc87c048786c3d76b6003fbaa833", "shasum": "" }, "require": { @@ -1682,7 +1682,7 @@ "upf", "utopia" ], - "time": "2020-06-29T12:39:35+00:00" + "time": "2020-06-29T20:53:16+00:00" }, { "name": "utopia-php/registry", From 218bbdcad865851fc5795b1e933c7691632b03bf Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 30 Jun 2020 18:46:42 +0300 Subject: [PATCH 11/17] Updated controllers --- app/controllers/api/database.php | 2 +- app/controllers/api/projects.php | 1981 +++++++++++++++--------------- app/controllers/web/console.php | 2 +- public/index.php | 2 +- 4 files changed, 1010 insertions(+), 977 deletions(-) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index b3fcfc3c73..235bb18ccc 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -229,7 +229,7 @@ App::put('/v1/database/collections/:collectionId') ->param('name', null, function () { return new Text(256); }, 'Collection name.') ->param('read', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions(/docs/permissions) and get a full list of available permissions.') ->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.') - ->param('rules', [], function () use ($projectDB) { return new ArrayList(new Collection($projectDB, [Database::SYSTEM_COLLECTION_RULES], ['$collection' => Database::SYSTEM_COLLECTION_RULES, '$permissions' => ['read' => [], 'write' => []]])); }, 'Array of [rule objects](/docs/rules). Each rule define a collection field name, data type and validation.', true) + ->param('rules', [], function ($projectDB) { return new ArrayList(new Collection($projectDB, [Database::SYSTEM_COLLECTION_RULES], ['$collection' => Database::SYSTEM_COLLECTION_RULES, '$permissions' => ['read' => [], 'write' => []]])); }, 'Array of [rule objects](/docs/rules). Each rule define a collection field name, data type and validation.', true, ['projectDB']) ->action(function ($collectionId, $name, $read, $write, $rules, $response, $projectDB, $webhook, $audit) { /** @var Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 6068984ea4..fce99548b6 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -37,51 +37,53 @@ App::post('/v1/projects') ->param('legalCity', '', function () { return new Text(256); }, 'Project legal City.', true) ->param('legalAddress', '', function () { return new Text(256); }, 'Project legal Address.', true) ->param('legalTaxId', '', function () { return new Text(256); }, 'Project legal Tax ID.', true) - ->action( - function ($name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId) use ($response, $consoleDB, $projectDB) { - $team = $projectDB->getDocument($teamId); + ->action(function ($name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $consoleDB, $projectDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ + /** @var Appwrite\Database\Database $projectDB */ - if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { - throw new Exception('Team not found', 404); - } + $team = $projectDB->getDocument($teamId); - $project = $consoleDB->createDocument( - [ - '$collection' => Database::SYSTEM_COLLECTION_PROJECTS, - '$permissions' => [ - 'read' => ['team:'.$teamId], - 'write' => ['team:'.$teamId.'/owner', 'team:'.$teamId.'/developer'], - ], - 'name' => $name, - 'description' => $description, - 'logo' => $logo, - 'url' => $url, - 'legalName' => $legalName, - 'legalCountry' => $legalCountry, - 'legalState' => $legalState, - 'legalCity' => $legalCity, - 'legalAddress' => $legalAddress, - 'legalTaxId' => $legalTaxId, - 'teamId' => $team->getId(), - 'platforms' => [], - 'webhooks' => [], - 'keys' => [], - 'tasks' => [], - ] - ); - - if (false === $project) { - throw new Exception('Failed saving project to DB', 500); - } - - $consoleDB->createNamespace($project->getId()); - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($project->getArrayCopy()) - ; + if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { + throw new Exception('Team not found', 404); } - ); + + $project = $consoleDB->createDocument( + [ + '$collection' => Database::SYSTEM_COLLECTION_PROJECTS, + '$permissions' => [ + 'read' => ['team:'.$teamId], + 'write' => ['team:'.$teamId.'/owner', 'team:'.$teamId.'/developer'], + ], + 'name' => $name, + 'description' => $description, + 'logo' => $logo, + 'url' => $url, + 'legalName' => $legalName, + 'legalCountry' => $legalCountry, + 'legalState' => $legalState, + 'legalCity' => $legalCity, + 'legalAddress' => $legalAddress, + 'legalTaxId' => $legalTaxId, + 'teamId' => $team->getId(), + 'platforms' => [], + 'webhooks' => [], + 'keys' => [], + 'tasks' => [], + ] + ); + + if (false === $project) { + throw new Exception('Failed saving project to DB', 500); + } + + $consoleDB->createNamespace($project->getId()); + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->json($project->getArrayCopy()) + ; + }, ['response', 'consoleDB', 'projectDB']); App::get('/v1/projects') ->desc('List Projects') @@ -89,49 +91,22 @@ App::get('/v1/projects') ->label('scope', 'projects.read') ->label('sdk.namespace', 'projects') ->label('sdk.method', 'list') - ->action( - function () use ($response, $consoleDB) { - $results = $consoleDB->getCollection([ - 'limit' => 20, - 'offset' => 0, - 'orderField' => 'name', - 'orderType' => 'ASC', - 'orderCast' => 'string', - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_PROJECTS, - ], - ]); - - foreach ($results as $project) { - foreach (Config::getParam('providers') as $provider => $node) { - $secret = \json_decode($project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}'), true); - - if (!empty($secret) && isset($secret['version'])) { - $key = App::getEnv('_APP_OPENSSL_KEY_V'.$secret['version']); - $project->setAttribute('usersOauth2'.\ucfirst($provider).'Secret', OpenSSL::decrypt($secret['data'], $secret['method'], $key, 0, \hex2bin($secret['iv']), \hex2bin($secret['tag']))); - } - } - } - - $response->json($results); - } - ); - -App::get('/v1/projects/:projectId') - ->desc('Get Project') - ->groups(['api', 'projects']) - ->label('scope', 'projects.read') - ->label('sdk.namespace', 'projects') - ->label('sdk.method', 'get') - ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.') - ->action( - function ($projectId) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); - - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + ->action(function ($response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ + + $results = $consoleDB->getCollection([ + 'limit' => 20, + 'offset' => 0, + 'orderField' => 'name', + 'orderType' => 'ASC', + 'orderCast' => 'string', + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_PROJECTS, + ], + ]); + foreach ($results as $project) { foreach (Config::getParam('providers') as $provider => $node) { $secret = \json_decode($project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}'), true); @@ -140,10 +115,39 @@ App::get('/v1/projects/:projectId') $project->setAttribute('usersOauth2'.\ucfirst($provider).'Secret', OpenSSL::decrypt($secret['data'], $secret['method'], $key, 0, \hex2bin($secret['iv']), \hex2bin($secret['tag']))); } } - - $response->json($project->getArrayCopy()); } - ); + + $response->json($results); + }, ['response', 'consoleDB']); + +App::get('/v1/projects/:projectId') + ->desc('Get Project') + ->groups(['api', 'projects']) + ->label('scope', 'projects.read') + ->label('sdk.namespace', 'projects') + ->label('sdk.method', 'get') + ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.') + ->action(function ($projectId, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ + + $project = $consoleDB->getDocument($projectId); + + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); + } + + foreach (Config::getParam('providers') as $provider => $node) { + $secret = \json_decode($project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}'), true); + + if (!empty($secret) && isset($secret['version'])) { + $key = App::getEnv('_APP_OPENSSL_KEY_V'.$secret['version']); + $project->setAttribute('usersOauth2'.\ucfirst($provider).'Secret', OpenSSL::decrypt($secret['data'], $secret['method'], $key, 0, \hex2bin($secret['iv']), \hex2bin($secret['tag']))); + } + } + + $response->json($project->getArrayCopy()); + }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/usage') ->desc('Get Project') @@ -153,159 +157,162 @@ App::get('/v1/projects/:projectId/usage') ->label('sdk.method', 'getUsage') ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.') ->param('range', 'last30', function () { return new WhiteList(['daily', 'monthly', 'last30', 'last90']); }, 'Date range.', true) - ->action( - function ($projectId, $range) use ($response, $consoleDB, $projectDB, $register) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $range, $response, $consoleDB, $projectDB, $register) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Registry\Registry $register */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); + $project = $consoleDB->getDocument($projectId); + + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); + } + + $period = [ + 'daily' => [ + 'start' => DateTime::createFromFormat('U', \strtotime('today')), + 'end' => DateTime::createFromFormat('U', \strtotime('tomorrow')), + 'group' => '1m', + ], + 'monthly' => [ + 'start' => DateTime::createFromFormat('U', \strtotime('midnight first day of this month')), + 'end' => DateTime::createFromFormat('U', \strtotime('midnight last day of this month')), + 'group' => '1d', + ], + 'last30' => [ + 'start' => DateTime::createFromFormat('U', \strtotime('-30 days')), + 'end' => DateTime::createFromFormat('U', \strtotime('tomorrow')), + 'group' => '1d', + ], + 'last90' => [ + 'start' => DateTime::createFromFormat('U', \strtotime('-90 days')), + 'end' => DateTime::createFromFormat('U', \strtotime('today')), + 'group' => '1d', + ], + // 'yearly' => [ + // 'start' => DateTime::createFromFormat('U', strtotime('midnight first day of january')), + // 'end' => DateTime::createFromFormat('U', strtotime('midnight last day of december')), + // 'group' => '4w', + // ], + ]; + + $client = $register->get('influxdb'); + + $requests = []; + $network = []; + + if ($client) { + $start = $period[$range]['start']->format(DateTime::RFC3339); + $end = $period[$range]['end']->format(DateTime::RFC3339); + $database = $client->selectDB('telegraf'); + + // Requests + $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_requests_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)'); + $points = $result->getPoints(); + + foreach ($points as $point) { + $requests[] = [ + 'value' => (!empty($point['value'])) ? $point['value'] : 0, + 'date' => \strtotime($point['time']), + ]; } - $period = [ - 'daily' => [ - 'start' => DateTime::createFromFormat('U', \strtotime('today')), - 'end' => DateTime::createFromFormat('U', \strtotime('tomorrow')), - 'group' => '1m', - ], - 'monthly' => [ - 'start' => DateTime::createFromFormat('U', \strtotime('midnight first day of this month')), - 'end' => DateTime::createFromFormat('U', \strtotime('midnight last day of this month')), - 'group' => '1d', - ], - 'last30' => [ - 'start' => DateTime::createFromFormat('U', \strtotime('-30 days')), - 'end' => DateTime::createFromFormat('U', \strtotime('tomorrow')), - 'group' => '1d', - ], - 'last90' => [ - 'start' => DateTime::createFromFormat('U', \strtotime('-90 days')), - 'end' => DateTime::createFromFormat('U', \strtotime('today')), - 'group' => '1d', - ], - // 'yearly' => [ - // 'start' => DateTime::createFromFormat('U', strtotime('midnight first day of january')), - // 'end' => DateTime::createFromFormat('U', strtotime('midnight last day of december')), - // 'group' => '4w', - // ], - ]; + // Network + $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_network_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)'); + $points = $result->getPoints(); - $client = $register->get('influxdb'); - - $requests = []; - $network = []; - - if ($client) { - $start = $period[$range]['start']->format(DateTime::RFC3339); - $end = $period[$range]['end']->format(DateTime::RFC3339); - $database = $client->selectDB('telegraf'); - - // Requests - $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_requests_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)'); - $points = $result->getPoints(); - - foreach ($points as $point) { - $requests[] = [ - 'value' => (!empty($point['value'])) ? $point['value'] : 0, - 'date' => \strtotime($point['time']), - ]; - } - - // Network - $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_network_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)'); - $points = $result->getPoints(); - - foreach ($points as $point) { - $network[] = [ - 'value' => (!empty($point['value'])) ? $point['value'] : 0, - 'date' => \strtotime($point['time']), - ]; - } + foreach ($points as $point) { + $network[] = [ + 'value' => (!empty($point['value'])) ? $point['value'] : 0, + 'date' => \strtotime($point['time']), + ]; } + } - // Users + // Users - $projectDB->getCollection([ + $projectDB->getCollection([ + 'limit' => 0, + 'offset' => 0, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_USERS, + ], + ]); + + $usersTotal = $projectDB->getSum(); + + // Documents + + $collections = $projectDB->getCollection([ + 'limit' => 100, + 'offset' => 0, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_COLLECTIONS, + ], + ]); + + $collectionsTotal = $projectDB->getSum(); + + $documents = []; + + foreach ($collections as $collection) { + $result = $projectDB->getCollection([ 'limit' => 0, 'offset' => 0, 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, + '$collection='.$collection['$id'], ], ]); - $usersTotal = $projectDB->getSum(); - - // Documents - - $collections = $projectDB->getCollection([ - 'limit' => 100, - 'offset' => 0, - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_COLLECTIONS, - ], - ]); - - $collectionsTotal = $projectDB->getSum(); - - $documents = []; - - foreach ($collections as $collection) { - $result = $projectDB->getCollection([ - 'limit' => 0, - 'offset' => 0, - 'filters' => [ - '$collection='.$collection['$id'], - ], - ]); - - $documents[] = ['name' => $collection['name'], 'total' => $projectDB->getSum()]; - } - - // Tasks - $tasksTotal = \count($project->getAttribute('tasks', [])); - - $response->json([ - 'requests' => [ - 'data' => $requests, - 'total' => \array_sum(\array_map(function ($item) { - return $item['value']; - }, $requests)), - ], - 'network' => [ - 'data' => $network, - 'total' => \array_sum(\array_map(function ($item) { - return $item['value']; - }, $network)), - ], - 'collections' => [ - 'data' => $collections, - 'total' => $collectionsTotal, - ], - 'documents' => [ - 'data' => $documents, - 'total' => \array_sum(\array_map(function ($item) { - return $item['total']; - }, $documents)), - ], - 'users' => [ - 'data' => [], - 'total' => $usersTotal, - ], - 'tasks' => [ - 'data' => [], - 'total' => $tasksTotal, - ], - 'storage' => [ - 'total' => $projectDB->getCount( - [ - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_FILES, - ], - ] - ), - ], - ]); + $documents[] = ['name' => $collection['name'], 'total' => $projectDB->getSum()]; } - ); + + // Tasks + $tasksTotal = \count($project->getAttribute('tasks', [])); + + $response->json([ + 'requests' => [ + 'data' => $requests, + 'total' => \array_sum(\array_map(function ($item) { + return $item['value']; + }, $requests)), + ], + 'network' => [ + 'data' => $network, + 'total' => \array_sum(\array_map(function ($item) { + return $item['value']; + }, $network)), + ], + 'collections' => [ + 'data' => $collections, + 'total' => $collectionsTotal, + ], + 'documents' => [ + 'data' => $documents, + 'total' => \array_sum(\array_map(function ($item) { + return $item['total']; + }, $documents)), + ], + 'users' => [ + 'data' => [], + 'total' => $usersTotal, + ], + 'tasks' => [ + 'data' => [], + 'total' => $tasksTotal, + ], + 'storage' => [ + 'total' => $projectDB->getCount( + [ + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_FILES, + ], + ] + ), + ], + ]); + }, ['response', 'consoleDB', 'projectDB', 'register']); App::patch('/v1/projects/:projectId') ->desc('Update Project') @@ -324,34 +331,35 @@ App::patch('/v1/projects/:projectId') ->param('legalCity', '', function () { return new Text(256); }, 'Project legal city.', true) ->param('legalAddress', '', function () { return new Text(256); }, 'Project legal address.', true) ->param('legalTaxId', '', function () { return new Text(256); }, 'Project legal tax ID.', true) - ->action( - function ($projectId, $name, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $name, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $project = $consoleDB->updateDocument(\array_merge($project->getArrayCopy(), [ - 'name' => $name, - 'description' => $description, - 'logo' => $logo, - 'url' => $url, - 'legalName' => $legalName, - 'legalCountry' => $legalCountry, - 'legalState' => $legalState, - 'legalCity' => $legalCity, - 'legalAddress' => $legalAddress, - 'legalTaxId' => $legalTaxId, - ])); - - if (false === $project) { - throw new Exception('Failed saving project to DB', 500); - } - - $response->json($project->getArrayCopy()); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $project = $consoleDB->updateDocument(\array_merge($project->getArrayCopy(), [ + 'name' => $name, + 'description' => $description, + 'logo' => $logo, + 'url' => $url, + 'legalName' => $legalName, + 'legalCountry' => $legalCountry, + 'legalState' => $legalState, + 'legalCity' => $legalCity, + 'legalAddress' => $legalAddress, + 'legalTaxId' => $legalTaxId, + ])); + + if (false === $project) { + throw new Exception('Failed saving project to DB', 500); + } + + $response->json($project->getArrayCopy()); + }, ['response', 'consoleDB']); App::patch('/v1/projects/:projectId/oauth2') ->desc('Update Project OAuth2') @@ -363,37 +371,38 @@ App::patch('/v1/projects/:projectId/oauth2') ->param('provider', '', function () { return new WhiteList(\array_keys(Config::getParam('providers'))); }, 'Provider Name', false) ->param('appId', '', function () { return new Text(256); }, 'Provider app ID.', true) ->param('secret', '', function () { return new text(512); }, 'Provider secret key.', true) - ->action( - function ($projectId, $provider, $appId, $secret) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $provider, $appId, $secret, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $key = App::getEnv('_APP_OPENSSL_KEY_V1'); - $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); - $tag = null; - $secret = \json_encode([ - 'data' => OpenSSL::encrypt($secret, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag), - 'method' => OpenSSL::CIPHER_AES_128_GCM, - 'iv' => \bin2hex($iv), - 'tag' => \bin2hex($tag), - 'version' => '1', - ]); - - $project = $consoleDB->updateDocument(\array_merge($project->getArrayCopy(), [ - 'usersOauth2'.\ucfirst($provider).'Appid' => $appId, - 'usersOauth2'.\ucfirst($provider).'Secret' => $secret, - ])); - - if (false === $project) { - throw new Exception('Failed saving project to DB', 500); - } - - $response->json($project->getArrayCopy()); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $key = App::getEnv('_APP_OPENSSL_KEY_V1'); + $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); + $tag = null; + $secret = \json_encode([ + 'data' => OpenSSL::encrypt($secret, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag), + 'method' => OpenSSL::CIPHER_AES_128_GCM, + 'iv' => \bin2hex($iv), + 'tag' => \bin2hex($tag), + 'version' => '1', + ]); + + $project = $consoleDB->updateDocument(\array_merge($project->getArrayCopy(), [ + 'usersOauth2'.\ucfirst($provider).'Appid' => $appId, + 'usersOauth2'.\ucfirst($provider).'Secret' => $secret, + ])); + + if (false === $project) { + throw new Exception('Failed saving project to DB', 500); + } + + $response->json($project->getArrayCopy()); + }, ['response', 'consoleDB']); App::delete('/v1/projects/:projectId') ->desc('Delete Project') @@ -403,41 +412,44 @@ App::delete('/v1/projects/:projectId') ->label('sdk.method', 'delete') ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.') ->param('password', '', function () { return new UID(); }, 'Your user password for confirmation. Must be between 6 to 32 chars.') - ->action( - function ($projectId, $password) use ($response, $consoleDB, $user, $deletes) { - if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password - throw new Exception('Invalid credentials', 401); - } + ->action(function ($projectId, $password, $response, $user, $consoleDB, $deletes) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Document $user */ + /** @var Appwrite\Database\Database $consoleDB */ + /** @var Appwrite\Event\Event $deletes */ - $project = $consoleDB->getDocument($projectId); + if (!Auth::passwordVerify($password, $user->getAttribute('password'))) { // Double check user password + throw new Exception('Invalid credentials', 401); + } - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $deletes->setParam('document', $project->getArrayCopy()); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); + } - foreach (['keys', 'webhooks', 'tasks', 'platforms', 'domains'] as $key) { // Delete all children (keys, webhooks, tasks [stop tasks?], platforms) - $list = $project->getAttribute('webhooks', []); + $deletes->setParam('document', $project->getArrayCopy()); - foreach ($list as $document) { /* @var $document Document */ - if (!$consoleDB->deleteDocument($projectId)) { - throw new Exception('Failed to remove project document ('.$key.')] from DB', 500); - } + foreach (['keys', 'webhooks', 'tasks', 'platforms', 'domains'] as $key) { // Delete all children (keys, webhooks, tasks [stop tasks?], platforms) + $list = $project->getAttribute('webhooks', []); + + foreach ($list as $document) { /* @var $document Document */ + if (!$consoleDB->deleteDocument($projectId)) { + throw new Exception('Failed to remove project document ('.$key.')] from DB', 500); } } - - if (!$consoleDB->deleteDocument($project->getAttribute('teamId', null))) { - throw new Exception('Failed to remove project team from DB', 500); - } - - if (!$consoleDB->deleteDocument($projectId)) { - throw new Exception('Failed to remove project from DB', 500); - } - - $response->noContent(); } - ); + + if (!$consoleDB->deleteDocument($project->getAttribute('teamId', null))) { + throw new Exception('Failed to remove project team from DB', 500); + } + + if (!$consoleDB->deleteDocument($projectId)) { + throw new Exception('Failed to remove project from DB', 500); + } + + $response->noContent(); + }, ['response', 'user', 'consoleDB', 'deletes']); // Webhooks @@ -454,58 +466,59 @@ App::post('/v1/projects/:projectId/webhooks') ->param('security', false, function () { return new Boolean(true); }, 'Certificate verification, false for disabled or true for enabled.') ->param('httpUser', '', function () { return new Text(256); }, 'Webhook HTTP user.', true) ->param('httpPass', '', function () { return new Text(256); }, 'Webhook HTTP password.', true) - ->action( - function ($projectId, $name, $events, $url, $security, $httpUser, $httpPass) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $name, $events, $url, $security, $httpUser, $httpPass, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); - $key = App::getEnv('_APP_OPENSSL_KEY_V1'); - $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); - $tag = null; - $httpPass = \json_encode([ - 'data' => OpenSSL::encrypt($httpPass, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag), - 'method' => OpenSSL::CIPHER_AES_128_GCM, - 'iv' => \bin2hex($iv), - 'tag' => \bin2hex($tag), - 'version' => '1', - ]); - - $webhook = $consoleDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_WEBHOOKS, - '$permissions' => [ - 'read' => ['team:'.$project->getAttribute('teamId', null)], - 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'], - ], - 'name' => $name, - 'events' => $events, - 'url' => $url, - 'security' => (int) $security, - 'httpUser' => $httpUser, - 'httpPass' => $httpPass, - ]); - - if (false === $webhook) { - throw new Exception('Failed saving webhook to DB', 500); - } - - $project->setAttribute('webhooks', $webhook, Document::SET_TYPE_APPEND); - - $project = $consoleDB->updateDocument($project->getArrayCopy()); - - if (false === $project) { - throw new Exception('Failed saving project to DB', 500); - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($webhook->getArrayCopy()) - ; + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); + $key = App::getEnv('_APP_OPENSSL_KEY_V1'); + $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); + $tag = null; + $httpPass = \json_encode([ + 'data' => OpenSSL::encrypt($httpPass, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag), + 'method' => OpenSSL::CIPHER_AES_128_GCM, + 'iv' => \bin2hex($iv), + 'tag' => \bin2hex($tag), + 'version' => '1', + ]); + + $webhook = $consoleDB->createDocument([ + '$collection' => Database::SYSTEM_COLLECTION_WEBHOOKS, + '$permissions' => [ + 'read' => ['team:'.$project->getAttribute('teamId', null)], + 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'], + ], + 'name' => $name, + 'events' => $events, + 'url' => $url, + 'security' => (int) $security, + 'httpUser' => $httpUser, + 'httpPass' => $httpPass, + ]); + + if (false === $webhook) { + throw new Exception('Failed saving webhook to DB', 500); + } + + $project->setAttribute('webhooks', $webhook, Document::SET_TYPE_APPEND); + + $project = $consoleDB->updateDocument($project->getArrayCopy()); + + if (false === $project) { + throw new Exception('Failed saving project to DB', 500); + } + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->json($webhook->getArrayCopy()) + ; + }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/webhooks') ->desc('List Webhooks') @@ -514,31 +527,32 @@ App::get('/v1/projects/:projectId/webhooks') ->label('sdk.namespace', 'projects') ->label('sdk.method', 'listWebhooks') ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.') - ->action( - function ($projectId) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $webhooks = $project->getAttribute('webhooks', []); - - foreach ($webhooks as $webhook) { /* @var $webhook Document */ - $httpPass = \json_decode($webhook->getAttribute('httpPass', '{}'), true); - - if (empty($httpPass) || !isset($httpPass['version'])) { - continue; - } - - $key = App::getEnv('_APP_OPENSSL_KEY_V'.$httpPass['version']); - - $webhook->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag']))); - } - - $response->json($webhooks); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $webhooks = $project->getAttribute('webhooks', []); + + foreach ($webhooks as $webhook) { /* @var $webhook Document */ + $httpPass = \json_decode($webhook->getAttribute('httpPass', '{}'), true); + + if (empty($httpPass) || !isset($httpPass['version'])) { + continue; + } + + $key = App::getEnv('_APP_OPENSSL_KEY_V'.$httpPass['version']); + + $webhook->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag']))); + } + + $response->json($webhooks); + }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/webhooks/:webhookId') ->desc('Get Webhook') @@ -548,31 +562,31 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId') ->label('sdk.method', 'getWebhook') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('webhookId', null, function () { return new UID(); }, 'Webhook unique ID.') - ->action( - function ($projectId, $webhookId) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $webhookId, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $webhook = $project->search('$id', $webhookId, $project->getAttribute('webhooks', [])); - - if (empty($webhook) || !$webhook instanceof Document) { - throw new Exception('Webhook not found', 404); - } - - $httpPass = \json_decode($webhook->getAttribute('httpPass', '{}'), true); - - if (!empty($httpPass) && isset($httpPass['version'])) { - $key = App::getEnv('_APP_OPENSSL_KEY_V'.$httpPass['version']); - $webhook->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag']))); - } - - $response->json($webhook->getArrayCopy()); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + $webhook = $project->search('$id', $webhookId, $project->getAttribute('webhooks', [])); + + if (empty($webhook) || !$webhook instanceof Document) { + throw new Exception('Webhook not found', 404); + } + + $httpPass = \json_decode($webhook->getAttribute('httpPass', '{}'), true); + + if (!empty($httpPass) && isset($httpPass['version'])) { + $key = App::getEnv('_APP_OPENSSL_KEY_V'.$httpPass['version']); + $webhook->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag']))); + } + + $response->json($webhook->getArrayCopy()); + }, ['response', 'consoleDB']); App::put('/v1/projects/:projectId/webhooks/:webhookId') ->desc('Update Webhook') @@ -587,48 +601,49 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') ->param('url', null, function () { return new Text(2000); }, 'Webhook URL.') ->param('security', false, function () { return new Boolean(true); }, 'Certificate verification, false for disabled or true for enabled.') ->param('httpUser', '', function () { return new Text(256); }, 'Webhook HTTP user.', true) ->param('httpPass', '', function () { return new Text(256); }, 'Webhook HTTP password.', true) - ->action( - function ($projectId, $webhookId, $name, $events, $url, $security, $httpUser, $httpPass) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $webhookId, $name, $events, $url, $security, $httpUser, $httpPass, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); - $key = App::getEnv('_APP_OPENSSL_KEY_V1'); - $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); - $tag = null; - $httpPass = \json_encode([ - 'data' => OpenSSL::encrypt($httpPass, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag), - 'method' => OpenSSL::CIPHER_AES_128_GCM, - 'iv' => \bin2hex($iv), - 'tag' => \bin2hex($tag), - 'version' => '1', - ]); - - $webhook = $project->search('$id', $webhookId, $project->getAttribute('webhooks', [])); - - if (empty($webhook) || !$webhook instanceof Document) { - throw new Exception('Webhook not found', 404); - } - - $webhook - ->setAttribute('name', $name) - ->setAttribute('events', $events) - ->setAttribute('url', $url) - ->setAttribute('security', (int) $security) - ->setAttribute('httpUser', $httpUser) - ->setAttribute('httpPass', $httpPass) - ; - - if (false === $consoleDB->updateDocument($webhook->getArrayCopy())) { - throw new Exception('Failed saving webhook to DB', 500); - } - - $response->json($webhook->getArrayCopy()); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); + $key = App::getEnv('_APP_OPENSSL_KEY_V1'); + $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); + $tag = null; + $httpPass = \json_encode([ + 'data' => OpenSSL::encrypt($httpPass, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag), + 'method' => OpenSSL::CIPHER_AES_128_GCM, + 'iv' => \bin2hex($iv), + 'tag' => \bin2hex($tag), + 'version' => '1', + ]); + + $webhook = $project->search('$id', $webhookId, $project->getAttribute('webhooks', [])); + + if (empty($webhook) || !$webhook instanceof Document) { + throw new Exception('Webhook not found', 404); + } + + $webhook + ->setAttribute('name', $name) + ->setAttribute('events', $events) + ->setAttribute('url', $url) + ->setAttribute('security', (int) $security) + ->setAttribute('httpUser', $httpUser) + ->setAttribute('httpPass', $httpPass) + ; + + if (false === $consoleDB->updateDocument($webhook->getArrayCopy())) { + throw new Exception('Failed saving webhook to DB', 500); + } + + $response->json($webhook->getArrayCopy()); + }, ['response', 'consoleDB']); App::delete('/v1/projects/:projectId/webhooks/:webhookId') ->desc('Delete Webhook') @@ -638,27 +653,28 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId') ->label('sdk.method', 'deleteWebhook') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('webhookId', null, function () { return new UID(); }, 'Webhook unique ID.') - ->action( - function ($projectId, $webhookId) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $webhookId, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $webhook = $project->search('$id', $webhookId, $project->getAttribute('webhooks', [])); - - if (empty($webhook) || !$webhook instanceof Document) { - throw new Exception('Webhook not found', 404); - } - - if (!$consoleDB->deleteDocument($webhook->getId())) { - throw new Exception('Failed to remove webhook from DB', 500); - } - - $response->noContent(); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $webhook = $project->search('$id', $webhookId, $project->getAttribute('webhooks', [])); + + if (empty($webhook) || !$webhook instanceof Document) { + throw new Exception('Webhook not found', 404); + } + + if (!$consoleDB->deleteDocument($webhook->getId())) { + throw new Exception('Failed to remove webhook from DB', 500); + } + + $response->noContent(); + }, ['response', 'consoleDB']); // Keys @@ -671,43 +687,44 @@ App::post('/v1/projects/:projectId/keys') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('name', null, function () { return new Text(256); }, 'Key name.') ->param('scopes', null, function () { return new ArrayList(new WhiteList(Config::getParam('scopes'))); }, 'Key scopes list.') - ->action( - function ($projectId, $name, $scopes) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $name, $scopes, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $key = $consoleDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_KEYS, - '$permissions' => [ - 'read' => ['team:'.$project->getAttribute('teamId', null)], - 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'], - ], - 'name' => $name, - 'scopes' => $scopes, - 'secret' => \bin2hex(\random_bytes(128)), - ]); - - if (false === $key) { - throw new Exception('Failed saving key to DB', 500); - } - - $project->setAttribute('keys', $key, Document::SET_TYPE_APPEND); - - $project = $consoleDB->updateDocument($project->getArrayCopy()); - - if (false === $project) { - throw new Exception('Failed saving project to DB', 500); - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($key->getArrayCopy()) - ; + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $key = $consoleDB->createDocument([ + '$collection' => Database::SYSTEM_COLLECTION_KEYS, + '$permissions' => [ + 'read' => ['team:'.$project->getAttribute('teamId', null)], + 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'], + ], + 'name' => $name, + 'scopes' => $scopes, + 'secret' => \bin2hex(\random_bytes(128)), + ]); + + if (false === $key) { + throw new Exception('Failed saving key to DB', 500); + } + + $project->setAttribute('keys', $key, Document::SET_TYPE_APPEND); + + $project = $consoleDB->updateDocument($project->getArrayCopy()); + + if (false === $project) { + throw new Exception('Failed saving project to DB', 500); + } + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->json($key->getArrayCopy()) + ; + }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/keys') ->desc('List Keys') @@ -716,17 +733,18 @@ App::get('/v1/projects/:projectId/keys') ->label('sdk.namespace', 'projects') ->label('sdk.method', 'listKeys') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') - ->action( - function ($projectId) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ + + $project = $consoleDB->getDocument($projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } - - $response->json($project->getAttribute('keys', [])); //FIXME make sure array objects return correctly + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $response->json($project->getAttribute('keys', [])); //FIXME make sure array objects return correctly + }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/keys/:keyId') ->desc('Get Key') @@ -736,23 +754,21 @@ App::get('/v1/projects/:projectId/keys/:keyId') ->label('sdk.method', 'getKey') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('keyId', null, function () { return new UID(); }, 'Key unique ID.') - ->action( - function ($projectId, $keyId) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $keyId, $response, $consoleDB) { + $project = $consoleDB->getDocument($projectId); - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } - - $key = $project->search('$id', $keyId, $project->getAttribute('keys', [])); - - if (empty($key) || !$key instanceof Document) { - throw new Exception('Key not found', 404); - } - - $response->json($key->getArrayCopy()); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $key = $project->search('$id', $keyId, $project->getAttribute('keys', [])); + + if (empty($key) || !$key instanceof Document) { + throw new Exception('Key not found', 404); + } + + $response->json($key->getArrayCopy()); + }, ['response', 'consoleDB']); App::put('/v1/projects/:projectId/keys/:keyId') ->desc('Update Key') @@ -764,32 +780,33 @@ App::put('/v1/projects/:projectId/keys/:keyId') ->param('keyId', null, function () { return new UID(); }, 'Key unique ID.') ->param('name', null, function () { return new Text(256); }, 'Key name.') ->param('scopes', null, function () { return new ArrayList(new WhiteList(Config::getParam('scopes'))); }, 'Key scopes list') - ->action( - function ($projectId, $keyId, $name, $scopes) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $keyId, $name, $scopes, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $key = $project->search('$id', $keyId, $project->getAttribute('keys', [])); - - if (empty($key) || !$key instanceof Document) { - throw new Exception('Key not found', 404); - } - - $key - ->setAttribute('name', $name) - ->setAttribute('scopes', $scopes) - ; - - if (false === $consoleDB->updateDocument($key->getArrayCopy())) { - throw new Exception('Failed saving key to DB', 500); - } - - $response->json($key->getArrayCopy()); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $key = $project->search('$id', $keyId, $project->getAttribute('keys', [])); + + if (empty($key) || !$key instanceof Document) { + throw new Exception('Key not found', 404); + } + + $key + ->setAttribute('name', $name) + ->setAttribute('scopes', $scopes) + ; + + if (false === $consoleDB->updateDocument($key->getArrayCopy())) { + throw new Exception('Failed saving key to DB', 500); + } + + $response->json($key->getArrayCopy()); + }, ['response', 'consoleDB']); App::delete('/v1/projects/:projectId/keys/:keyId') ->desc('Delete Key') @@ -799,27 +816,28 @@ App::delete('/v1/projects/:projectId/keys/:keyId') ->label('sdk.method', 'deleteKey') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('keyId', null, function () { return new UID(); }, 'Key unique ID.') - ->action( - function ($projectId, $keyId) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $keyId, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $key = $project->search('$id', $keyId, $project->getAttribute('keys', [])); - - if (empty($key) || !$key instanceof Document) { - throw new Exception('Key not found', 404); - } - - if (!$consoleDB->deleteDocument($key->getId())) { - throw new Exception('Failed to remove key from DB', 500); - } - - $response->noContent(); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $key = $project->search('$id', $keyId, $project->getAttribute('keys', [])); + + if (empty($key) || !$key instanceof Document) { + throw new Exception('Key not found', 404); + } + + if (!$consoleDB->deleteDocument($key->getId())) { + throw new Exception('Failed to remove key from DB', 500); + } + + $response->noContent(); + }, ['response', 'consoleDB']); // Tasks @@ -838,73 +856,74 @@ App::post('/v1/projects/:projectId/tasks') ->param('httpHeaders', null, function () { return new ArrayList(new Text(256)); }, 'Task HTTP headers list.', true) ->param('httpUser', '', function () { return new Text(256); }, 'Task HTTP user.', true) ->param('httpPass', '', function () { return new Text(256); }, 'Task HTTP password.', true) - ->action( - function ($projectId, $name, $status, $schedule, $security, $httpMethod, $httpUrl, $httpHeaders, $httpUser, $httpPass) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $name, $status, $schedule, $security, $httpMethod, $httpUrl, $httpHeaders, $httpUser, $httpPass, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $cron = CronExpression::factory($schedule); - $next = ($status == 'play') ? $cron->getNextRunDate()->format('U') : null; - - $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); - $key = App::getEnv('_APP_OPENSSL_KEY_V1'); - $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); - $tag = null; - $httpPass = \json_encode([ - 'data' => OpenSSL::encrypt($httpPass, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag), - 'method' => OpenSSL::CIPHER_AES_128_GCM, - 'iv' => \bin2hex($iv), - 'tag' => \bin2hex($tag), - 'version' => '1', - ]); - - $task = $consoleDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_TASKS, - '$permissions' => [ - 'read' => ['team:'.$project->getAttribute('teamId', null)], - 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'], - ], - 'name' => $name, - 'status' => $status, - 'schedule' => $schedule, - 'updated' => \time(), - 'previous' => null, - 'next' => $next, - 'security' => (int) $security, - 'httpMethod' => $httpMethod, - 'httpUrl' => $httpUrl, - 'httpHeaders' => $httpHeaders, - 'httpUser' => $httpUser, - 'httpPass' => $httpPass, - 'log' => '{}', - 'failures' => 0, - ]); - - if (false === $task) { - throw new Exception('Failed saving tasks to DB', 500); - } - - $project->setAttribute('tasks', $task, Document::SET_TYPE_APPEND); - - $project = $consoleDB->updateDocument($project->getArrayCopy()); - - if (false === $project) { - throw new Exception('Failed saving project to DB', 500); - } - - if ($next) { - ResqueScheduler::enqueueAt($next, 'v1-tasks', 'TasksV1', $task->getArrayCopy()); - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($task->getArrayCopy()) - ; + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $cron = CronExpression::factory($schedule); + $next = ($status == 'play') ? $cron->getNextRunDate()->format('U') : null; + + $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); + $key = App::getEnv('_APP_OPENSSL_KEY_V1'); + $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); + $tag = null; + $httpPass = \json_encode([ + 'data' => OpenSSL::encrypt($httpPass, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag), + 'method' => OpenSSL::CIPHER_AES_128_GCM, + 'iv' => \bin2hex($iv), + 'tag' => \bin2hex($tag), + 'version' => '1', + ]); + + $task = $consoleDB->createDocument([ + '$collection' => Database::SYSTEM_COLLECTION_TASKS, + '$permissions' => [ + 'read' => ['team:'.$project->getAttribute('teamId', null)], + 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'], + ], + 'name' => $name, + 'status' => $status, + 'schedule' => $schedule, + 'updated' => \time(), + 'previous' => null, + 'next' => $next, + 'security' => (int) $security, + 'httpMethod' => $httpMethod, + 'httpUrl' => $httpUrl, + 'httpHeaders' => $httpHeaders, + 'httpUser' => $httpUser, + 'httpPass' => $httpPass, + 'log' => '{}', + 'failures' => 0, + ]); + + if (false === $task) { + throw new Exception('Failed saving tasks to DB', 500); + } + + $project->setAttribute('tasks', $task, Document::SET_TYPE_APPEND); + + $project = $consoleDB->updateDocument($project->getArrayCopy()); + + if (false === $project) { + throw new Exception('Failed saving project to DB', 500); + } + + if ($next) { + ResqueScheduler::enqueueAt($next, 'v1-tasks', 'TasksV1', $task->getArrayCopy()); + } + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->json($task->getArrayCopy()) + ; + }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/tasks') ->desc('List Tasks') @@ -913,31 +932,32 @@ App::get('/v1/projects/:projectId/tasks') ->label('sdk.namespace', 'projects') ->label('sdk.method', 'listTasks') ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.') - ->action( - function ($projectId) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $tasks = $project->getAttribute('tasks', []); - - foreach ($tasks as $task) { /* @var $task Document */ - $httpPass = \json_decode($task->getAttribute('httpPass', '{}'), true); - - if (empty($httpPass) || !isset($httpPass['version'])) { - continue; - } - - $key = App::getEnv('_APP_OPENSSL_KEY_V'.$httpPass['version']); - - $task->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag']))); - } - - $response->json($tasks); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $tasks = $project->getAttribute('tasks', []); + + foreach ($tasks as $task) { /* @var $task Document */ + $httpPass = \json_decode($task->getAttribute('httpPass', '{}'), true); + + if (empty($httpPass) || !isset($httpPass['version'])) { + continue; + } + + $key = App::getEnv('_APP_OPENSSL_KEY_V'.$httpPass['version']); + + $task->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag']))); + } + + $response->json($tasks); + }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/tasks/:taskId') ->desc('Get Task') @@ -947,30 +967,31 @@ App::get('/v1/projects/:projectId/tasks/:taskId') ->label('sdk.method', 'getTask') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('taskId', null, function () { return new UID(); }, 'Task unique ID.') - ->action( - function ($projectId, $taskId) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $taskId, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $task = $project->search('$id', $taskId, $project->getAttribute('tasks', [])); - - if (empty($task) || !$task instanceof Document) { - throw new Exception('Task not found', 404); - } - - $httpPass = \json_decode($task->getAttribute('httpPass', '{}'), true); - - if (!empty($httpPass) && isset($httpPass['version'])) { - $key = App::getEnv('_APP_OPENSSL_KEY_V'.$httpPass['version']); - $task->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag']))); - } - - $response->json($task->getArrayCopy()); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $task = $project->search('$id', $taskId, $project->getAttribute('tasks', [])); + + if (empty($task) || !$task instanceof Document) { + throw new Exception('Task not found', 404); + } + + $httpPass = \json_decode($task->getAttribute('httpPass', '{}'), true); + + if (!empty($httpPass) && isset($httpPass['version'])) { + $key = App::getEnv('_APP_OPENSSL_KEY_V'.$httpPass['version']); + $task->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag']))); + } + + $response->json($task->getArrayCopy()); + }, ['response', 'consoleDB']); App::put('/v1/projects/:projectId/tasks/:taskId') ->desc('Update Task') @@ -989,60 +1010,61 @@ App::put('/v1/projects/:projectId/tasks/:taskId') ->param('httpHeaders', null, function () { return new ArrayList(new Text(256)); }, 'Task HTTP headers list.', true) ->param('httpUser', '', function () { return new Text(256); }, 'Task HTTP user.', true) ->param('httpPass', '', function () { return new Text(256); }, 'Task HTTP password.', true) - ->action( - function ($projectId, $taskId, $name, $status, $schedule, $security, $httpMethod, $httpUrl, $httpHeaders, $httpUser, $httpPass) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $taskId, $name, $status, $schedule, $security, $httpMethod, $httpUrl, $httpHeaders, $httpUser, $httpPass, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $task = $project->search('$id', $taskId, $project->getAttribute('tasks', [])); - - if (empty($task) || !$task instanceof Document) { - throw new Exception('Task not found', 404); - } - - $cron = CronExpression::factory($schedule); - $next = ($status == 'play') ? $cron->getNextRunDate()->format('U') : null; - - $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); - $key = App::getEnv('_APP_OPENSSL_KEY_V1'); - $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); - $tag = null; - $httpPass = \json_encode([ - 'data' => OpenSSL::encrypt($httpPass, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag), - 'method' => OpenSSL::CIPHER_AES_128_GCM, - 'iv' => \bin2hex($iv), - 'tag' => \bin2hex($tag), - 'version' => '1', - ]); - - $task - ->setAttribute('name', $name) - ->setAttribute('status', $status) - ->setAttribute('schedule', $schedule) - ->setAttribute('updated', \time()) - ->setAttribute('next', $next) - ->setAttribute('security', (int) $security) - ->setAttribute('httpMethod', $httpMethod) - ->setAttribute('httpUrl', $httpUrl) - ->setAttribute('httpHeaders', $httpHeaders) - ->setAttribute('httpUser', $httpUser) - ->setAttribute('httpPass', $httpPass) - ; - - if (false === $consoleDB->updateDocument($task->getArrayCopy())) { - throw new Exception('Failed saving tasks to DB', 500); - } - - if ($next) { - ResqueScheduler::enqueueAt($next, 'v1-tasks', 'TasksV1', $task->getArrayCopy()); - } - - $response->json($task->getArrayCopy()); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $task = $project->search('$id', $taskId, $project->getAttribute('tasks', [])); + + if (empty($task) || !$task instanceof Document) { + throw new Exception('Task not found', 404); + } + + $cron = CronExpression::factory($schedule); + $next = ($status == 'play') ? $cron->getNextRunDate()->format('U') : null; + + $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); + $key = App::getEnv('_APP_OPENSSL_KEY_V1'); + $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); + $tag = null; + $httpPass = \json_encode([ + 'data' => OpenSSL::encrypt($httpPass, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag), + 'method' => OpenSSL::CIPHER_AES_128_GCM, + 'iv' => \bin2hex($iv), + 'tag' => \bin2hex($tag), + 'version' => '1', + ]); + + $task + ->setAttribute('name', $name) + ->setAttribute('status', $status) + ->setAttribute('schedule', $schedule) + ->setAttribute('updated', \time()) + ->setAttribute('next', $next) + ->setAttribute('security', (int) $security) + ->setAttribute('httpMethod', $httpMethod) + ->setAttribute('httpUrl', $httpUrl) + ->setAttribute('httpHeaders', $httpHeaders) + ->setAttribute('httpUser', $httpUser) + ->setAttribute('httpPass', $httpPass) + ; + + if (false === $consoleDB->updateDocument($task->getArrayCopy())) { + throw new Exception('Failed saving tasks to DB', 500); + } + + if ($next) { + ResqueScheduler::enqueueAt($next, 'v1-tasks', 'TasksV1', $task->getArrayCopy()); + } + + $response->json($task->getArrayCopy()); + }, ['response', 'consoleDB']); App::delete('/v1/projects/:projectId/tasks/:taskId') ->desc('Delete Task') @@ -1052,27 +1074,28 @@ App::delete('/v1/projects/:projectId/tasks/:taskId') ->label('sdk.method', 'deleteTask') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('taskId', null, function () { return new UID(); }, 'Task unique ID.') - ->action( - function ($projectId, $taskId) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $taskId, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $task = $project->search('$id', $taskId, $project->getAttribute('tasks', [])); - - if (empty($task) || !$task instanceof Document) { - throw new Exception('Task not found', 404); - } - - if (!$consoleDB->deleteDocument($task->getId())) { - throw new Exception('Failed to remove tasks from DB', 500); - } - - $response->noContent(); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $task = $project->search('$id', $taskId, $project->getAttribute('tasks', [])); + + if (empty($task) || !$task instanceof Document) { + throw new Exception('Task not found', 404); + } + + if (!$consoleDB->deleteDocument($task->getId())) { + throw new Exception('Failed to remove tasks from DB', 500); + } + + $response->noContent(); + }, ['response', 'consoleDB']); // Platforms @@ -1088,47 +1111,48 @@ App::post('/v1/projects/:projectId/platforms') ->param('key', '', function () { return new Text(256); }, 'Package name for android or bundle ID for iOS.', true) ->param('store', '', function () { return new Text(256); }, 'App store or Google Play store ID.', true) ->param('hostname', '', function () { return new Text(256); }, 'Platform client hostname.', true) - ->action( - function ($projectId, $type, $name, $key, $store, $hostname) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $type, $name, $key, $store, $hostname, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $platform = $consoleDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_PLATFORMS, - '$permissions' => [ - 'read' => ['team:'.$project->getAttribute('teamId', null)], - 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'], - ], - 'type' => $type, - 'name' => $name, - 'key' => $key, - 'store' => $store, - 'hostname' => $hostname, - 'dateCreated' => \time(), - 'dateUpdated' => \time(), - ]); - - if (false === $platform) { - throw new Exception('Failed saving platform to DB', 500); - } - - $project->setAttribute('platforms', $platform, Document::SET_TYPE_APPEND); - - $project = $consoleDB->updateDocument($project->getArrayCopy()); - - if (false === $project) { - throw new Exception('Failed saving project to DB', 500); - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($platform->getArrayCopy()) - ; + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $platform = $consoleDB->createDocument([ + '$collection' => Database::SYSTEM_COLLECTION_PLATFORMS, + '$permissions' => [ + 'read' => ['team:'.$project->getAttribute('teamId', null)], + 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'], + ], + 'type' => $type, + 'name' => $name, + 'key' => $key, + 'store' => $store, + 'hostname' => $hostname, + 'dateCreated' => \time(), + 'dateUpdated' => \time(), + ]); + + if (false === $platform) { + throw new Exception('Failed saving platform to DB', 500); + } + + $project->setAttribute('platforms', $platform, Document::SET_TYPE_APPEND); + + $project = $consoleDB->updateDocument($project->getArrayCopy()); + + if (false === $project) { + throw new Exception('Failed saving project to DB', 500); + } + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->json($platform->getArrayCopy()) + ; + }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/platforms') ->desc('List Platforms') @@ -1137,19 +1161,20 @@ App::get('/v1/projects/:projectId/platforms') ->label('sdk.namespace', 'projects') ->label('sdk.method', 'listPlatforms') ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.') - ->action( - function ($projectId) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $platforms = $project->getAttribute('platforms', []); - - $response->json($platforms); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $platforms = $project->getAttribute('platforms', []); + + $response->json($platforms); + }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/platforms/:platformId') ->desc('Get Platform') @@ -1159,23 +1184,24 @@ App::get('/v1/projects/:projectId/platforms/:platformId') ->label('sdk.method', 'getPlatform') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('platformId', null, function () { return new UID(); }, 'Platform unique ID.') - ->action( - function ($projectId, $platformId) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $platformId, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $platform = $project->search('$id', $platformId, $project->getAttribute('platforms', [])); - - if (empty($platform) || !$platform instanceof Document) { - throw new Exception('Platform not found', 404); - } - - $response->json($platform->getArrayCopy()); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $platform = $project->search('$id', $platformId, $project->getAttribute('platforms', [])); + + if (empty($platform) || !$platform instanceof Document) { + throw new Exception('Platform not found', 404); + } + + $response->json($platform->getArrayCopy()); + }, ['response', 'consoleDB']); App::put('/v1/projects/:projectId/platforms/:platformId') ->desc('Update Platform') @@ -1189,35 +1215,36 @@ App::put('/v1/projects/:projectId/platforms/:platformId') ->param('key', '', function () { return new Text(256); }, 'Package name for android or bundle ID for iOS.', true) ->param('store', '', function () { return new Text(256); }, 'App store or Google Play store ID.', true) ->param('hostname', '', function () { return new Text(256); }, 'Platform client URL.', true) - ->action( - function ($projectId, $platformId, $name, $key, $store, $hostname) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $platformId, $name, $key, $store, $hostname, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $platform = $project->search('$id', $platformId, $project->getAttribute('platforms', [])); - - if (empty($platform) || !$platform instanceof Document) { - throw new Exception('Platform not found', 404); - } - - $platform - ->setAttribute('name', $name) - ->setAttribute('dateUpdated', \time()) - ->setAttribute('key', $key) - ->setAttribute('store', $store) - ->setAttribute('hostname', $hostname) - ; - - if (false === $consoleDB->updateDocument($platform->getArrayCopy())) { - throw new Exception('Failed saving platform to DB', 500); - } - - $response->json($platform->getArrayCopy()); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $platform = $project->search('$id', $platformId, $project->getAttribute('platforms', [])); + + if (empty($platform) || !$platform instanceof Document) { + throw new Exception('Platform not found', 404); + } + + $platform + ->setAttribute('name', $name) + ->setAttribute('dateUpdated', \time()) + ->setAttribute('key', $key) + ->setAttribute('store', $store) + ->setAttribute('hostname', $hostname) + ; + + if (false === $consoleDB->updateDocument($platform->getArrayCopy())) { + throw new Exception('Failed saving platform to DB', 500); + } + + $response->json($platform->getArrayCopy()); + }, ['response', 'consoleDB']); App::delete('/v1/projects/:projectId/platforms/:platformId') ->desc('Delete Platform') @@ -1227,27 +1254,28 @@ App::delete('/v1/projects/:projectId/platforms/:platformId') ->label('sdk.method', 'deletePlatform') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('platformId', null, function () { return new UID(); }, 'Platform unique ID.') - ->action( - function ($projectId, $platformId) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $platformId, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $platform = $project->search('$id', $platformId, $project->getAttribute('platforms', [])); - - if (empty($platform) || !$platform instanceof Document) { - throw new Exception('Platform not found', 404); - } - - if (!$consoleDB->deleteDocument($platform->getId())) { - throw new Exception('Failed to remove platform from DB', 500); - } - - $response->noContent(); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $platform = $project->search('$id', $platformId, $project->getAttribute('platforms', [])); + + if (empty($platform) || !$platform instanceof Document) { + throw new Exception('Platform not found', 404); + } + + if (!$consoleDB->deleteDocument($platform->getId())) { + throw new Exception('Failed to remove platform from DB', 500); + } + + $response->noContent(); + }, ['response', 'consoleDB']); // Domains @@ -1259,60 +1287,61 @@ App::post('/v1/projects/:projectId/domains') ->label('sdk.method', 'createDomain') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('domain', null, function () { return new DomainValidator(); }, 'Domain name.') - ->action( - function ($projectId, $domain) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $domain, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $document = $project->search('domain', $domain, $project->getAttribute('domains', [])); - - if (!empty($document)) { - throw new Exception('Domain already exists', 409); - } - - $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', '')); - - if (!$target->isKnown() || $target->isTest()) { - throw new Exception('Unreachable CNAME target ('.$target->get().'), plesse use a domain with a public suffix.', 500); - } - - $domain = new Domain($domain); - - $domain = $consoleDB->createDocument([ - '$collection' => Database::SYSTEM_COLLECTION_DOMAINS, - '$permissions' => [ - 'read' => ['team:'.$project->getAttribute('teamId', null)], - 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'], - ], - 'updated' => \time(), - 'domain' => $domain->get(), - 'tld' => $domain->getSuffix(), - 'registerable' => $domain->getRegisterable(), - 'verification' => false, - 'certificateId' => null, - ]); - - if (false === $domain) { - throw new Exception('Failed saving domain to DB', 500); - } - - $project->setAttribute('domains', $domain, Document::SET_TYPE_APPEND); - - $project = $consoleDB->updateDocument($project->getArrayCopy()); - - if (false === $project) { - throw new Exception('Failed saving project to DB', 500); - } - - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($domain->getArrayCopy()) - ; + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $document = $project->search('domain', $domain, $project->getAttribute('domains', [])); + + if (!empty($document)) { + throw new Exception('Domain already exists', 409); + } + + $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', '')); + + if (!$target->isKnown() || $target->isTest()) { + throw new Exception('Unreachable CNAME target ('.$target->get().'), plesse use a domain with a public suffix.', 500); + } + + $domain = new Domain($domain); + + $domain = $consoleDB->createDocument([ + '$collection' => Database::SYSTEM_COLLECTION_DOMAINS, + '$permissions' => [ + 'read' => ['team:'.$project->getAttribute('teamId', null)], + 'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'], + ], + 'updated' => \time(), + 'domain' => $domain->get(), + 'tld' => $domain->getSuffix(), + 'registerable' => $domain->getRegisterable(), + 'verification' => false, + 'certificateId' => null, + ]); + + if (false === $domain) { + throw new Exception('Failed saving domain to DB', 500); + } + + $project->setAttribute('domains', $domain, Document::SET_TYPE_APPEND); + + $project = $consoleDB->updateDocument($project->getArrayCopy()); + + if (false === $project) { + throw new Exception('Failed saving project to DB', 500); + } + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->json($domain->getArrayCopy()) + ; + }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/domains') ->desc('List Domains') @@ -1321,19 +1350,20 @@ App::get('/v1/projects/:projectId/domains') ->label('sdk.namespace', 'projects') ->label('sdk.method', 'listDomains') ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.') - ->action( - function ($projectId) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $domains = $project->getAttribute('domains', []); - - $response->json($domains); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $domains = $project->getAttribute('domains', []); + + $response->json($domains); + }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/domains/:domainId') ->desc('Get Domain') @@ -1343,23 +1373,24 @@ App::get('/v1/projects/:projectId/domains/:domainId') ->label('sdk.method', 'getDomain') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('domainId', null, function () { return new UID(); }, 'Domain unique ID.') - ->action( - function ($projectId, $domainId) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $domainId, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $domain = $project->search('$id', $domainId, $project->getAttribute('domains', [])); - - if (empty($domain) || !$domain instanceof Document) { - throw new Exception('Domain not found', 404); - } - - $response->json($domain->getArrayCopy()); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $domain = $project->search('$id', $domainId, $project->getAttribute('domains', [])); + + if (empty($domain) || !$domain instanceof Document) { + throw new Exception('Domain not found', 404); + } + + $response->json($domain->getArrayCopy()); + }, ['response', 'consoleDB']); App::patch('/v1/projects/:projectId/domains/:domainId/verification') ->desc('Update Domain Verification Status') @@ -1369,54 +1400,55 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') ->label('sdk.method', 'updateDomainVerification') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('domainId', null, function () { return new UID(); }, 'Domain unique ID.') - ->action( - function ($projectId, $domainId) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $domainId, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $domain = $project->search('$id', $domainId, $project->getAttribute('domains', [])); - - if (empty($domain) || !$domain instanceof Document) { - throw new Exception('Domain not found', 404); - } - - $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', '')); - - if (!$target->isKnown() || $target->isTest()) { - throw new Exception('Unreachable CNAME target ('.$target->get().'), plesse use a domain with a public suffix.', 500); - } - - if ($domain->getAttribute('verification') === true) { - return $response->json($domain->getArrayCopy()); - } - - // Verify Domain with DNS records - $validator = new CNAME($target->get()); - - if (!$validator->isValid($domain->getAttribute('domain', ''))) { - throw new Exception('Failed to verify domain', 401); - } - - $domain - ->setAttribute('verification', true) - ; - - if (false === $consoleDB->updateDocument($domain->getArrayCopy())) { - throw new Exception('Failed saving domains to DB', 500); - } - - // Issue a TLS certificate when domain is verified - Resque::enqueue('v1-certificates', 'CertificatesV1', [ - 'document' => $domain->getArrayCopy(), - 'domain' => $domain->getAttribute('domain'), - ]); - - $response->json($domain->getArrayCopy()); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); + + $domain = $project->search('$id', $domainId, $project->getAttribute('domains', [])); + + if (empty($domain) || !$domain instanceof Document) { + throw new Exception('Domain not found', 404); + } + + $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', '')); + + if (!$target->isKnown() || $target->isTest()) { + throw new Exception('Unreachable CNAME target ('.$target->get().'), plesse use a domain with a public suffix.', 500); + } + + if ($domain->getAttribute('verification') === true) { + return $response->json($domain->getArrayCopy()); + } + + // Verify Domain with DNS records + $validator = new CNAME($target->get()); + + if (!$validator->isValid($domain->getAttribute('domain', ''))) { + throw new Exception('Failed to verify domain', 401); + } + + $domain + ->setAttribute('verification', true) + ; + + if (false === $consoleDB->updateDocument($domain->getArrayCopy())) { + throw new Exception('Failed saving domains to DB', 500); + } + + // Issue a TLS certificate when domain is verified + Resque::enqueue('v1-certificates', 'CertificatesV1', [ + 'document' => $domain->getArrayCopy(), + 'domain' => $domain->getAttribute('domain'), + ]); + + $response->json($domain->getArrayCopy()); + }, ['response', 'consoleDB']); App::delete('/v1/projects/:projectId/domains/:domainId') ->desc('Delete Domain') @@ -1426,24 +1458,25 @@ App::delete('/v1/projects/:projectId/domains/:domainId') ->label('sdk.method', 'deleteDomain') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('domainId', null, function () { return new UID(); }, 'Domain unique ID.') - ->action( - function ($projectId, $domainId) use ($response, $consoleDB) { - $project = $consoleDB->getDocument($projectId); + ->action(function ($projectId, $domainId, $response, $consoleDB) { + /** @var Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ - if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { - throw new Exception('Project not found', 404); - } + $project = $consoleDB->getDocument($projectId); - $domain = $project->search('$id', $domainId, $project->getAttribute('domains', [])); - - if (empty($domain) || !$domain instanceof Document) { - throw new Exception('Domain not found', 404); - } - - if (!$consoleDB->deleteDocument($domain->getId())) { - throw new Exception('Failed to remove domains from DB', 500); - } - - $response->noContent(); + if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { + throw new Exception('Project not found', 404); } - ); \ No newline at end of file + + $domain = $project->search('$id', $domainId, $project->getAttribute('domains', [])); + + if (empty($domain) || !$domain instanceof Document) { + throw new Exception('Domain not found', 404); + } + + if (!$consoleDB->deleteDocument($domain->getId())) { + throw new Exception('Failed to remove domains from DB', 500); + } + + $response->noContent(); + }, ['response', 'consoleDB']); \ No newline at end of file diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php index 962373bd53..fb64331c32 100644 --- a/app/controllers/web/console.php +++ b/app/controllers/web/console.php @@ -331,7 +331,7 @@ App::get('/console/users/teams/team') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') - ->action(function () use ($layout) { + ->action(function ($layout) { /** @var Utopia\View $layout */ $page = new View(__DIR__.'/../../views/console/users/team.phtml'); diff --git a/public/index.php b/public/index.php index ef4d11d2a2..794382ff58 100644 --- a/public/index.php +++ b/public/index.php @@ -14,6 +14,6 @@ ini_set('display_errors', 0); ini_set('display_errors', 1); ini_set('display_startup_errors', 1); error_reporting(E_ALL); -trigger_error('hide errors in prod', E_USER_NOTICE); +//trigger_error('hide errors in prod', E_USER_NOTICE); include __DIR__ . '/../app/app.php'; From 22867f9a8d8c4718bc1de8dc9a65a99091c256cf Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 30 Jun 2020 21:08:02 +0300 Subject: [PATCH 12/17] Updated resources --- app/app.php | 108 +++++------------------------- app/controllers/api/account.php | 2 +- app/controllers/web/home.php | 7 +- app/init.php | 113 ++++++++++++++++++++++++++++++-- 4 files changed, 128 insertions(+), 102 deletions(-) diff --git a/app/app.php b/app/app.php index fd9188ac8e..d31cc9192f 100644 --- a/app/app.php +++ b/app/app.php @@ -15,86 +15,33 @@ use Appwrite\Database\Document; use Appwrite\Database\Validator\Authorization; use Appwrite\Network\Validator\Origin; +Config::setParam('domain', $_SERVER['HTTP_HOST']); +Config::setParam('domainVerification', false); // Config::setParam('domain', $request->getServer('HTTP_HOST', '')); // Config::setParam('domainVerification', false); -// Config::setParam('protocol', $request->getServer('HTTP_X_FORWARDED_PROTO', $request->getServer('REQUEST_SCHEME', 'https'))); -// Config::setParam('port', (string) \parse_url(Config::getParam('protocol').'://'.$request->getServer('HTTP_HOST', ''), PHP_URL_PORT)); -// Config::setParam('hostname', \parse_url(Config::getParam('protocol').'://'.$request->getServer('HTTP_HOST', null), PHP_URL_HOST)); + +\define('COOKIE_DOMAIN', + ( + $_SERVER['HTTP_HOST'] === 'localhost' || + $_SERVER['HTTP_HOST'] === 'localhost:'.$request->getPort() || + (\filter_var($request->getHostname(), FILTER_VALIDATE_IP) !== false) + ) + ? null + : '.'.$request->getHostname() + ); +\define('COOKIE_SAMESITE', Response::COOKIE_SAMESITE_NONE); // \define('COOKIE_DOMAIN', // ( // $request->getServer('HTTP_HOST', null) === 'localhost' || -// $request->getServer('HTTP_HOST', null) === 'localhost:'.Config::getParam('port') || -// (\filter_var(Config::getParam('hostname'), FILTER_VALIDATE_IP) !== false) +// $request->getServer('HTTP_HOST', null) === 'localhost:'.$request->getPort() || +// (\filter_var($request->getHostname(), FILTER_VALIDATE_IP) !== false) // ) // ? null -// : '.'.Config::getParam('hostname') +// : '.'.$request->getHostname() // ); // \define('COOKIE_SAMESITE', Response::COOKIE_SAMESITE_NONE); -// Authorization::disable(); - -// $project = $consoleDB->getDocument($request->getParam('project', $request->getHeader('X-Appwrite-Project', ''))); - -// Authorization::enable(); - -// $console = $consoleDB->getDocument('console'); - -// $mode = $request->getParam('mode', $request->getHeader('X-Appwrite-Mode', 'default')); - -// Auth::setCookieName('a_session_'.$project->getId()); - -// if (APP_MODE_ADMIN === $mode) { -// Auth::setCookieName('a_session_'.$console->getId()); -// } - -// $session = Auth::decodeSession( -// $request->getCookie(Auth::$cookieName, // Get sessions -// $request->getCookie(Auth::$cookieName.'_legacy', // Get fallback session from old clients (no SameSite support) -// $request->getHeader('X-Appwrite-Key', '')))); // Get API Key - -// // Get fallback session from clients who block 3rd-party cookies -// $response->addHeader('X-Debug-Fallback', 'false'); - -// if(empty($session['id']) && empty($session['secret'])) { -// $response->addHeader('X-Debug-Fallback', 'true'); -// $fallback = $request->getHeader('X-Fallback-Cookies', ''); -// $fallback = \json_decode($fallback, true); -// $session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : '')); -// } - -// Auth::$unique = $session['id']; -// Auth::$secret = $session['secret']; - -// $projectDB = new Database(); -// $projectDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)); -// $projectDB->setNamespace('app_'.$project->getId()); -// $projectDB->setMocks(Config::getParam('collections', [])); - -// if (APP_MODE_ADMIN !== $mode) { -// $user = $projectDB->getDocument(Auth::$unique); -// } -// else { -// $user = $consoleDB->getDocument(Auth::$unique); - -// $user -// ->setAttribute('$id', 'admin-'.$user->getAttribute('$id')) -// ; -// } - -// if (empty($user->getId()) // Check a document has been found in the DB -// || Database::SYSTEM_COLLECTION_USERS !== $user->getCollection() // Validate returned document is really a user document -// || !Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_LOGIN, Auth::$secret)) { // Validate user has valid login token -// $user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]); -// } - -// if (APP_MODE_ADMIN === $mode) { -// if (!empty($user->search('teamId', $project->getAttribute('teamId'), $user->getAttribute('memberships')))) { -// Authorization::disable(); -// } else { -// $user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]); -// } -// } // // Set project mail // $register->get('smtp') @@ -106,29 +53,6 @@ use Appwrite\Network\Validator\Origin; // ) // ); -/** - * Get All verified client URLs for both console and current projects - * + Filter for duplicated entries - */ -// $clientsConsole = \array_map(function ($node) { -// return $node['hostname']; -// }, \array_filter($console->getAttribute('platforms', []), function ($node) { -// if (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname'])) { -// return true; -// } - -// return false; -// })); - -// $clients = \array_unique(\array_merge($clientsConsole, \array_map(function ($node) { -// return $node['hostname']; -// }, \array_filter($project->getAttribute('platforms', []), function ($node) { -// if (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname'])) { -// return true; -// } - -// return false; -// })))); App::init(function ($utopia, $request, $response, $console, $project, $user, $locale, $webhooks, $audits, $usage, $clients) { /** @var Utopia\Request $request */ diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 6596bc7537..3092779819 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -565,7 +565,7 @@ App::get('/v1/account') ], $oauth2Keys )), ['roles' => Authorization::getRoles()])); - }, ['response', ['user']]); + }, ['response', 'user']); App::get('/v1/account/prefs') ->desc('Get Account Preferences') diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index 9cbfef4e47..6f8c0d9f06 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -188,8 +188,9 @@ App::get('/open-api-2.json') ->param('platform', APP_PLATFORM_CLIENT, function () {return new WhiteList([APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER, APP_PLATFORM_CONSOLE]);}, 'Choose target platform.', true) ->param('extensions', 0, function () {return new Range(0, 1);}, 'Show extra data.', true) ->param('tests', 0, function () {return new Range(0, 1);}, 'Include only test services.', true) - ->action(function ($platform, $extensions, $tests, $utopia, $response) { + ->action(function ($platform, $extensions, $tests, $utopia, $request, $response) { /** @var Utopia\App $utopia */ + /** @var Utopia\Request $request */ /** @var Utopia\Response $response */ $services = Config::getParam('services', []); @@ -370,7 +371,7 @@ App::get('/open-api-2.json') ], 'externalDocs' => [ 'description' => 'Full API docs, specs and tutorials', - 'url' => Config::getParam('protocol').'://'.Config::getParam('domain').'/docs', + 'url' => $request->getProtocol().'://'.Config::getParam('domain').'/docs', ], ]; @@ -586,4 +587,4 @@ App::get('/open-api-2.json') $response ->json($output); - }, ['utopia', 'response']); \ No newline at end of file + }, ['utopia', 'request', 'response']); \ No newline at end of file diff --git a/app/init.php b/app/init.php index d02ded1951..ea69d1a90c 100644 --- a/app/init.php +++ b/app/init.php @@ -11,6 +11,7 @@ if (\file_exists(__DIR__.'/../vendor/autoload.php')) { require_once __DIR__.'/../vendor/autoload.php'; } +use Appwrite\Auth\Auth; use Utopia\App; use Utopia\Config\Config; use Utopia\Locale\Locale; @@ -19,6 +20,7 @@ use Appwrite\Database\Database; use Appwrite\Database\Adapter\MySQL as MySQLAdapter; use Appwrite\Database\Adapter\Redis as RedisAdapter; use Appwrite\Database\Document; +use Appwrite\Database\Validator\Authorization; use Appwrite\Event\Event; use PHPMailer\PHPMailer\PHPMailer; use Utopia\View; @@ -268,13 +270,101 @@ App::setResource('deletes', function($register) { }, ['register']); // Test Mock -App::setResource('clients', function() { return []; }); +App::setResource('clients', function($console, $project) { + /** + * Get All verified client URLs for both console and current projects + * + Filter for duplicated entries + */ + $clientsConsole = \array_map(function ($node) { + return $node['hostname']; + }, \array_filter($console->getAttribute('platforms', []), function ($node) { + if (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname'])) { + return true; + } -App::setResource('user', function() { return new Document([]); }); + return false; + })); -App::setResource('project', function() { return new Document([]); }); + $clients = \array_unique(\array_merge($clientsConsole, \array_map(function ($node) { + return $node['hostname']; + }, \array_filter($project->getAttribute('platforms', []), function ($node) { + if (isset($node['type']) && $node['type'] === 'web' && isset($node['hostname']) && !empty($node['hostname'])) { + return true; + } -App::setResource('console', function() { return new Document([]); }); + return false; + })))); + + return $clients; +}, ['console', 'project']); + +App::setResource('user', function($mode, $project, $console, $request, $response, $projectDB, $consoleDB) { + + Auth::setCookieName('a_session_'.$project->getId()); + + if (APP_MODE_ADMIN === $mode) { + Auth::setCookieName('a_session_'.$console->getId()); + } + + $session = Auth::decodeSession( + $request->getCookie(Auth::$cookieName, // Get sessions + $request->getCookie(Auth::$cookieName.'_legacy', // Get fallback session from old clients (no SameSite support) + $request->getHeader('X-Appwrite-Key', '')))); // Get API Key + + // Get fallback session from clients who block 3rd-party cookies + $response->addHeader('X-Debug-Fallback', 'false'); + + if(empty($session['id']) && empty($session['secret'])) { + $response->addHeader('X-Debug-Fallback', 'true'); + $fallback = $request->getHeader('X-Fallback-Cookies', ''); + $fallback = \json_decode($fallback, true); + $session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : '')); + } + Auth::$unique = $session['id']; + Auth::$secret = $session['secret']; + + if (APP_MODE_ADMIN !== $mode) { + $user = $projectDB->getDocument(Auth::$unique); + } + else { + $user = $consoleDB->getDocument(Auth::$unique); + + $user + ->setAttribute('$id', 'admin-'.$user->getAttribute('$id')) + ; + } + + if (empty($user->getId()) // Check a document has been found in the DB + || Database::SYSTEM_COLLECTION_USERS !== $user->getCollection() // Validate returned document is really a user document + || !Auth::tokenVerify($user->getAttribute('tokens', []), Auth::TOKEN_TYPE_LOGIN, Auth::$secret)) { // Validate user has valid login token + $user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]); + } + + if (APP_MODE_ADMIN === $mode) { + if (!empty($user->search('teamId', $project->getAttribute('teamId'), $user->getAttribute('memberships')))) { + Authorization::disable(); + } else { + $user = new Document(['$id' => '', '$collection' => Database::SYSTEM_COLLECTION_USERS]); + } + } + + return $user; +}, ['mode', 'project', 'console', 'request', 'response', 'projectDB', 'consoleDB']); + +App::setResource('project', function($consoleDB, $request) { + Authorization::disable(); + + $project = $consoleDB->getDocument($request->getParam('project', + $request->getHeader('X-Appwrite-Project', ''))); + + Authorization::enable(); + + return $project; +}, ['consoleDB', 'request']); + +App::setResource('console', function($consoleDB) { + return $consoleDB->getDocument('console'); +}, ['consoleDB']); App::setResource('consoleDB', function($register) { $consoleDB = new Database(); @@ -282,8 +372,19 @@ App::setResource('consoleDB', function($register) { $consoleDB->setNamespace('app_console'); // Should be replaced with param if we want to have parent projects $consoleDB->setMocks(Config::getParam('collections', [])); + + return $consoleDB; }, ['register']); -App::setResource('projectDB', function() { return new Database([]); }); +App::setResource('projectDB', function($register, $project) { + $projectDB = new Database(); + $projectDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)); + $projectDB->setNamespace('app_'.$project->getId()); + $projectDB->setMocks(Config::getParam('collections', [])); -App::setResource('mode', function() { return false; }); + return $projectDB; +}, ['register', 'project']); + +App::setResource('mode', function($request) { + return $request->getParam('mode', $request->getHeader('X-Appwrite-Mode', 'default')); +}, ['request']); From fd90a57ecee7289353f0d87db4da3b11b409fc93 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 1 Jul 2020 00:38:06 +0300 Subject: [PATCH 13/17] Fixed tests --- app/controllers/api/account.php | 4 +- app/controllers/api/storage.php | 2 +- app/controllers/mock.php | 258 +++++++++++++++----------------- 3 files changed, 123 insertions(+), 141 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 3092779819..16e7541d0b 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -660,7 +660,7 @@ App::get('/v1/account/logs') ->label('sdk.namespace', 'account') ->label('sdk.method', 'getLogs') ->label('sdk.description', '/docs/references/account/get-logs.md') - ->action(function ($response, $register, $project, $user) { + ->action(function ($response, $register, $project, $user, $locale) { /** @var Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ /** @var Appwrite\Database\Document $user */ @@ -726,7 +726,7 @@ App::get('/v1/account/logs') } $response->json($output); - }, ['response', 'register', 'project', 'user']); + }, ['response', 'register', 'project', 'user', 'locale']); App::patch('/v1/account/name') ->desc('Update Account Name') diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 656bed297b..3b7f9af96a 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -577,7 +577,7 @@ App::delete('/v1/storage/files/:fileId') ; $response->noContent(); - }, ['fileId', 'response', 'projectDB', 'webhook', 'audit', 'usage']); + }, ['response', 'projectDB', 'webhook', 'audit', 'usage']); // App::get('/v1/storage/files/:fileId/scan') // ->desc('Scan Storage') diff --git a/app/controllers/mock.php b/app/controllers/mock.php index 9adfce55b4..1676d3f8eb 100644 --- a/app/controllers/mock.php +++ b/app/controllers/mock.php @@ -19,10 +19,8 @@ App::get('/v1/mock/tests/foo') ->param('x', '', function () { return new Text(100); }, 'Sample string param') ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param') ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param') - ->action( - function ($x, $y, $z) { - } - ); + ->action(function ($x, $y, $z) { + }); App::post('/v1/mock/tests/foo') ->desc('Mock a post request for SDK tests') @@ -33,10 +31,8 @@ App::post('/v1/mock/tests/foo') ->param('x', '', function () { return new Text(100); }, 'Sample string param') ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param') ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param') - ->action( - function ($x, $y, $z) { - } - ); + ->action(function ($x, $y, $z) { + }); App::patch('/v1/mock/tests/foo') ->desc('Mock a patch request for SDK tests') @@ -47,10 +43,8 @@ App::patch('/v1/mock/tests/foo') ->param('x', '', function () { return new Text(100); }, 'Sample string param') ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param') ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param') - ->action( - function ($x, $y, $z) { - } - ); + ->action(function ($x, $y, $z) { + }); App::put('/v1/mock/tests/foo') ->desc('Mock a put request for SDK tests') @@ -61,10 +55,8 @@ App::put('/v1/mock/tests/foo') ->param('x', '', function () { return new Text(100); }, 'Sample string param') ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param') ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param') - ->action( - function ($x, $y, $z) { - } - ); + ->action(function ($x, $y, $z) { + }); App::delete('/v1/mock/tests/foo') ->desc('Mock a delete request for SDK tests') @@ -75,10 +67,8 @@ App::delete('/v1/mock/tests/foo') ->param('x', '', function () { return new Text(100); }, 'Sample string param') ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param') ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param') - ->action( - function ($x, $y, $z) { - } - ); + ->action(function ($x, $y, $z) { + }); App::get('/v1/mock/tests/bar') ->desc('Mock a get request for SDK tests') @@ -89,10 +79,8 @@ App::get('/v1/mock/tests/bar') ->param('x', '', function () { return new Text(100); }, 'Sample string param') ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param') ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param') - ->action( - function ($x, $y, $z) { - } - ); + ->action(function ($x, $y, $z) { + }); App::post('/v1/mock/tests/bar') ->desc('Mock a post request for SDK tests') @@ -103,10 +91,8 @@ App::post('/v1/mock/tests/bar') ->param('x', '', function () { return new Text(100); }, 'Sample string param') ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param') ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param') - ->action( - function ($x, $y, $z) { - } - ); + ->action(function ($x, $y, $z) { + }); App::patch('/v1/mock/tests/bar') ->desc('Mock a patch request for SDK tests') @@ -117,10 +103,8 @@ App::patch('/v1/mock/tests/bar') ->param('x', '', function () { return new Text(100); }, 'Sample string param') ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param') ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param') - ->action( - function ($x, $y, $z) { - } - ); + ->action(function ($x, $y, $z) { + }); App::put('/v1/mock/tests/bar') ->desc('Mock a put request for SDK tests') @@ -131,10 +115,8 @@ App::put('/v1/mock/tests/bar') ->param('x', '', function () { return new Text(100); }, 'Sample string param') ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param') ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param') - ->action( - function ($x, $y, $z) { - } - ); + ->action(function ($x, $y, $z) { + }); App::delete('/v1/mock/tests/bar') ->desc('Mock a delete request for SDK tests') @@ -145,10 +127,8 @@ App::delete('/v1/mock/tests/bar') ->param('x', '', function () { return new Text(100); }, 'Sample string param') ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param') ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param') - ->action( - function ($x, $y, $z) { - } - ); + ->action(function ($x, $y, $z) { + }); App::post('/v1/mock/tests/general/upload') ->desc('Mock a post request for SDK tests') @@ -161,32 +141,32 @@ App::post('/v1/mock/tests/general/upload') ->param('y', '', function () { return new Numeric(); }, 'Sample numeric param') ->param('z', null, function () { return new ArrayList(new Text(256)); }, 'Sample array param') ->param('file', [], function () { return new File(); }, 'Sample file param', false) - ->action( - function ($x, $y, $z, $file) use ($request) { - $file = $request->getFiles('file'); - $file['tmp_name'] = (\is_array($file['tmp_name'])) ? $file['tmp_name'] : [$file['tmp_name']]; - $file['name'] = (\is_array($file['name'])) ? $file['name'] : [$file['name']]; - $file['size'] = (\is_array($file['size'])) ? $file['size'] : [$file['size']]; + ->action(function ($x, $y, $z, $file, $request) { + /** @var Utopia\Request $request */ + + $file = $request->getFiles('file'); + $file['tmp_name'] = (\is_array($file['tmp_name'])) ? $file['tmp_name'] : [$file['tmp_name']]; + $file['name'] = (\is_array($file['name'])) ? $file['name'] : [$file['name']]; + $file['size'] = (\is_array($file['size'])) ? $file['size'] : [$file['size']]; - foreach ($file['name'] as $i => $name) { - if ($name !== 'file.png') { - throw new Exception('Wrong file name', 400); - } - } - - foreach ($file['size'] as $i => $size) { - if ($size !== 38756) { - throw new Exception('Wrong file size', 400); - } - } - - foreach ($file['tmp_name'] as $i => $tmpName) { - if (\md5(\file_get_contents($tmpName)) !== 'd80e7e6999a3eb2ae0d631a96fe135a4') { - throw new Exception('Wrong file uploaded', 400); - } + foreach ($file['name'] as $i => $name) { + if ($name !== 'file.png') { + throw new Exception('Wrong file name', 400); } } - ); + + foreach ($file['size'] as $i => $size) { + if ($size !== 38756) { + throw new Exception('Wrong file size', 400); + } + } + + foreach ($file['tmp_name'] as $i => $tmpName) { + if (\md5(\file_get_contents($tmpName)) !== 'd80e7e6999a3eb2ae0d631a96fe135a4') { + throw new Exception('Wrong file uploaded', 400); + } + } + }, ['request']); App::get('/v1/mock/tests/general/redirect') ->desc('Mock a post request for SDK tests') @@ -194,11 +174,11 @@ App::get('/v1/mock/tests/general/redirect') ->label('sdk.namespace', 'general') ->label('sdk.method', 'redirect') ->label('sdk.description', 'Mock a redirect request for SDK tests') - ->action( - function () use ($response) { - $response->redirect('/v1/mock/tests/general/redirected'); - } - ); + ->action(function ($response) { + /** @var Utopia\Response $response */ + + $response->redirect('/v1/mock/tests/general/redirected'); + }, ['response']); App::get('/v1/mock/tests/general/redirected') ->desc('Mock a post request for SDK tests') @@ -206,10 +186,8 @@ App::get('/v1/mock/tests/general/redirected') ->label('sdk.namespace', 'general') ->label('sdk.method', 'redirected') ->label('sdk.description', 'Mock a redirected request for SDK tests') - ->action( - function () { - } - ); + ->action(function () { + }); App::get('/v1/mock/tests/general/set-cookie') ->desc('Mock a cookie request for SDK tests') @@ -217,11 +195,11 @@ App::get('/v1/mock/tests/general/set-cookie') ->label('sdk.namespace', 'general') ->label('sdk.method', 'setCookie') ->label('sdk.description', 'Mock a set cookie request for SDK tests') - ->action( - function () use ($response) { - $response->addCookie('cookieName', 'cookieValue', \time() + 31536000, '/', 'localhost', true, true); - } - ); + ->action(function ($response) { + /** @var Utopia\Response $response */ + + $response->addCookie('cookieName', 'cookieValue', \time() + 31536000, '/', 'localhost', true, true); + }, ['response']); App::get('/v1/mock/tests/general/get-cookie') ->desc('Mock a cookie request for SDK tests') @@ -229,13 +207,13 @@ App::get('/v1/mock/tests/general/get-cookie') ->label('sdk.namespace', 'general') ->label('sdk.method', 'getCookie') ->label('sdk.description', 'Mock a get cookie request for SDK tests') - ->action( - function () use ($request) { - if ($request->getCookie('cookieName', '') !== 'cookieValue') { - throw new Exception('Missing cookie value', 400); - } + ->action(function ($request) { + /** @var Utopia\Request $request */ + + if ($request->getCookie('cookieName', '') !== 'cookieValue') { + throw new Exception('Missing cookie value', 400); } - ); + }, ['request']); App::get('/v1/mock/tests/general/empty') ->desc('Mock a post request for SDK tests') @@ -243,12 +221,12 @@ App::get('/v1/mock/tests/general/empty') ->label('sdk.namespace', 'general') ->label('sdk.method', 'empty') ->label('sdk.description', 'Mock a redirected request for SDK tests') - ->action( - function () use ($response) { - $response->noContent(); - exit(); - } - ); + ->action(function ($response) { + /** @var Utopia\Response $response */ + + $response->noContent(); + exit(); + }, ['response']); App::get('/v1/mock/tests/general/oauth2') ->desc('Mock an OAuth2 login route') @@ -258,11 +236,11 @@ App::get('/v1/mock/tests/general/oauth2') ->param('redirect_uri', '', function () { return new Host(['localhost']); }, 'OAuth2 Redirect URI.') // Important to deny an open redirect attack ->param('scope', '', function () { return new Text(100); }, 'OAuth2 scope list.') ->param('state', '', function () { return new Text(1024); }, 'OAuth2 state.') - ->action( - function ($clientId, $redirectURI, $scope, $state) use ($response) { - $response->redirect($redirectURI.'?'.\http_build_query(['code' => 'abcdef', 'state' => $state])); - } - ); + ->action(function ($clientId, $redirectURI, $scope, $state, $response) { + /** @var Utopia\Response $response */ + + $response->redirect($redirectURI.'?'.\http_build_query(['code' => 'abcdef', 'state' => $state])); + }, ['response']); App::get('/v1/mock/tests/general/oauth2/token') ->desc('Mock an OAuth2 login route') @@ -272,68 +250,72 @@ App::get('/v1/mock/tests/general/oauth2/token') ->param('redirect_uri', '', function () { return new Host(['localhost']); }, 'OAuth2 Redirect URI.') ->param('client_secret', '', function () { return new Text(100); }, 'OAuth2 scope list.') ->param('code', '', function () { return new Text(100); }, 'OAuth2 state.') - ->action( - function ($clientId, $redirectURI, $clientSecret, $code) use ($response) { - if ($clientId != '1') { - throw new Exception('Invalid client ID'); - } + ->action(function ($clientId, $redirectURI, $clientSecret, $code, $response) { + /** @var Utopia\Response $response */ - if ($clientSecret != '123456') { - throw new Exception('Invalid client secret'); - } - - if ($code != 'abcdef') { - throw new Exception('Invalid token'); - } - - $response->json(['access_token' => '123456']); + if ($clientId != '1') { + throw new Exception('Invalid client ID'); } - ); + + if ($clientSecret != '123456') { + throw new Exception('Invalid client secret'); + } + + if ($code != 'abcdef') { + throw new Exception('Invalid token'); + } + + $response->json(['access_token' => '123456']); + }, ['response']); App::get('/v1/mock/tests/general/oauth2/user') ->desc('Mock an OAuth2 user route') ->label('scope', 'public') ->label('docs', false) ->param('token', '', function () { return new Text(100); }, 'OAuth2 Access Token.') - ->action( - function ($token) use ($response) { - if ($token != '123456') { - throw new Exception('Invalid token'); - } + ->action(function ($token, $response) { + /** @var Utopia\Response $response */ - $response->json([ - 'id' => 1, - 'name' => 'User Name', - 'email' => 'user@localhost.test', - ]); + if ($token != '123456') { + throw new Exception('Invalid token'); } - ); + + $response->json([ + 'id' => 1, + 'name' => 'User Name', + 'email' => 'user@localhost.test', + ]); + }, ['response']); App::get('/v1/mock/tests/general/oauth2/success') ->label('scope', 'public') ->label('docs', false) - ->action( - function () use ($response) { - $response->json([ - 'result' => 'success', - ]); - } - ); + ->action(function ($response) { + /** @var Utopia\Response $response */ + + $response->json([ + 'result' => 'success', + ]); + }, ['response']); App::get('/v1/mock/tests/general/oauth2/failure') ->label('scope', 'public') ->label('docs', false) - ->action( - function () use ($response) { - $response - ->setStatusCode(Response::STATUS_CODE_BAD_REQUEST) - ->json([ - 'result' => 'failure', - ]); - } - ); + ->action(function ($response) { + /** @var Utopia\Response $response */ + + $response + ->setStatusCode(Response::STATUS_CODE_BAD_REQUEST) + ->json([ + 'result' => 'failure', + ]); + }, ['response']); + +App::shutdown(function($utopia, $response, $request) { + /** @var Utopia\App $utopia */ + /** @var Utopia\Request $request */ + /** @var Utopia\Response $response */ -App::shutdown(function($response, $request, $utopia) { $result = []; $route = $utopia->match($request); $path = APP_STORAGE_CACHE.'/tests.json'; @@ -352,4 +334,4 @@ App::shutdown(function($response, $request, $utopia) { } $response->json(['result' => $route->getMethod() . ':' . $route->getURL() . ':passed']); -}, ['response', 'request', 'utopia'], 'mock'); \ No newline at end of file +}, ['utopia', 'response', 'request'], 'mock'); \ No newline at end of file From 78c0566028d1ffafac0cdfb385ad42b6243c9ac3 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 1 Jul 2020 08:08:49 +0300 Subject: [PATCH 14/17] Removed globals from shared controllers --- app/controllers/shared/api.php | 2 -- app/controllers/web/console.php | 2 -- app/controllers/web/home.php | 2 -- 3 files changed, 6 deletions(-) diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 38c40498c2..2a026cedf1 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -5,8 +5,6 @@ use Utopia\Exception; use Utopia\Abuse\Abuse; use Utopia\Abuse\Adapters\TimeLimit; -global $utopia, $request, $response, $register, $user, $project; - App::init(function ($utopia, $request, $response, $register, $user, $project) { $route = $utopia->match($request); diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php index fb64331c32..90c178b52d 100644 --- a/app/controllers/web/console.php +++ b/app/controllers/web/console.php @@ -1,7 +1,5 @@ Date: Wed, 1 Jul 2020 08:45:47 +0300 Subject: [PATCH 15/17] Updated flow --- app/app.php | 7 +------ public/index.php | 8 ++++++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/app.php b/app/app.php index d31cc9192f..ee1bbe0c43 100644 --- a/app/app.php +++ b/app/app.php @@ -53,7 +53,6 @@ Config::setParam('domainVerification', false); // ) // ); - App::init(function ($utopia, $request, $response, $console, $project, $user, $locale, $webhooks, $audits, $usage, $clients) { /** @var Utopia\Request $request */ /** @var Utopia\Response $response */ @@ -452,8 +451,4 @@ include_once __DIR__ . '/controllers/shared/web.php'; foreach(Config::getParam('services', []) as $service) { include_once $service['controller']; -} - -$app = new App('Asia/Tel_Aviv'); - -$app->run(new Request(), new Response()); \ No newline at end of file +} \ No newline at end of file diff --git a/public/index.php b/public/index.php index 794382ff58..a143994d99 100644 --- a/public/index.php +++ b/public/index.php @@ -8,6 +8,10 @@ * ― Rick Cook, The Wizardry Compiled */ +use Utopia\App; +use Utopia\Request; +use Utopia\Response; + error_reporting(0); ini_set('display_errors', 0); @@ -17,3 +21,7 @@ error_reporting(E_ALL); //trigger_error('hide errors in prod', E_USER_NOTICE); include __DIR__ . '/../app/app.php'; + +$app = new App('Asia/Tel_Aviv'); + +$app->run(new Request(), new Response()); \ No newline at end of file From 56f397c2a346493833a9614116e80540940f60de Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 1 Jul 2020 08:54:54 +0300 Subject: [PATCH 16/17] Fixed tests --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 93239ecdea..026e1beafb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -50,7 +50,7 @@ services: - ./phpunit.xml:/usr/share/nginx/html/phpunit.xml - ./tests:/usr/share/nginx/html/tests - ./app:/usr/share/nginx/html/app - - ./vendor:/usr/share/nginx/html/vendor + # - ./vendor:/usr/share/nginx/html/vendor - ./docs:/usr/share/nginx/html/docs - ./public:/usr/share/nginx/html/public - ./src:/usr/share/nginx/html/src From f5af0e701cfdf7c3ee3fc3b4ec85a232a513cfe8 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Wed, 1 Jul 2020 09:35:57 +0300 Subject: [PATCH 17/17] Fixed cookie vars --- app/app.php | 39 +++++++++++---------------------- app/controllers/api/account.php | 22 +++++++++---------- app/controllers/api/teams.php | 4 ++-- 3 files changed, 26 insertions(+), 39 deletions(-) diff --git a/app/app.php b/app/app.php index ee1bbe0c43..0c0258aa70 100644 --- a/app/app.php +++ b/app/app.php @@ -15,33 +15,10 @@ use Appwrite\Database\Document; use Appwrite\Database\Validator\Authorization; use Appwrite\Network\Validator\Origin; -Config::setParam('domain', $_SERVER['HTTP_HOST']); +Config::setParam('domain', 'localhost'); Config::setParam('domainVerification', false); -// Config::setParam('domain', $request->getServer('HTTP_HOST', '')); -// Config::setParam('domainVerification', false); - -\define('COOKIE_DOMAIN', - ( - $_SERVER['HTTP_HOST'] === 'localhost' || - $_SERVER['HTTP_HOST'] === 'localhost:'.$request->getPort() || - (\filter_var($request->getHostname(), FILTER_VALIDATE_IP) !== false) - ) - ? null - : '.'.$request->getHostname() - ); -\define('COOKIE_SAMESITE', Response::COOKIE_SAMESITE_NONE); - -// \define('COOKIE_DOMAIN', -// ( -// $request->getServer('HTTP_HOST', null) === 'localhost' || -// $request->getServer('HTTP_HOST', null) === 'localhost:'.$request->getPort() || -// (\filter_var($request->getHostname(), FILTER_VALIDATE_IP) !== false) -// ) -// ? null -// : '.'.$request->getHostname() -// ); -// \define('COOKIE_SAMESITE', Response::COOKIE_SAMESITE_NONE); - +Config::setParam('cookieDomain', 'localhost'); +Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE); // // Set project mail // $register->get('smtp') @@ -98,10 +75,20 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo $selfDomain = new Domain(Config::getParam('hostname')); $endDomain = new Domain($origin); + Config::setParam('domain', $request->getServer('HTTP_HOST', '')); + Config::setParam('domainVerification', ($selfDomain->getRegisterable() === $endDomain->getRegisterable()) && $endDomain->getRegisterable() !== ''); + Config::setParam('cookieDomain', ( + $request->getServer('HTTP_HOST', null) === 'localhost' || + $request->getServer('HTTP_HOST', null) === 'localhost:'.$request->getPort() || + (\filter_var($request->getHostname(), FILTER_VALIDATE_IP) !== false) + ) + ? null + : '.'.$request->getHostname() + ); /* * Security Headers * diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 16e7541d0b..84107cb81d 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -234,8 +234,8 @@ App::post('/v1/account/sessions') } $response - ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($profile->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, Auth::encodeSession($profile->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) + ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($profile->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, Auth::encodeSession($profile->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->setStatusCode(Response::STATUS_CODE_CREATED) ->json($session->getArrayCopy(['$id', 'type', 'expire'])) ; @@ -526,7 +526,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $state['success'] = URLParser::parse($state['success']); $query = URLParser::parseQuery($state['success']['query']); $query['project'] = $project->getId(); - $query['domain'] = COOKIE_DOMAIN; + $query['domain'] = Config::getParam('cookieDomain'); $query['key'] = Auth::$cookieName; $query['secret'] = Auth::encodeSession($user->getId(), $secret); $state['success']['query'] = URLParser::unparseQuery($query); @@ -536,8 +536,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') $response ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') ->addHeader('Pragma', 'no-cache') - ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) + ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->redirect($state['success']) ; }, ['request', 'response', 'project', 'user', 'projectDB', 'audit']); @@ -975,8 +975,8 @@ App::delete('/v1/account') } $response - ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) + ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->noContent() ; }, ['request', 'response', 'user', 'projectDB', 'audit', 'webhook']); @@ -1034,8 +1034,8 @@ App::delete('/v1/account/sessions/:sessionId') if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too $response - ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) + ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ; } @@ -1093,8 +1093,8 @@ App::delete('/v1/account/sessions') if ($token->getAttribute('secret') == Auth::hash(Auth::$secret)) { // If current session delete the cookies too $response - ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) + ->addCookie(Auth::$cookieName.'_legacy', '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, '', \time() - 3600, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ; } } diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 4af413f62e..6e1c31751c 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -570,8 +570,8 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') } $response - ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, null) - ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', COOKIE_DOMAIN, ('https' == $protocol), true, COOKIE_SAMESITE) + ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->json(\array_merge($membership->getArrayCopy([ '$id', 'userId',