From 62dda1d42556a98f7839953f10df477a566896ee Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 26 Jun 2020 15:28:57 +0300 Subject: [PATCH 01/27] Added storage init --- app/controllers/api/storage.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 9a12b56048..cdf7c60474 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -23,8 +23,6 @@ use Appwrite\Storage\Compression\Algorithms\GZIP; use Appwrite\Resize\Resize; use Appwrite\OpenSSL\OpenSSL; -Storage::addDevice('local', new Local(APP_STORAGE_UPLOADS.'/app-'.$project->getId())); - $fileLogos = [ // Based on this list @see http://stackoverflow.com/a/4212908/2299554 'default' => __DIR__.'/../../config/files/none.png', @@ -131,6 +129,10 @@ $mimes = [ 'application/pdf', ]; +$utopia->init(function () use ($project) { + Storage::addDevice('local', new Local(APP_STORAGE_UPLOADS.'/app-'.$project->getId())); +}, 'storage'); + $utopia->post('/v1/storage/files') ->desc('Create File') ->groups(['api', 'storage']) From e088140ff56ed8c72456d2e3c8c245141f866db7 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 26 Jun 2020 15:40:24 +0300 Subject: [PATCH 02/27] Use utopia to get env vars --- app/config/collections.php | 8 ++--- app/controllers/api/account.php | 12 +++---- app/init.php | 56 ++++++++++++++++----------------- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index 3141419c1b..2213b8c8f9 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1,6 +1,6 @@ '', 'legalAddress' => '', 'legalTaxId' => '', - 'authWhitelistEmails' => (!empty($request->getServer('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', $request->getServer('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [], - 'authWhitelistIPs' => (!empty($request->getServer('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', $request->getServer('_APP_CONSOLE_WHITELIST_IPS', null)) : [], - 'authWhitelistDomains' => (!empty($request->getServer('_APP_CONSOLE_WHITELIST_DOMAINS', null))) ? \explode(',', $request->getServer('_APP_CONSOLE_WHITELIST_DOMAINS', null)) : [], + 'authWhitelistEmails' => (!empty($utopia->getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', $utopia->getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [], + 'authWhitelistIPs' => (!empty($utopia->getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', $utopia->getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [], + 'authWhitelistDomains' => (!empty($utopia->getEnv('_APP_CONSOLE_WHITELIST_DOMAINS', null))) ? \explode(',', $utopia->getEnv('_APP_CONSOLE_WHITELIST_DOMAINS', null)) : [], ], Database::SYSTEM_COLLECTION_COLLECTIONS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 3bce54eafa..0bba1b43e3 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -29,8 +29,8 @@ use DeviceDetector\DeviceDetector; use GeoIp2\Database\Reader; use Utopia\Validator\ArrayList; -$oauthDefaultSuccess = $request->getServer('_APP_HOME').'/auth/oauth2/success'; -$oauthDefaultFailure = $request->getServer('_APP_HOME').'/auth/oauth2/failure'; +$oauthDefaultSuccess = $utopia->getEnv('_APP_HOME').'/auth/oauth2/success'; +$oauthDefaultFailure = $utopia->getEnv('_APP_HOME').'/auth/oauth2/failure'; $oauth2Keys = []; @@ -256,7 +256,7 @@ $utopia->get('/v1/account/sessions/oauth2/:provider') ->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('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) { + function ($provider, $success, $failure, $scopes) use ($response, $request, $utopia, $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', ''); @@ -265,7 +265,7 @@ $utopia->get('/v1/account/sessions/oauth2/:provider') $appSecret = \json_decode($appSecret, true); if (!empty($appSecret) && isset($appSecret['version'])) { - $key = $request->getServer('_APP_OPENSSL_KEY_V'.$appSecret['version']); + $key = $utopia->getEnv('_APP_OPENSSL_KEY_V'.$appSecret['version']); $appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, \hex2bin($appSecret['iv']), \hex2bin($appSecret['tag'])); } @@ -348,7 +348,7 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect') ->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) { + function ($provider, $code, $state) use ($response, $request, $utopia, $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' => '']; @@ -360,7 +360,7 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect') $appSecret = \json_decode($appSecret, true); if (!empty($appSecret) && isset($appSecret['version'])) { - $key = $request->getServer('_APP_OPENSSL_KEY_V'.$appSecret['version']); + $key = $utopia->getEnv('_APP_OPENSSL_KEY_V'.$appSecret['version']); $appSecret = OpenSSL::decrypt($appSecret['data'], $appSecret['method'], $key, 0, \hex2bin($appSecret['iv']), \hex2bin($appSecret['tag'])); } diff --git a/app/init.php b/app/init.php index ca378a2b08..4512061d2d 100644 --- a/app/init.php +++ b/app/init.php @@ -68,13 +68,13 @@ Config::load('services', __DIR__.'/../app/config/services.php'); // List of ser Config::setParam('env', $utopia->getMode()); Config::setParam('domain', $request->getServer('HTTP_HOST', '')); Config::setParam('domainVerification', false); -Config::setParam('version', $request->getServer('_APP_VERSION', 'UNKNOWN')); +Config::setParam('version', $utopia->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)); -Resque::setBackend($request->getServer('_APP_REDIS_HOST', '') - .':'.$request->getServer('_APP_REDIS_PORT', '')); +Resque::setBackend($utopia->getEnv('_APP_REDIS_HOST', '') + .':'.$utopia->getEnv('_APP_REDIS_PORT', '')); \define('COOKIE_DOMAIN', ( @@ -90,11 +90,11 @@ Resque::setBackend($request->getServer('_APP_REDIS_HOST', '') /* * Registry */ -$register->set('db', function () use ($request) { // Register DB connection - $dbHost = $request->getServer('_APP_DB_HOST', ''); - $dbUser = $request->getServer('_APP_DB_USER', ''); - $dbPass = $request->getServer('_APP_DB_PASS', ''); - $dbScheme = $request->getServer('_APP_DB_SCHEMA', ''); +$register->set('db', function () use ($utopia) { // Register DB connection + $dbHost = $utopia->getEnv('_APP_DB_HOST', ''); + $dbUser = $utopia->getEnv('_APP_DB_USER', ''); + $dbPass = $utopia->getEnv('_APP_DB_PASS', ''); + $dbScheme = $utopia->getEnv('_APP_DB_SCHEMA', ''); $pdo = new PDO("mysql:host={$dbHost};dbname={$dbScheme};charset=utf8mb4", $dbUser, $dbPass, array( PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', @@ -107,9 +107,9 @@ $register->set('db', function () use ($request) { // Register DB connection return $pdo; }); -$register->set('influxdb', function () use ($request) { // Register DB connection - $host = $request->getServer('_APP_INFLUXDB_HOST', ''); - $port = $request->getServer('_APP_INFLUXDB_PORT', ''); +$register->set('influxdb', function () use ($utopia) { // Register DB connection + $host = $utopia->getEnv('_APP_INFLUXDB_HOST', ''); + $port = $utopia->getEnv('_APP_INFLUXDB_PORT', ''); if (empty($host) || empty($port)) { return; @@ -119,43 +119,43 @@ $register->set('influxdb', function () use ($request) { // Register DB connectio return $client; }); -$register->set('statsd', function () use ($request) { // Register DB connection - $host = $request->getServer('_APP_STATSD_HOST', 'telegraf'); - $port = $request->getServer('_APP_STATSD_PORT', 8125); +$register->set('statsd', function () use ($utopia) { // Register DB connection + $host = $utopia->getEnv('_APP_STATSD_HOST', 'telegraf'); + $port = $utopia->getEnv('_APP_STATSD_PORT', 8125); $connection = new \Domnikl\Statsd\Connection\UdpSocket($host, $port); $statsd = new \Domnikl\Statsd\Client($connection); return $statsd; }); -$register->set('cache', function () use ($request) { // Register cache connection +$register->set('cache', function () use ($utopia) { // Register cache connection $redis = new Redis(); - $redis->connect($request->getServer('_APP_REDIS_HOST', ''), - $request->getServer('_APP_REDIS_PORT', '')); + $redis->connect($utopia->getEnv('_APP_REDIS_HOST', ''), + $utopia->getEnv('_APP_REDIS_PORT', '')); return $redis; }); -$register->set('smtp', function () use ($request) { +$register->set('smtp', function () use ($utopia) { $mail = new PHPMailer(true); $mail->isSMTP(); - $username = $request->getServer('_APP_SMTP_USERNAME', null); - $password = $request->getServer('_APP_SMTP_PASSWORD', null); + $username = $utopia->getEnv('_APP_SMTP_USERNAME', null); + $password = $utopia->getEnv('_APP_SMTP_PASSWORD', null); $mail->XMailer = 'Appwrite Mailer'; - $mail->Host = $request->getServer('_APP_SMTP_HOST', 'smtp'); - $mail->Port = $request->getServer('_APP_SMTP_PORT', 25); + $mail->Host = $utopia->getEnv('_APP_SMTP_HOST', 'smtp'); + $mail->Port = $utopia->getEnv('_APP_SMTP_PORT', 25); $mail->SMTPAuth = (!empty($username) && !empty($password)); $mail->Username = $username; $mail->Password = $password; - $mail->SMTPSecure = $request->getServer('_APP_SMTP_SECURE', false); + $mail->SMTPSecure = $utopia->getEnv('_APP_SMTP_SECURE', false); $mail->SMTPAutoTLS = false; $mail->CharSet = 'UTF-8'; - $from = \urldecode($request->getServer('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server')); - $email = $request->getServer('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM); + $from = \urldecode($utopia->getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server')); + $email = $utopia->getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM); $mail->setFrom($email, $from); $mail->addReplyTo($email, $from); @@ -230,7 +230,7 @@ if (\in_array($locale, Config::getParam('locales'))) { 'method' => 'GET', 'user_agent' => \sprintf(APP_USERAGENT, Config::getParam('version'), - $request->getServer('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)), + $utopia->getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)), 'timeout' => 2, ], ]); @@ -310,9 +310,9 @@ if (APP_MODE_ADMIN === $mode) { // Set project mail $register->get('smtp') ->setFrom( - $request->getServer('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), + $utopia->getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), ($project->getId() === 'console') - ? \urldecode($request->getServer('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server')) + ? \urldecode($utopia->getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server')) : \sprintf(Locale::getText('account.emails.team'), $project->getAttribute('name') ) ); From 68b073c02a46730b5767dfd2172855ab10e708f7 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 27 Jun 2020 07:29:33 +0300 Subject: [PATCH 03/27] Removed path and version global vars --- public/index.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/public/index.php b/public/index.php index c87b14a26e..6c0dfd59c2 100644 --- a/public/index.php +++ b/public/index.php @@ -15,9 +15,4 @@ ini_set('display_errors', 0); // ini_set('display_startup_errors', 1); // error_reporting(E_ALL); -$path = (isset($_GET['q'])) ? explode('/', $_GET['q']) : []; - -array_shift($path); -$version = array_shift($path); - include __DIR__ . '/../app/app.php'; From 443e05a37e3c09299a568fe30a601768a4f880ab Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 27 Jun 2020 07:44:04 +0300 Subject: [PATCH 04/27] Added request global --- app/config/collections.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/collections.php b/app/config/collections.php index 2213b8c8f9..4f8bed232f 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1,6 +1,6 @@ Date: Sat, 27 Jun 2020 08:36:22 +0300 Subject: [PATCH 05/27] Added avatars to the config class --- app/app.php | 2 +- app/controllers/api/avatars.php | 21 ++++++++------------- app/init.php | 3 +++ 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/app/app.php b/app/app.php index e92660b5a0..53adb75c63 100644 --- a/app/app.php +++ b/app/app.php @@ -50,7 +50,7 @@ $clients = \array_unique(\array_merge($clientsConsole, \array_map(function ($nod return false; })))); -$utopia->init(function () use ($utopia, $request, $response, &$user, $project, $console, $webhook, $mail, $audit, $usage, $clients) { +$utopia->init(function () use ($utopia, $request, $response, &$user, $project, $console, $webhook, $audit, $usage, $clients) { $route = $utopia->match($request); diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index 90f8ac34c8..08940b1196 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -18,21 +18,16 @@ use BaconQrCode\Writer; use Utopia\Config\Config; use Utopia\Validator\HexColor; -$types = [ - 'browsers' => include __DIR__.'/../../config/avatars/browsers.php', - 'credit-cards' => include __DIR__.'/../../config/avatars/credit-cards.php', - 'flags' => include __DIR__.'/../../config/avatars/flags.php', -]; - -$avatarCallback = function ($type, $code, $width, $height, $quality) use ($types, $response) { +$avatarCallback = function ($type, $code, $width, $height, $quality) use ($response) { $code = \strtolower($code); $type = \strtolower($type); + $set = Config::getParam('avatar-'.$type, []); - if (!\array_key_exists($type, $types)) { + if (empty($set)) { throw new Exception('Avatar set not found', 404); } - if (!\array_key_exists($code, $types[$type])) { + if (!\array_key_exists($code, $set)) { throw new Exception('Avatar not found', 404); } @@ -43,7 +38,7 @@ $avatarCallback = function ($type, $code, $width, $height, $quality) use ($types $output = 'png'; $date = \date('D, d M Y H:i:s', \time() + (60 * 60 * 24 * 45)).' GMT'; // 45 days cache $key = \md5('/v1/avatars/:type/:code-'.$code.$width.$height.$quality.$output); - $path = $types[$type][$code]; + $path = $set[$code]; $type = 'png'; if (!\is_readable($path)) { @@ -89,7 +84,7 @@ $avatarCallback = function ($type, $code, $width, $height, $quality) use ($types $utopia->get('/v1/avatars/credit-cards/:code') ->desc('Get Credit Card Icon') ->groups(['api', 'avatars']) - ->param('code', '', function () use ($types) { return new WhiteList(\array_keys($types['credit-cards'])); }, 'Credit Card Code. Possible values: '.\implode(', ', \array_keys($types['credit-cards'])).'.') + ->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) @@ -106,7 +101,7 @@ $utopia->get('/v1/avatars/credit-cards/:code') $utopia->get('/v1/avatars/browsers/:code') ->desc('Get Browser Icon') ->groups(['api', 'avatars']) - ->param('code', '', function () use ($types) { return new WhiteList(\array_keys($types['browsers'])); }, 'Browser Code.') + ->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) @@ -123,7 +118,7 @@ $utopia->get('/v1/avatars/browsers/:code') $utopia->get('/v1/avatars/flags/:code') ->desc('Get Country Flag') ->groups(['api', 'avatars']) - ->param('code', '', function () use ($types) { return new WhiteList(\array_keys($types['flags'])); }, 'Country Code. ISO Alpha-2 country code format.') + ->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) diff --git a/app/init.php b/app/init.php index 4512061d2d..1b340d23ac 100644 --- a/app/init.php +++ b/app/init.php @@ -64,6 +64,9 @@ 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::setParam('env', $utopia->getMode()); Config::setParam('domain', $request->getServer('HTTP_HOST', '')); From 601073302b05c43c56db81afaeea7795d9514015 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 27 Jun 2020 08:48:47 +0300 Subject: [PATCH 06/27] Removed unused globals --- app/controllers/api/health.php | 2 +- app/controllers/api/locale.php | 2 +- app/controllers/api/storage.php | 2 +- app/controllers/api/teams.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index 76727563ae..46db6b65f6 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -1,6 +1,6 @@ Date: Sat, 27 Jun 2020 08:52:23 +0300 Subject: [PATCH 07/27] Remove unused global --- app/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/app.php b/app/app.php index 53adb75c63..70cf32e817 100644 --- a/app/app.php +++ b/app/app.php @@ -2,7 +2,7 @@ require_once __DIR__.'/init.php'; -global $utopia, $request, $response, $register, $consoleDB, $project; +global $utopia, $request, $response, $register, $project; use Utopia\App; use Utopia\Request; From 4abc35fe1a8e17cac967825c6898364a801772c9 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 27 Jun 2020 10:25:12 +0300 Subject: [PATCH 08/27] Removed commented code --- app/views/console/index.phtml | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/app/views/console/index.phtml b/app/views/console/index.phtml index a30006b052..78167df5fd 100644 --- a/app/views/console/index.phtml +++ b/app/views/console/index.phtml @@ -37,21 +37,7 @@ $home = $this->getParam('home', '');

 

- +
From 3c2394d0c06203673c9eb2015e037be0563dfb5c Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sat, 27 Jun 2020 22:42:38 +0300 Subject: [PATCH 09/27] Added new resources --- app/init.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/init.php b/app/init.php index 1b340d23ac..825894f556 100644 --- a/app/init.php +++ b/app/init.php @@ -23,6 +23,7 @@ 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 PHPMailer\PHPMailer\PHPMailer; const APP_NAME = 'Appwrite'; @@ -167,6 +168,21 @@ $register->set('smtp', function () use ($utopia) { return $mail; }); +$register->set('queue-webhooks', function () { + return new Event('v1-webhooks', 'WebhooksV1'); +}); +$register->set('queue-audits', function () { + return new Event('v1-audits', 'AuditsV1'); +}); +$register->set('queue-usage', function () { + return new Event('v1-usage', 'UsageV1'); +}); +$register->set('queue-mails', function () { + return new Event('v1-mails', 'MailsV1'); +}); +$register->set('queue-deletes', function () { + return new Event('v1-deletes', 'DeletesV1'); +}); /* * Localization From 00e3632c5982925963f093b1e942340e895924ab Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 28 Jun 2020 20:31:21 +0300 Subject: [PATCH 10/27] Upgraded FW --- app/app.php | 18 ++++----- app/config/collections.php | 9 +++-- app/controllers/api/account.php | 49 ++++++++++++------------ app/controllers/api/avatars.php | 15 ++++---- app/controllers/api/database.php | 24 ++++++------ app/controllers/api/graphql.php | 4 +- app/controllers/api/health.php | 29 +++++++------- app/controllers/api/locale.php | 16 ++++---- app/controllers/api/projects.php | 65 ++++++++++++++++---------------- app/controllers/api/storage.php | 21 ++++++----- app/controllers/api/teams.php | 19 +++++----- app/controllers/api/users.php | 21 ++++++----- app/controllers/mock.php | 47 ++++++++++++----------- app/controllers/shared/api.php | 3 +- app/controllers/shared/web.php | 5 ++- app/controllers/web/console.php | 37 +++++++++--------- app/controllers/web/home.php | 27 ++++++------- app/init.php | 50 ++++++++++++------------ composer.json | 2 +- composer.lock | 22 +++++------ 20 files changed, 249 insertions(+), 234 deletions(-) diff --git a/app/app.php b/app/app.php index 70cf32e817..3348198ee0 100644 --- a/app/app.php +++ b/app/app.php @@ -50,7 +50,7 @@ $clients = \array_unique(\array_merge($clientsConsole, \array_map(function ($nod return false; })))); -$utopia->init(function () use ($utopia, $request, $response, &$user, $project, $console, $webhook, $audit, $usage, $clients) { +App::init(function () use ($utopia, $request, $response, &$user, $project, $console, $webhook, $audit, $usage, $clients) { $route = $utopia->match($request); @@ -79,7 +79,7 @@ $utopia->init(function () use ($utopia, $request, $response, &$user, $project, $ * As recommended at: * @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers */ - if ($utopia->getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS + if (App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS if(Config::getParam('protocol') !== 'https') { return $response->redirect('https://' . Config::getParam('domain').$request->getServer('REQUEST_URI')); } @@ -224,7 +224,7 @@ $utopia->init(function () use ($utopia, $request, $response, &$user, $project, $ ; }); -$utopia->shutdown(function () use ($response, $request, $webhook, $audit, $usage, $deletes, $mode, $project, $utopia) { +App::shutdown(function () use ($response, $request, $webhook, $audit, $usage, $deletes, $mode, $project, $utopia) { /* * Trigger events for background workers @@ -254,7 +254,7 @@ $utopia->shutdown(function () use ($response, $request, $webhook, $audit, $usage } }); -$utopia->options(function () use ($request, $response) { +App::options(function () use ($request, $response) { $origin = $request->getServer('HTTP_ORIGIN'); $response @@ -266,7 +266,7 @@ $utopia->options(function () use ($request, $response) { ->send(); }); -$utopia->error(function ($error /* @var $error Exception */) use ($request, $response, $utopia, $project) { +App::error(function ($error /* @var $error Exception */) use ($request, $response, $utopia, $project) { $env = Config::getParam('env'); $version = Config::getParam('version'); @@ -339,7 +339,7 @@ $utopia->error(function ($error /* @var $error Exception */) use ($request, $res ; }); -$utopia->get('/manifest.json') +App::get('/manifest.json') ->desc('Progressive app manifest file') ->label('scope', 'public') ->label('docs', false) @@ -365,7 +365,7 @@ $utopia->get('/manifest.json') } ); -$utopia->get('/robots.txt') +App::get('/robots.txt') ->desc('Robots.txt File') ->label('scope', 'public') ->label('docs', false) @@ -376,7 +376,7 @@ $utopia->get('/robots.txt') } ); -$utopia->get('/humans.txt') +App::get('/humans.txt') ->desc('Humans.txt File') ->label('scope', 'public') ->label('docs', false) @@ -387,7 +387,7 @@ $utopia->get('/humans.txt') } ); -$utopia->get('/.well-known/acme-challenge') +App::get('/.well-known/acme-challenge') ->desc('SSL Verification') ->label('scope', 'public') ->label('docs', false) diff --git a/app/config/collections.php b/app/config/collections.php index 4f8bed232f..4e00c36c35 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1,7 +1,8 @@ '', 'legalAddress' => '', 'legalTaxId' => '', - 'authWhitelistEmails' => (!empty($utopia->getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', $utopia->getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [], - 'authWhitelistIPs' => (!empty($utopia->getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', $utopia->getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [], - 'authWhitelistDomains' => (!empty($utopia->getEnv('_APP_CONSOLE_WHITELIST_DOMAINS', null))) ? \explode(',', $utopia->getEnv('_APP_CONSOLE_WHITELIST_DOMAINS', null)) : [], + 'authWhitelistEmails' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [], + 'authWhitelistIPs' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [], + 'authWhitelistDomains' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_DOMAINS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_DOMAINS', null)) : [], ], Database::SYSTEM_COLLECTION_COLLECTIONS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 2fc83c91d8..5bf9eff7e1 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -3,6 +3,7 @@ global $utopia, $register, $request, $response, $user, $audit, $webhook, $mail, $project, $projectDB, $clients; +use Utopia\App; use Utopia\Exception; use Utopia\Response; use Utopia\Config\Config; @@ -34,7 +35,7 @@ $oauthDefaultFailure = '/auth/oauth2/failure'; $oauth2Keys = []; -$utopia->init(function() use (&$oauth2Keys) { +App::init(function() use (&$oauth2Keys) { foreach (Config::getParam('providers') as $key => $provider) { if (!$provider['enabled']) { continue; @@ -45,7 +46,7 @@ $utopia->init(function() use (&$oauth2Keys) { } }, 'account'); -$utopia->post('/v1/account') +App::post('/v1/account') ->desc('Create Account') ->groups(['api', 'account']) ->label('webhook', 'account.create') @@ -146,7 +147,7 @@ $utopia->post('/v1/account') } ); -$utopia->post('/v1/account/sessions') +App::post('/v1/account/sessions') ->desc('Create Account Session') ->groups(['api', 'account']) ->label('webhook', 'account.sessions.create') @@ -237,7 +238,7 @@ $utopia->post('/v1/account/sessions') } ); -$utopia->get('/v1/account/sessions/oauth2/:provider') +App::get('/v1/account/sessions/oauth2/:provider') ->desc('Create Account Session with OAuth2') ->groups(['api', 'account']) ->label('error', __DIR__.'/../../views/general/error.phtml') @@ -265,7 +266,7 @@ $utopia->get('/v1/account/sessions/oauth2/:provider') $appSecret = \json_decode($appSecret, true); if (!empty($appSecret) && isset($appSecret['version'])) { - $key = $utopia->getEnv('_APP_OPENSSL_KEY_V'.$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'])); } @@ -288,7 +289,7 @@ $utopia->get('/v1/account/sessions/oauth2/:provider') } ); -$utopia->get('/v1/account/sessions/oauth2/callback/:provider/:projectId') +App::get('/v1/account/sessions/oauth2/callback/:provider/:projectId') ->desc('OAuth2 Callback') ->groups(['api', 'account']) ->label('error', __DIR__.'/../../views/general/error.phtml') @@ -311,7 +312,7 @@ $utopia->get('/v1/account/sessions/oauth2/callback/:provider/:projectId') } ); -$utopia->post('/v1/account/sessions/oauth2/callback/:provider/:projectId') +App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId') ->desc('OAuth2 Callback') ->groups(['api', 'account']) ->label('error', __DIR__.'/../../views/general/error.phtml') @@ -335,7 +336,7 @@ $utopia->post('/v1/account/sessions/oauth2/callback/:provider/:projectId') } ); -$utopia->get('/v1/account/sessions/oauth2/:provider/redirect') +App::get('/v1/account/sessions/oauth2/:provider/redirect') ->desc('OAuth2 Redirect') ->groups(['api', 'account']) ->label('error', __DIR__.'/../../views/general/error.phtml') @@ -360,7 +361,7 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect') $appSecret = \json_decode($appSecret, true); if (!empty($appSecret) && isset($appSecret['version'])) { - $key = $utopia->getEnv('_APP_OPENSSL_KEY_V'.$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'])); } @@ -531,7 +532,7 @@ $utopia->get('/v1/account/sessions/oauth2/:provider/redirect') } ); -$utopia->get('/v1/account') +App::get('/v1/account') ->desc('Get Account') ->groups(['api', 'account']) ->label('scope', 'account') @@ -555,7 +556,7 @@ $utopia->get('/v1/account') } ); -$utopia->get('/v1/account/prefs') +App::get('/v1/account/prefs') ->desc('Get Account Preferences') ->groups(['api', 'account']) ->label('scope', 'account') @@ -578,7 +579,7 @@ $utopia->get('/v1/account/prefs') } ); -$utopia->get('/v1/account/sessions') +App::get('/v1/account/sessions') ->desc('Get Account Sessions') ->groups(['api', 'account']) ->label('scope', 'account') @@ -637,7 +638,7 @@ $utopia->get('/v1/account/sessions') } ); -$utopia->get('/v1/account/logs') +App::get('/v1/account/logs') ->desc('Get Account Logs') ->groups(['api', 'account']) ->label('scope', 'account') @@ -710,7 +711,7 @@ $utopia->get('/v1/account/logs') } ); -$utopia->patch('/v1/account/name') +App::patch('/v1/account/name') ->desc('Update Account Name') ->groups(['api', 'account']) ->label('webhook', 'account.update.name') @@ -748,7 +749,7 @@ $utopia->patch('/v1/account/name') } ); -$utopia->patch('/v1/account/password') +App::patch('/v1/account/password') ->desc('Update Account Password') ->groups(['api', 'account']) ->label('webhook', 'account.update.password') @@ -791,7 +792,7 @@ $utopia->patch('/v1/account/password') } ); -$utopia->patch('/v1/account/email') +App::patch('/v1/account/email') ->desc('Update Account Email') ->groups(['api', 'account']) ->label('webhook', 'account.update.email') @@ -850,7 +851,7 @@ $utopia->patch('/v1/account/email') } ); -$utopia->patch('/v1/account/prefs') +App::patch('/v1/account/prefs') ->desc('Update Account Preferences') ->groups(['api', 'account']) ->label('webhook', 'account.update.prefs') @@ -891,7 +892,7 @@ $utopia->patch('/v1/account/prefs') } ); -$utopia->delete('/v1/account') +App::delete('/v1/account') ->desc('Delete Account') ->groups(['api', 'account']) ->label('webhook', 'account.delete') @@ -947,7 +948,7 @@ $utopia->delete('/v1/account') } ); -$utopia->delete('/v1/account/sessions/:sessionId') +App::delete('/v1/account/sessions/:sessionId') ->desc('Delete Account Session') ->groups(['api', 'account']) ->label('scope', 'account') @@ -1007,7 +1008,7 @@ $utopia->delete('/v1/account/sessions/:sessionId') } ); -$utopia->delete('/v1/account/sessions') +App::delete('/v1/account/sessions') ->desc('Delete All Account Sessions') ->groups(['api', 'account']) ->label('scope', 'account') @@ -1058,7 +1059,7 @@ $utopia->delete('/v1/account/sessions') } ); -$utopia->post('/v1/account/recovery') +App::post('/v1/account/recovery') ->desc('Create Password Recovery') ->groups(['api', 'account']) ->label('scope', 'public') @@ -1158,7 +1159,7 @@ $utopia->post('/v1/account/recovery') } ); -$utopia->put('/v1/account/recovery') +App::put('/v1/account/recovery') ->desc('Complete Password Recovery') ->groups(['api', 'account']) ->label('scope', 'public') @@ -1229,7 +1230,7 @@ $utopia->put('/v1/account/recovery') } ); -$utopia->post('/v1/account/verification') +App::post('/v1/account/verification') ->desc('Create Email Verification') ->groups(['api', 'account']) ->label('scope', 'account') @@ -1316,7 +1317,7 @@ $utopia->post('/v1/account/verification') } ); -$utopia->put('/v1/account/verification') +App::put('/v1/account/verification') ->desc('Complete Email Verification') ->groups(['api', 'account']) ->label('scope', 'public') diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index 08940b1196..f2c3c70897 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -2,6 +2,7 @@ global $utopia, $request, $response; +use Utopia\App; use Utopia\Exception; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; @@ -81,7 +82,7 @@ $avatarCallback = function ($type, $code, $width, $height, $quality) use ($respo unset($resize); }; -$utopia->get('/v1/avatars/credit-cards/:code') +App::get('/v1/avatars/credit-cards/:code') ->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'))).'.') @@ -98,7 +99,7 @@ $utopia->get('/v1/avatars/credit-cards/:code') return $avatarCallback('credit-cards', $code, $width, $height, $quality); }); -$utopia->get('/v1/avatars/browsers/:code') +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.') @@ -115,7 +116,7 @@ $utopia->get('/v1/avatars/browsers/:code') return $avatarCallback('browsers', $code, $width, $height, $quality); }); -$utopia->get('/v1/avatars/flags/:code') +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.') @@ -132,7 +133,7 @@ $utopia->get('/v1/avatars/flags/:code') return $avatarCallback('flags', $code, $width, $height, $quality); }); -$utopia->get('/v1/avatars/image') +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.') @@ -200,7 +201,7 @@ $utopia->get('/v1/avatars/image') } ); -$utopia->get('/v1/avatars/favicon') +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.') @@ -353,7 +354,7 @@ $utopia->get('/v1/avatars/favicon') } ); -$utopia->get('/v1/avatars/qr') +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.') @@ -387,7 +388,7 @@ $utopia->get('/v1/avatars/qr') } ); -$utopia->get('/v1/avatars/initials') +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) diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index fafed2624b..b2a7bd9f7b 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -25,7 +25,7 @@ use Appwrite\Database\Exception\Structure as StructureException; use DeviceDetector\DeviceDetector; use GeoIp2\Database\Reader; -$utopia->post('/v1/database/collections') +App::post('/v1/database/collections') ->desc('Create Collection') ->groups(['api', 'database']) ->label('webhook', 'database.collections.create') @@ -99,7 +99,7 @@ $utopia->post('/v1/database/collections') } ); -$utopia->get('/v1/database/collections') +App::get('/v1/database/collections') ->desc('List Collections') ->groups(['api', 'database']) ->label('scope', 'collections.read') @@ -148,7 +148,7 @@ $utopia->get('/v1/database/collections') } ); -$utopia->get('/v1/database/collections/:collectionId') +App::get('/v1/database/collections/:collectionId') ->desc('Get Collection') ->groups(['api', 'database']) ->label('scope', 'collections.read') @@ -169,7 +169,7 @@ $utopia->get('/v1/database/collections/:collectionId') } ); -// $utopia->get('/v1/database/collections/:collectionId/logs') +// App::get('/v1/database/collections/:collectionId/logs') // ->desc('Get Collection Logs') // ->groups(['api', 'database']) // ->label('scope', 'collections.read') @@ -234,7 +234,7 @@ $utopia->get('/v1/database/collections/:collectionId') // } // ); -$utopia->put('/v1/database/collections/:collectionId') +App::put('/v1/database/collections/:collectionId') ->desc('Update Collection') ->groups(['api', 'database']) ->label('scope', 'collections.write') @@ -307,7 +307,7 @@ $utopia->put('/v1/database/collections/:collectionId') } ); -$utopia->delete('/v1/database/collections/:collectionId') +App::delete('/v1/database/collections/:collectionId') ->desc('Delete Collection') ->groups(['api', 'database']) ->label('scope', 'collections.write') @@ -345,7 +345,7 @@ $utopia->delete('/v1/database/collections/:collectionId') } ); -$utopia->post('/v1/database/collections/:collectionId/documents') +App::post('/v1/database/collections/:collectionId/documents') ->desc('Create Document') ->groups(['api', 'database']) ->label('webhook', 'database.documents.create') @@ -465,7 +465,7 @@ $utopia->post('/v1/database/collections/:collectionId/documents') } ); -$utopia->get('/v1/database/collections/:collectionId/documents') +App::get('/v1/database/collections/:collectionId/documents') ->desc('List Documents') ->groups(['api', 'database']) ->label('scope', 'documents.read') @@ -508,7 +508,7 @@ $utopia->get('/v1/database/collections/:collectionId/documents') if ($first || $last) { $response->json((!empty($list) ? $list->getArrayCopy() : [])); } else { - if ($utopia->isDevelopment()) { + if (App::isDevelopment()) { $collection ->setAttribute('debug', $projectDB->getDebug()) ->setAttribute('limit', $limit) @@ -533,7 +533,7 @@ $utopia->get('/v1/database/collections/:collectionId/documents') } ); -$utopia->get('/v1/database/collections/:collectionId/documents/:documentId') +App::get('/v1/database/collections/:collectionId/documents/:documentId') ->desc('Get Document') ->groups(['api', 'database']) ->label('scope', 'documents.read') @@ -579,7 +579,7 @@ $utopia->get('/v1/database/collections/:collectionId/documents/:documentId') } ); -$utopia->patch('/v1/database/collections/:collectionId/documents/:documentId') +App::patch('/v1/database/collections/:collectionId/documents/:documentId') ->desc('Update Document') ->groups(['api', 'database']) ->label('webhook', 'database.documents.update') @@ -659,7 +659,7 @@ $utopia->patch('/v1/database/collections/:collectionId/documents/:documentId') } ); -$utopia->delete('/v1/database/collections/:collectionId/documents/:documentId') +App::delete('/v1/database/collections/:collectionId/documents/:documentId') ->desc('Delete Document') ->groups(['api', 'database']) ->label('scope', 'documents.write') diff --git a/app/controllers/api/graphql.php b/app/controllers/api/graphql.php index 0eecb23e15..295f6e7dd6 100644 --- a/app/controllers/api/graphql.php +++ b/app/controllers/api/graphql.php @@ -2,6 +2,8 @@ global $utopia; +use Utopia\App; + /** * TODO: * 1. Map all objects, object-params, object-fields @@ -12,7 +14,7 @@ global $utopia; * 6. Write tests! */ -$utopia->post('/v1/graphql') +App::post('/v1/graphql') ->desc('GraphQL Endpoint') ->groups(['api', 'graphql']) ->label('scope', 'public') diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index 46db6b65f6..7fdf56e362 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -2,12 +2,13 @@ global $utopia, $request, $response, $register; +use Utopia\App; use Utopia\Exception; use Appwrite\Storage\Device\Local; use Appwrite\Storage\Storage; use Appwrite\ClamAV\Network; -$utopia->get('/v1/health') +App::get('/v1/health') ->desc('Get HTTP') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -21,7 +22,7 @@ $utopia->get('/v1/health') } ); -$utopia->get('/v1/health/version') +App::get('/v1/health/version') ->desc('Get Version') ->groups(['api', 'health']) ->label('scope', 'public') @@ -31,7 +32,7 @@ $utopia->get('/v1/health/version') } ); -$utopia->get('/v1/health/db') +App::get('/v1/health/db') ->desc('Get DB') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -47,7 +48,7 @@ $utopia->get('/v1/health/db') } ); -$utopia->get('/v1/health/cache') +App::get('/v1/health/cache') ->desc('Get Cache') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -63,7 +64,7 @@ $utopia->get('/v1/health/cache') } ); -$utopia->get('/v1/health/time') +App::get('/v1/health/time') ->desc('Get Time') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -111,7 +112,7 @@ $utopia->get('/v1/health/time') } ); -$utopia->get('/v1/health/queue/webhooks') +App::get('/v1/health/queue/webhooks') ->desc('Get Webhooks Queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -125,7 +126,7 @@ $utopia->get('/v1/health/queue/webhooks') } ); -$utopia->get('/v1/health/queue/tasks') +App::get('/v1/health/queue/tasks') ->desc('Get Tasks Queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -139,7 +140,7 @@ $utopia->get('/v1/health/queue/tasks') } ); -$utopia->get('/v1/health/queue/logs') +App::get('/v1/health/queue/logs') ->desc('Get Logs Queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -153,7 +154,7 @@ $utopia->get('/v1/health/queue/logs') } ); -$utopia->get('/v1/health/queue/usage') +App::get('/v1/health/queue/usage') ->desc('Get Usage Queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -167,7 +168,7 @@ $utopia->get('/v1/health/queue/usage') } ); -$utopia->get('/v1/health/queue/certificates') +App::get('/v1/health/queue/certificates') ->desc('Get Certificate Queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -181,7 +182,7 @@ $utopia->get('/v1/health/queue/certificates') } ); -$utopia->get('/v1/health/queue/functions') +App::get('/v1/health/queue/functions') ->desc('Get Functions Queue') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -195,7 +196,7 @@ $utopia->get('/v1/health/queue/functions') } ); -$utopia->get('/v1/health/storage/local') +App::get('/v1/health/storage/local') ->desc('Get Local Storage') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -226,7 +227,7 @@ $utopia->get('/v1/health/storage/local') } ); -$utopia->get('/v1/health/anti-virus') +App::get('/v1/health/anti-virus') ->desc('Get Anti virus') ->groups(['api', 'health']) ->label('scope', 'health.read') @@ -249,7 +250,7 @@ $utopia->get('/v1/health/anti-virus') } ); -$utopia->get('/v1/health/stats') // Currently only used internally +App::get('/v1/health/stats') // Currently only used internally ->desc('Get System Stats') ->groups(['api', 'health']) ->label('scope', 'god') diff --git a/app/controllers/api/locale.php b/app/controllers/api/locale.php index 73995e0275..20650c2c1c 100644 --- a/app/controllers/api/locale.php +++ b/app/controllers/api/locale.php @@ -6,7 +6,7 @@ use Utopia\App; use Utopia\Locale\Locale; use GeoIp2\Database\Reader; -$utopia->get('/v1/locale') +App::get('/v1/locale') ->desc('Get User Locale') ->groups(['api', 'locale']) ->label('scope', 'locale.read') @@ -25,7 +25,7 @@ $utopia->get('/v1/locale') $countries = Locale::getText('countries'); $continents = Locale::getText('continents'); - if (App::MODE_TYPE_PRODUCTION !== $utopia->getMode()) { + if (!App::isProduction()) { $ip = '79.177.241.94'; } @@ -65,7 +65,7 @@ $utopia->get('/v1/locale') } ); -$utopia->get('/v1/locale/countries') +App::get('/v1/locale/countries') ->desc('List Countries') ->groups(['api', 'locale']) ->label('scope', 'locale.read') @@ -83,7 +83,7 @@ $utopia->get('/v1/locale/countries') } ); -$utopia->get('/v1/locale/countries/eu') +App::get('/v1/locale/countries/eu') ->desc('List EU Countries') ->groups(['api', 'locale']) ->label('scope', 'locale.read') @@ -109,7 +109,7 @@ $utopia->get('/v1/locale/countries/eu') } ); -$utopia->get('/v1/locale/countries/phones') +App::get('/v1/locale/countries/phones') ->desc('List Countries Phone Codes') ->groups(['api', 'locale']) ->label('scope', 'locale.read') @@ -135,7 +135,7 @@ $utopia->get('/v1/locale/countries/phones') } ); -$utopia->get('/v1/locale/continents') +App::get('/v1/locale/continents') ->desc('List Continents') ->groups(['api', 'locale']) ->label('scope', 'locale.read') @@ -154,7 +154,7 @@ $utopia->get('/v1/locale/continents') ); -$utopia->get('/v1/locale/currencies') +App::get('/v1/locale/currencies') ->desc('List Currencies') ->groups(['api', 'locale']) ->label('scope', 'locale.read') @@ -171,7 +171,7 @@ $utopia->get('/v1/locale/currencies') ); -$utopia->get('/v1/locale/languages') +App::get('/v1/locale/languages') ->desc('List Languages') ->groups(['api', 'locale']) ->label('scope', 'locale.read') diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 3311abbd0b..8dcba78430 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -2,6 +2,7 @@ global $utopia, $request, $response, $register, $user, $consoleDB, $projectDB, $deletes; +use Utopia\App; use Utopia\Exception; use Utopia\Response; use Utopia\Validator\ArrayList; @@ -23,7 +24,7 @@ use Cron\CronExpression; $scopes = include __DIR__.'/../../../app/config/scopes.php'; -$utopia->post('/v1/projects') +App::post('/v1/projects') ->desc('Create Project') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -86,7 +87,7 @@ $utopia->post('/v1/projects') } ); -$utopia->get('/v1/projects') +App::get('/v1/projects') ->desc('List Projects') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -120,7 +121,7 @@ $utopia->get('/v1/projects') } ); -$utopia->get('/v1/projects/:projectId') +App::get('/v1/projects/:projectId') ->desc('Get Project') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -148,7 +149,7 @@ $utopia->get('/v1/projects/:projectId') } ); -$utopia->get('/v1/projects/:projectId/usage') +App::get('/v1/projects/:projectId/usage') ->desc('Get Project') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -310,7 +311,7 @@ $utopia->get('/v1/projects/:projectId/usage') } ); -$utopia->patch('/v1/projects/:projectId') +App::patch('/v1/projects/:projectId') ->desc('Update Project') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -356,7 +357,7 @@ $utopia->patch('/v1/projects/:projectId') } ); -$utopia->patch('/v1/projects/:projectId/oauth2') +App::patch('/v1/projects/:projectId/oauth2') ->desc('Update Project OAuth2') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -398,7 +399,7 @@ $utopia->patch('/v1/projects/:projectId/oauth2') } ); -$utopia->delete('/v1/projects/:projectId') +App::delete('/v1/projects/:projectId') ->desc('Delete Project') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -444,7 +445,7 @@ $utopia->delete('/v1/projects/:projectId') // Webhooks -$utopia->post('/v1/projects/:projectId/webhooks') +App::post('/v1/projects/:projectId/webhooks') ->desc('Create Webhook') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -509,7 +510,7 @@ $utopia->post('/v1/projects/:projectId/webhooks') } ); -$utopia->get('/v1/projects/:projectId/webhooks') +App::get('/v1/projects/:projectId/webhooks') ->desc('List Webhooks') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -542,7 +543,7 @@ $utopia->get('/v1/projects/:projectId/webhooks') } ); -$utopia->get('/v1/projects/:projectId/webhooks/:webhookId') +App::get('/v1/projects/:projectId/webhooks/:webhookId') ->desc('Get Webhook') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -576,7 +577,7 @@ $utopia->get('/v1/projects/:projectId/webhooks/:webhookId') ); -$utopia->put('/v1/projects/:projectId/webhooks/:webhookId') +App::put('/v1/projects/:projectId/webhooks/:webhookId') ->desc('Update Webhook') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -632,7 +633,7 @@ $utopia->put('/v1/projects/:projectId/webhooks/:webhookId') } ); -$utopia->delete('/v1/projects/:projectId/webhooks/:webhookId') +App::delete('/v1/projects/:projectId/webhooks/:webhookId') ->desc('Delete Webhook') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -664,7 +665,7 @@ $utopia->delete('/v1/projects/:projectId/webhooks/:webhookId') // Keys -$utopia->post('/v1/projects/:projectId/keys') +App::post('/v1/projects/:projectId/keys') ->desc('Create Key') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -711,7 +712,7 @@ $utopia->post('/v1/projects/:projectId/keys') } ); -$utopia->get('/v1/projects/:projectId/keys') +App::get('/v1/projects/:projectId/keys') ->desc('List Keys') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -730,7 +731,7 @@ $utopia->get('/v1/projects/:projectId/keys') } ); -$utopia->get('/v1/projects/:projectId/keys/:keyId') +App::get('/v1/projects/:projectId/keys/:keyId') ->desc('Get Key') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -756,7 +757,7 @@ $utopia->get('/v1/projects/:projectId/keys/:keyId') } ); -$utopia->put('/v1/projects/:projectId/keys/:keyId') +App::put('/v1/projects/:projectId/keys/:keyId') ->desc('Update Key') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -793,7 +794,7 @@ $utopia->put('/v1/projects/:projectId/keys/:keyId') } ); -$utopia->delete('/v1/projects/:projectId/keys/:keyId') +App::delete('/v1/projects/:projectId/keys/:keyId') ->desc('Delete Key') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -825,7 +826,7 @@ $utopia->delete('/v1/projects/:projectId/keys/:keyId') // Tasks -$utopia->post('/v1/projects/:projectId/tasks') +App::post('/v1/projects/:projectId/tasks') ->desc('Create Task') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -908,7 +909,7 @@ $utopia->post('/v1/projects/:projectId/tasks') } ); -$utopia->get('/v1/projects/:projectId/tasks') +App::get('/v1/projects/:projectId/tasks') ->desc('List Tasks') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -941,7 +942,7 @@ $utopia->get('/v1/projects/:projectId/tasks') } ); -$utopia->get('/v1/projects/:projectId/tasks/:taskId') +App::get('/v1/projects/:projectId/tasks/:taskId') ->desc('Get Task') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -974,7 +975,7 @@ $utopia->get('/v1/projects/:projectId/tasks/:taskId') } ); -$utopia->put('/v1/projects/:projectId/tasks/:taskId') +App::put('/v1/projects/:projectId/tasks/:taskId') ->desc('Update Task') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1045,7 +1046,7 @@ $utopia->put('/v1/projects/:projectId/tasks/:taskId') } ); -$utopia->delete('/v1/projects/:projectId/tasks/:taskId') +App::delete('/v1/projects/:projectId/tasks/:taskId') ->desc('Delete Task') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1077,7 +1078,7 @@ $utopia->delete('/v1/projects/:projectId/tasks/:taskId') // Platforms -$utopia->post('/v1/projects/:projectId/platforms') +App::post('/v1/projects/:projectId/platforms') ->desc('Create Platform') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1131,7 +1132,7 @@ $utopia->post('/v1/projects/:projectId/platforms') } ); -$utopia->get('/v1/projects/:projectId/platforms') +App::get('/v1/projects/:projectId/platforms') ->desc('List Platforms') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -1152,7 +1153,7 @@ $utopia->get('/v1/projects/:projectId/platforms') } ); -$utopia->get('/v1/projects/:projectId/platforms/:platformId') +App::get('/v1/projects/:projectId/platforms/:platformId') ->desc('Get Platform') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -1178,7 +1179,7 @@ $utopia->get('/v1/projects/:projectId/platforms/:platformId') } ); -$utopia->put('/v1/projects/:projectId/platforms/:platformId') +App::put('/v1/projects/:projectId/platforms/:platformId') ->desc('Update Platform') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1220,7 +1221,7 @@ $utopia->put('/v1/projects/:projectId/platforms/:platformId') } ); -$utopia->delete('/v1/projects/:projectId/platforms/:platformId') +App::delete('/v1/projects/:projectId/platforms/:platformId') ->desc('Delete Platform') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1252,7 +1253,7 @@ $utopia->delete('/v1/projects/:projectId/platforms/:platformId') // Domains -$utopia->post('/v1/projects/:projectId/domains') +App::post('/v1/projects/:projectId/domains') ->desc('Create Domain') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1315,7 +1316,7 @@ $utopia->post('/v1/projects/:projectId/domains') } ); -$utopia->get('/v1/projects/:projectId/domains') +App::get('/v1/projects/:projectId/domains') ->desc('List Domains') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -1336,7 +1337,7 @@ $utopia->get('/v1/projects/:projectId/domains') } ); -$utopia->get('/v1/projects/:projectId/domains/:domainId') +App::get('/v1/projects/:projectId/domains/:domainId') ->desc('Get Domain') ->groups(['api', 'projects']) ->label('scope', 'projects.read') @@ -1362,7 +1363,7 @@ $utopia->get('/v1/projects/:projectId/domains/:domainId') } ); -$utopia->patch('/v1/projects/:projectId/domains/:domainId/verification') +App::patch('/v1/projects/:projectId/domains/:domainId/verification') ->desc('Update Domain Verification Status') ->groups(['api', 'projects']) ->label('scope', 'projects.write') @@ -1419,7 +1420,7 @@ $utopia->patch('/v1/projects/:projectId/domains/:domainId/verification') } ); -$utopia->delete('/v1/projects/:projectId/domains/:domainId') +App::delete('/v1/projects/:projectId/domains/:domainId') ->desc('Delete Domain') ->groups(['api', 'projects']) ->label('scope', 'projects.write') diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 9e6e376bbd..14b4ac2e1c 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -2,6 +2,7 @@ global $utopia, $request, $response, $user, $audit, $usage, $project, $projectDB; +use Utopia\App; use Utopia\Exception; use Utopia\Response; use Utopia\Validator\ArrayList; @@ -129,11 +130,11 @@ $mimes = [ 'application/pdf', ]; -$utopia->init(function () use ($project) { +App::init(function () use ($project) { Storage::addDevice('local', new Local(APP_STORAGE_UPLOADS.'/app-'.$project->getId())); }, 'storage'); -$utopia->post('/v1/storage/files') +App::post('/v1/storage/files') ->desc('Create File') ->groups(['api', 'storage']) ->label('scope', 'files.write') @@ -270,7 +271,7 @@ $utopia->post('/v1/storage/files') } ); -$utopia->get('/v1/storage/files') +App::get('/v1/storage/files') ->desc('List Files') ->groups(['api', 'storage']) ->label('scope', 'files.read') @@ -304,7 +305,7 @@ $utopia->get('/v1/storage/files') } ); -$utopia->get('/v1/storage/files/:fileId') +App::get('/v1/storage/files/:fileId') ->desc('Get File') ->groups(['api', 'storage']) ->label('scope', 'files.read') @@ -325,7 +326,7 @@ $utopia->get('/v1/storage/files/:fileId') } ); -$utopia->get('/v1/storage/files/:fileId/preview') +App::get('/v1/storage/files/:fileId/preview') ->desc('Get File Preview') ->groups(['api', 'storage']) ->label('scope', 'files.read') @@ -448,7 +449,7 @@ $utopia->get('/v1/storage/files/:fileId/preview') } ); -$utopia->get('/v1/storage/files/:fileId/download') +App::get('/v1/storage/files/:fileId/download') ->desc('Get File for Download') ->groups(['api', 'storage']) ->label('scope', 'files.read') @@ -502,7 +503,7 @@ $utopia->get('/v1/storage/files/:fileId/download') } ); -$utopia->get('/v1/storage/files/:fileId/view') +App::get('/v1/storage/files/:fileId/view') ->desc('Get File for View') ->groups(['api', 'storage']) ->label('scope', 'files.read') @@ -573,7 +574,7 @@ $utopia->get('/v1/storage/files/:fileId/view') } ); -$utopia->put('/v1/storage/files/:fileId') +App::put('/v1/storage/files/:fileId') ->desc('Update File') ->groups(['api', 'storage']) ->label('scope', 'files.write') @@ -619,7 +620,7 @@ $utopia->put('/v1/storage/files/:fileId') } ); -$utopia->delete('/v1/storage/files/:fileId') +App::delete('/v1/storage/files/:fileId') ->desc('Delete File') ->groups(['api', 'storage']) ->label('scope', 'files.write') @@ -662,7 +663,7 @@ $utopia->delete('/v1/storage/files/:fileId') } ); -// $utopia->get('/v1/storage/files/:fileId/scan') +// App::get('/v1/storage/files/:fileId/scan') // ->desc('Scan Storage') // ->groups(['api', 'storage']) // ->label('scope', 'god') diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 2e4ab99976..d9beb74b1d 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -2,6 +2,7 @@ global $utopia, $request, $response, $projectDB, $project, $user, $audit, $mail, $mode, $clients; +use Utopia\App; use Utopia\Exception; use Utopia\Response; use Utopia\Config\Config; @@ -20,7 +21,7 @@ use Appwrite\Database\Validator\Authorization; use Appwrite\Database\Exception\Duplicate; use Appwrite\Template\Template; -$utopia->post('/v1/teams') +App::post('/v1/teams') ->desc('Create Team') ->groups(['api', 'teams']) ->label('scope', 'teams.write') @@ -84,7 +85,7 @@ $utopia->post('/v1/teams') } ); -$utopia->get('/v1/teams') +App::get('/v1/teams') ->desc('List Teams') ->groups(['api', 'teams']) ->label('scope', 'teams.read') @@ -114,7 +115,7 @@ $utopia->get('/v1/teams') } ); -$utopia->get('/v1/teams/:teamId') +App::get('/v1/teams/:teamId') ->desc('Get Team') ->groups(['api', 'teams']) ->label('scope', 'teams.read') @@ -135,7 +136,7 @@ $utopia->get('/v1/teams/:teamId') } ); -$utopia->put('/v1/teams/:teamId') +App::put('/v1/teams/:teamId') ->desc('Update Team') ->groups(['api', 'teams']) ->label('scope', 'teams.write') @@ -165,7 +166,7 @@ $utopia->put('/v1/teams/:teamId') } ); -$utopia->delete('/v1/teams/:teamId') +App::delete('/v1/teams/:teamId') ->desc('Delete Team') ->groups(['api', 'teams']) ->label('scope', 'teams.write') @@ -205,7 +206,7 @@ $utopia->delete('/v1/teams/:teamId') } ); -$utopia->post('/v1/teams/:teamId/memberships') +App::post('/v1/teams/:teamId/memberships') ->desc('Create Team Membership') ->groups(['api', 'teams']) ->label('scope', 'teams.write') @@ -382,7 +383,7 @@ $utopia->post('/v1/teams/:teamId/memberships') } ); -$utopia->get('/v1/teams/:teamId/memberships') +App::get('/v1/teams/:teamId/memberships') ->desc('Get Team Memberships') ->groups(['api', 'teams']) ->label('scope', 'teams.read') @@ -440,7 +441,7 @@ $utopia->get('/v1/teams/:teamId/memberships') } ); -$utopia->patch('/v1/teams/:teamId/memberships/:inviteId/status') +App::patch('/v1/teams/:teamId/memberships/:inviteId/status') ->desc('Update Team Membership Status') ->groups(['api', 'teams']) ->label('scope', 'public') @@ -573,7 +574,7 @@ $utopia->patch('/v1/teams/:teamId/memberships/:inviteId/status') } ); -$utopia->delete('/v1/teams/:teamId/memberships/:inviteId') +App::delete('/v1/teams/:teamId/memberships/:inviteId') ->desc('Delete Team Membership') ->groups(['api', 'teams']) ->label('scope', 'teams.write') diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index d1744bf48a..36c27c542a 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -2,6 +2,7 @@ global $utopia, $response, $projectDB; +use Utopia\App; use Utopia\Exception; use Utopia\Response; use Utopia\Validator\Assoc; @@ -21,7 +22,7 @@ use Appwrite\Database\Validator\UID; use DeviceDetector\DeviceDetector; use GeoIp2\Database\Reader; -$utopia->post('/v1/users') +App::post('/v1/users') ->desc('Create User') ->groups(['api', 'users']) ->label('scope', 'users.write') @@ -91,7 +92,7 @@ $utopia->post('/v1/users') } ); -$utopia->get('/v1/users') +App::get('/v1/users') ->desc('List Users') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -146,7 +147,7 @@ $utopia->get('/v1/users') } ); -$utopia->get('/v1/users/:userId') +App::get('/v1/users/:userId') ->desc('Get User') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -188,7 +189,7 @@ $utopia->get('/v1/users/:userId') } ); -$utopia->get('/v1/users/:userId/prefs') +App::get('/v1/users/:userId/prefs') ->desc('Get User Preferences') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -218,7 +219,7 @@ $utopia->get('/v1/users/:userId/prefs') } ); -$utopia->get('/v1/users/:userId/sessions') +App::get('/v1/users/:userId/sessions') ->desc('Get User Sessions') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -282,7 +283,7 @@ $utopia->get('/v1/users/:userId/sessions') } ); -$utopia->get('/v1/users/:userId/logs') +App::get('/v1/users/:userId/logs') ->desc('Get User Logs') ->groups(['api', 'users']) ->label('scope', 'users.read') @@ -363,7 +364,7 @@ $utopia->get('/v1/users/:userId/logs') } ); -$utopia->patch('/v1/users/:userId/status') +App::patch('/v1/users/:userId/status') ->desc('Update User Status') ->groups(['api', 'users']) ->label('scope', 'users.write') @@ -412,7 +413,7 @@ $utopia->patch('/v1/users/:userId/status') } ); -$utopia->patch('/v1/users/:userId/prefs') +App::patch('/v1/users/:userId/prefs') ->desc('Update User Preferences') ->groups(['api', 'users']) ->label('scope', 'users.write') @@ -455,7 +456,7 @@ $utopia->patch('/v1/users/:userId/prefs') ); -$utopia->delete('/v1/users/:userId/sessions/:sessionId') +App::delete('/v1/users/:userId/sessions/:sessionId') ->desc('Delete User Session') ->groups(['api', 'users']) ->label('scope', 'users.write') @@ -488,7 +489,7 @@ $utopia->delete('/v1/users/:userId/sessions/:sessionId') } ); -$utopia->delete('/v1/users/:userId/sessions') +App::delete('/v1/users/:userId/sessions') ->desc('Delete User Sessions') ->groups(['api', 'users']) ->label('scope', 'users.write') diff --git a/app/controllers/mock.php b/app/controllers/mock.php index 097501fbba..e07559449d 100644 --- a/app/controllers/mock.php +++ b/app/controllers/mock.php @@ -2,16 +2,17 @@ global $utopia, $request, $response; +use Utopia\App; +use Utopia\Response; use Utopia\Validator\Numeric; use Utopia\Validator\Text; use Utopia\Validator\ArrayList; -use Utopia\Response; use Utopia\Validator\Host; use Appwrite\Storage\Validator\File; $result = []; -$utopia->get('/v1/mock/tests/foo') +App::get('/v1/mock/tests/foo') ->desc('Mock a get request for SDK tests') ->label('scope', 'public') ->label('sdk.namespace', 'foo') @@ -25,7 +26,7 @@ $utopia->get('/v1/mock/tests/foo') } ); -$utopia->post('/v1/mock/tests/foo') +App::post('/v1/mock/tests/foo') ->desc('Mock a post request for SDK tests') ->label('scope', 'public') ->label('sdk.namespace', 'foo') @@ -39,7 +40,7 @@ $utopia->post('/v1/mock/tests/foo') } ); -$utopia->patch('/v1/mock/tests/foo') +App::patch('/v1/mock/tests/foo') ->desc('Mock a patch request for SDK tests') ->label('scope', 'public') ->label('sdk.namespace', 'foo') @@ -53,7 +54,7 @@ $utopia->patch('/v1/mock/tests/foo') } ); -$utopia->put('/v1/mock/tests/foo') +App::put('/v1/mock/tests/foo') ->desc('Mock a put request for SDK tests') ->label('scope', 'public') ->label('sdk.namespace', 'foo') @@ -67,7 +68,7 @@ $utopia->put('/v1/mock/tests/foo') } ); -$utopia->delete('/v1/mock/tests/foo') +App::delete('/v1/mock/tests/foo') ->desc('Mock a delete request for SDK tests') ->label('scope', 'public') ->label('sdk.namespace', 'foo') @@ -81,7 +82,7 @@ $utopia->delete('/v1/mock/tests/foo') } ); -$utopia->get('/v1/mock/tests/bar') +App::get('/v1/mock/tests/bar') ->desc('Mock a get request for SDK tests') ->label('scope', 'public') ->label('sdk.namespace', 'bar') @@ -95,7 +96,7 @@ $utopia->get('/v1/mock/tests/bar') } ); -$utopia->post('/v1/mock/tests/bar') +App::post('/v1/mock/tests/bar') ->desc('Mock a post request for SDK tests') ->label('scope', 'public') ->label('sdk.namespace', 'bar') @@ -109,7 +110,7 @@ $utopia->post('/v1/mock/tests/bar') } ); -$utopia->patch('/v1/mock/tests/bar') +App::patch('/v1/mock/tests/bar') ->desc('Mock a patch request for SDK tests') ->label('scope', 'public') ->label('sdk.namespace', 'bar') @@ -123,7 +124,7 @@ $utopia->patch('/v1/mock/tests/bar') } ); -$utopia->put('/v1/mock/tests/bar') +App::put('/v1/mock/tests/bar') ->desc('Mock a put request for SDK tests') ->label('scope', 'public') ->label('sdk.namespace', 'bar') @@ -137,7 +138,7 @@ $utopia->put('/v1/mock/tests/bar') } ); -$utopia->delete('/v1/mock/tests/bar') +App::delete('/v1/mock/tests/bar') ->desc('Mock a delete request for SDK tests') ->label('scope', 'public') ->label('sdk.namespace', 'bar') @@ -151,7 +152,7 @@ $utopia->delete('/v1/mock/tests/bar') } ); -$utopia->post('/v1/mock/tests/general/upload') +App::post('/v1/mock/tests/general/upload') ->desc('Mock a post request for SDK tests') ->label('scope', 'public') ->label('sdk.namespace', 'general') @@ -189,7 +190,7 @@ $utopia->post('/v1/mock/tests/general/upload') } ); -$utopia->get('/v1/mock/tests/general/redirect') +App::get('/v1/mock/tests/general/redirect') ->desc('Mock a post request for SDK tests') ->label('scope', 'public') ->label('sdk.namespace', 'general') @@ -201,7 +202,7 @@ $utopia->get('/v1/mock/tests/general/redirect') } ); -$utopia->get('/v1/mock/tests/general/redirected') +App::get('/v1/mock/tests/general/redirected') ->desc('Mock a post request for SDK tests') ->label('scope', 'public') ->label('sdk.namespace', 'general') @@ -212,7 +213,7 @@ $utopia->get('/v1/mock/tests/general/redirected') } ); -$utopia->get('/v1/mock/tests/general/set-cookie') +App::get('/v1/mock/tests/general/set-cookie') ->desc('Mock a cookie request for SDK tests') ->label('scope', 'public') ->label('sdk.namespace', 'general') @@ -224,7 +225,7 @@ $utopia->get('/v1/mock/tests/general/set-cookie') } ); -$utopia->get('/v1/mock/tests/general/get-cookie') +App::get('/v1/mock/tests/general/get-cookie') ->desc('Mock a cookie request for SDK tests') ->label('scope', 'public') ->label('sdk.namespace', 'general') @@ -238,7 +239,7 @@ $utopia->get('/v1/mock/tests/general/get-cookie') } ); -$utopia->get('/v1/mock/tests/general/empty') +App::get('/v1/mock/tests/general/empty') ->desc('Mock a post request for SDK tests') ->label('scope', 'public') ->label('sdk.namespace', 'general') @@ -251,7 +252,7 @@ $utopia->get('/v1/mock/tests/general/empty') } ); -$utopia->get('/v1/mock/tests/general/oauth2') +App::get('/v1/mock/tests/general/oauth2') ->desc('Mock an OAuth2 login route') ->label('scope', 'public') ->label('docs', false) @@ -265,7 +266,7 @@ $utopia->get('/v1/mock/tests/general/oauth2') } ); -$utopia->get('/v1/mock/tests/general/oauth2/token') +App::get('/v1/mock/tests/general/oauth2/token') ->desc('Mock an OAuth2 login route') ->label('scope', 'public') ->label('docs', false) @@ -291,7 +292,7 @@ $utopia->get('/v1/mock/tests/general/oauth2/token') } ); -$utopia->get('/v1/mock/tests/general/oauth2/user') +App::get('/v1/mock/tests/general/oauth2/user') ->desc('Mock an OAuth2 user route') ->label('scope', 'public') ->label('docs', false) @@ -310,7 +311,7 @@ $utopia->get('/v1/mock/tests/general/oauth2/user') } ); -$utopia->get('/v1/mock/tests/general/oauth2/success') +App::get('/v1/mock/tests/general/oauth2/success') ->label('scope', 'public') ->label('docs', false) ->action( @@ -321,7 +322,7 @@ $utopia->get('/v1/mock/tests/general/oauth2/success') } ); -$utopia->get('/v1/mock/tests/general/oauth2/failure') +App::get('/v1/mock/tests/general/oauth2/failure') ->label('scope', 'public') ->label('docs', false) ->action( @@ -334,7 +335,7 @@ $utopia->get('/v1/mock/tests/general/oauth2/failure') } ); -$utopia->shutdown(function() use ($response, $request, &$result, $utopia) { +App::shutdown(function() use ($response, $request, &$result, $utopia) { $route = $utopia->match($request); $path = APP_STORAGE_CACHE.'/tests.json'; $tests = (\file_exists($path)) ? \json_decode(\file_get_contents($path), true) : []; diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index e911c2c556..3fd17c47ff 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -1,12 +1,13 @@ init(function () use ($utopia, $request, $response, $register, $user, $project) { +App::init(function () use ($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 diff --git a/app/controllers/shared/web.php b/app/controllers/shared/web.php index a2f85bcb0a..9bf879c126 100644 --- a/app/controllers/shared/web.php +++ b/app/controllers/shared/web.php @@ -1,11 +1,12 @@ init(function () use ($utopia, $response, $request, $layout) { +App::init(function () use ($utopia, $response, $request, $layout) { /* AJAX check */ if (!empty($request->getQuery('version', ''))) { @@ -24,7 +25,7 @@ $utopia->init(function () use ($utopia, $response, $request, $layout) { ['type' => 'developer', 'label' => 'Developer'], ['type' => 'admin', 'label' => 'Admin'], ]) - ->setParam('env', $utopia->getMode()) + ->setParam('env', App::getMode()) ; $time = (60 * 60 * 24 * 45); // 45 days cache diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php index 595d36d33a..756263d7eb 100644 --- a/app/controllers/web/console.php +++ b/app/controllers/web/console.php @@ -2,6 +2,7 @@ global $utopia, $response, $request, $layout, $projectDB; +use Utopia\App; use Utopia\View; use Utopia\Config\Config; use Utopia\Domains\Domain; @@ -10,14 +11,14 @@ use Appwrite\Database\Validator\Authorization; use Appwrite\Database\Validator\UID; use Appwrite\Storage\Storage; -$utopia->init(function () use ($layout) { +App::init(function () use ($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'); -$utopia->shutdown(function () use ($response, $request, $layout) { +App::shutdown(function () use ($response, $request, $layout) { $header = new View(__DIR__.'/../../views/console/comps/header.phtml'); $footer = new View(__DIR__.'/../../views/console/comps/footer.phtml'); @@ -34,7 +35,7 @@ $utopia->shutdown(function () use ($response, $request, $layout) { $response->send($layout->render()); }, 'console'); -$utopia->get('/error/:code') +App::get('/error/:code') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'home') @@ -51,7 +52,7 @@ $utopia->get('/error/:code') ->setParam('body', $page); }); -$utopia->get('/console') +App::get('/console') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') @@ -67,7 +68,7 @@ $utopia->get('/console') ->setParam('body', $page); }); -$utopia->get('/console/account') +App::get('/console/account') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') @@ -85,7 +86,7 @@ $utopia->get('/console/account') ->setParam('body', $page); }); -$utopia->get('/console/notifications') +App::get('/console/notifications') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') @@ -97,7 +98,7 @@ $utopia->get('/console/notifications') ->setParam('body', $page); }); -$utopia->get('/console/home') +App::get('/console/home') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') @@ -109,7 +110,7 @@ $utopia->get('/console/home') ->setParam('body', $page); }); -$utopia->get('/console/settings') +App::get('/console/settings') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') @@ -128,7 +129,7 @@ $utopia->get('/console/settings') ->setParam('body', $page); }); -$utopia->get('/console/webhooks') +App::get('/console/webhooks') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') @@ -144,7 +145,7 @@ $utopia->get('/console/webhooks') ->setParam('body', $page); }); -$utopia->get('/console/keys') +App::get('/console/keys') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') @@ -159,7 +160,7 @@ $utopia->get('/console/keys') ->setParam('body', $page); }); -$utopia->get('/console/tasks') +App::get('/console/tasks') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') @@ -171,7 +172,7 @@ $utopia->get('/console/tasks') ->setParam('body', $page); }); -$utopia->get('/console/database') +App::get('/console/database') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') @@ -183,7 +184,7 @@ $utopia->get('/console/database') ->setParam('body', $page); }); -$utopia->get('/console/database/collection') +App::get('/console/database/collection') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') @@ -215,7 +216,7 @@ $utopia->get('/console/database/collection') ; }); -$utopia->get('/console/database/document') +App::get('/console/database/document') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') @@ -245,7 +246,7 @@ $utopia->get('/console/database/document') ->setParam('body', $page); }); -$utopia->get('/console/storage') +App::get('/console/storage') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') @@ -263,7 +264,7 @@ $utopia->get('/console/storage') ->setParam('body', $page); }); -$utopia->get('/console/users') +App::get('/console/users') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') @@ -277,7 +278,7 @@ $utopia->get('/console/users') ->setParam('body', $page); }); -$utopia->get('/console/users/user') +App::get('/console/users/user') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') @@ -289,7 +290,7 @@ $utopia->get('/console/users/user') ->setParam('body', $page); }); -$utopia->get('/console/users/teams/team') +App::get('/console/users/teams/team') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index 65097d36c3..4c0f63351f 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -2,12 +2,13 @@ global $utopia, $response, $request, $layout; +use Utopia\App; use Utopia\View; use Utopia\Config\Config; use Utopia\Validator\WhiteList; use Utopia\Validator\Range; -$utopia->init(function () use ($layout) { +App::init(function () use ($layout) { $header = new View(__DIR__.'/../../views/home/comps/header.phtml'); $footer = new View(__DIR__.'/../../views/home/comps/footer.phtml'); @@ -25,11 +26,11 @@ $utopia->init(function () use ($layout) { ; }, 'home'); -$utopia->shutdown(function () use ($response, $layout) { +App::shutdown(function () use ($response, $layout) { $response->send($layout->render()); }, 'home'); -$utopia->get('/') +App::get('/') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') @@ -39,7 +40,7 @@ $utopia->get('/') } ); -$utopia->get('/auth/signin') +App::get('/auth/signin') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') @@ -51,7 +52,7 @@ $utopia->get('/auth/signin') ->setParam('body', $page); }); -$utopia->get('/auth/signup') +App::get('/auth/signup') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') @@ -63,7 +64,7 @@ $utopia->get('/auth/signup') ->setParam('body', $page); }); -$utopia->get('/auth/recovery') +App::get('/auth/recovery') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') @@ -75,7 +76,7 @@ $utopia->get('/auth/recovery') ->setParam('body', $page); }); -$utopia->get('/auth/confirm') +App::get('/auth/confirm') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') @@ -87,7 +88,7 @@ $utopia->get('/auth/confirm') ->setParam('body', $page); }); -$utopia->get('/auth/join') +App::get('/auth/join') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') @@ -99,7 +100,7 @@ $utopia->get('/auth/join') ->setParam('body', $page); }); -$utopia->get('/auth/recovery/reset') +App::get('/auth/recovery/reset') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') @@ -112,7 +113,7 @@ $utopia->get('/auth/recovery/reset') }); -$utopia->get('/auth/oauth2/success') +App::get('/auth/oauth2/success') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') @@ -127,7 +128,7 @@ $utopia->get('/auth/oauth2/success') ; }); -$utopia->get('/auth/oauth2/failure') +App::get('/auth/oauth2/failure') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') @@ -142,7 +143,7 @@ $utopia->get('/auth/oauth2/failure') ; }); -$utopia->get('/error/:code') +App::get('/error/:code') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') @@ -159,7 +160,7 @@ $utopia->get('/error/:code') ->setParam('body', $page); }); -$utopia->get('/open-api-2.json') +App::get('/open-api-2.json') ->groups(['web', 'home']) ->label('scope', 'public') ->label('docs', false) diff --git a/app/init.php b/app/init.php index 825894f556..1d81e48806 100644 --- a/app/init.php +++ b/app/init.php @@ -53,7 +53,7 @@ $request = new Request(); $response = new Response(); $utopia = new App('Asia/Tel_Aviv'); -$utopia->setMode($utopia->getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION)); +App::setMode(App::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION)); /* * ENV vars @@ -69,16 +69,16 @@ 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::setParam('env', $utopia->getMode()); +Config::setParam('env', App::getMode()); Config::setParam('domain', $request->getServer('HTTP_HOST', '')); Config::setParam('domainVerification', false); -Config::setParam('version', $utopia->getEnv('_APP_VERSION', 'UNKNOWN')); +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)); -Resque::setBackend($utopia->getEnv('_APP_REDIS_HOST', '') - .':'.$utopia->getEnv('_APP_REDIS_PORT', '')); +Resque::setBackend(App::getEnv('_APP_REDIS_HOST', '') + .':'.App::getEnv('_APP_REDIS_PORT', '')); \define('COOKIE_DOMAIN', ( @@ -95,10 +95,10 @@ Resque::setBackend($utopia->getEnv('_APP_REDIS_HOST', '') * Registry */ $register->set('db', function () use ($utopia) { // Register DB connection - $dbHost = $utopia->getEnv('_APP_DB_HOST', ''); - $dbUser = $utopia->getEnv('_APP_DB_USER', ''); - $dbPass = $utopia->getEnv('_APP_DB_PASS', ''); - $dbScheme = $utopia->getEnv('_APP_DB_SCHEMA', ''); + $dbHost = App::getEnv('_APP_DB_HOST', ''); + $dbUser = App::getEnv('_APP_DB_USER', ''); + $dbPass = App::getEnv('_APP_DB_PASS', ''); + $dbScheme = App::getEnv('_APP_DB_SCHEMA', ''); $pdo = new PDO("mysql:host={$dbHost};dbname={$dbScheme};charset=utf8mb4", $dbUser, $dbPass, array( PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', @@ -112,8 +112,8 @@ $register->set('db', function () use ($utopia) { // Register DB connection return $pdo; }); $register->set('influxdb', function () use ($utopia) { // Register DB connection - $host = $utopia->getEnv('_APP_INFLUXDB_HOST', ''); - $port = $utopia->getEnv('_APP_INFLUXDB_PORT', ''); + $host = App::getEnv('_APP_INFLUXDB_HOST', ''); + $port = App::getEnv('_APP_INFLUXDB_PORT', ''); if (empty($host) || empty($port)) { return; @@ -124,8 +124,8 @@ $register->set('influxdb', function () use ($utopia) { // Register DB connection return $client; }); $register->set('statsd', function () use ($utopia) { // Register DB connection - $host = $utopia->getEnv('_APP_STATSD_HOST', 'telegraf'); - $port = $utopia->getEnv('_APP_STATSD_PORT', 8125); + $host = App::getEnv('_APP_STATSD_HOST', 'telegraf'); + $port = App::getEnv('_APP_STATSD_PORT', 8125); $connection = new \Domnikl\Statsd\Connection\UdpSocket($host, $port); $statsd = new \Domnikl\Statsd\Client($connection); @@ -135,8 +135,8 @@ $register->set('statsd', function () use ($utopia) { // Register DB connection $register->set('cache', function () use ($utopia) { // Register cache connection $redis = new Redis(); - $redis->connect($utopia->getEnv('_APP_REDIS_HOST', ''), - $utopia->getEnv('_APP_REDIS_PORT', '')); + $redis->connect(App::getEnv('_APP_REDIS_HOST', ''), + App::getEnv('_APP_REDIS_PORT', '')); return $redis; }); @@ -145,21 +145,21 @@ $register->set('smtp', function () use ($utopia) { $mail->isSMTP(); - $username = $utopia->getEnv('_APP_SMTP_USERNAME', null); - $password = $utopia->getEnv('_APP_SMTP_PASSWORD', null); + $username = App::getEnv('_APP_SMTP_USERNAME', null); + $password = App::getEnv('_APP_SMTP_PASSWORD', null); $mail->XMailer = 'Appwrite Mailer'; - $mail->Host = $utopia->getEnv('_APP_SMTP_HOST', 'smtp'); - $mail->Port = $utopia->getEnv('_APP_SMTP_PORT', 25); + $mail->Host = App::getEnv('_APP_SMTP_HOST', 'smtp'); + $mail->Port = App::getEnv('_APP_SMTP_PORT', 25); $mail->SMTPAuth = (!empty($username) && !empty($password)); $mail->Username = $username; $mail->Password = $password; - $mail->SMTPSecure = $utopia->getEnv('_APP_SMTP_SECURE', false); + $mail->SMTPSecure = App::getEnv('_APP_SMTP_SECURE', false); $mail->SMTPAutoTLS = false; $mail->CharSet = 'UTF-8'; - $from = \urldecode($utopia->getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server')); - $email = $utopia->getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM); + $from = \urldecode(App::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server')); + $email = App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM); $mail->setFrom($email, $from); $mail->addReplyTo($email, $from); @@ -249,7 +249,7 @@ if (\in_array($locale, Config::getParam('locales'))) { 'method' => 'GET', 'user_agent' => \sprintf(APP_USERAGENT, Config::getParam('version'), - $utopia->getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)), + App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY)), 'timeout' => 2, ], ]); @@ -329,9 +329,9 @@ if (APP_MODE_ADMIN === $mode) { // Set project mail $register->get('smtp') ->setFrom( - $utopia->getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), + App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), ($project->getId() === 'console') - ? \urldecode($utopia->getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server')) + ? \urldecode(App::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME.' Server')) : \sprintf(Locale::getText('account.emails.team'), $project->getAttribute('name') ) ); diff --git a/composer.json b/composer.json index 78b0fd76cc..62ef9fea9f 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "appwrite/php-clamav": "1.0.*", - "utopia-php/framework": "0.4.0", + "utopia-php/framework": "0.6.0", "utopia-php/abuse": "0.2.*", "utopia-php/audit": "0.3.*", "utopia-php/cache": "0.2.*", diff --git a/composer.lock b/composer.lock index a7d030f06a..c871ced2d1 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": "7f6cbe77fe2e0f8bdff33c37a4d9ca11", + "content-hash": "54f5e7c2291eb22ff442d2c491fa4eca", "packages": [ { "name": "appwrite/php-clamav", @@ -1596,16 +1596,16 @@ }, { "name": "utopia-php/framework", - "version": "0.4.0", + "version": "0.6.0", "source": { "type": "git", "url": "https://github.com/utopia-php/framework.git", - "reference": "30aeb2aeecf8ea2ab83242efad0f5f9fab8d4be5" + "reference": "5412a080f6fdf99310f20a803a797ae97de8b539" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/framework/zipball/30aeb2aeecf8ea2ab83242efad0f5f9fab8d4be5", - "reference": "30aeb2aeecf8ea2ab83242efad0f5f9fab8d4be5", + "url": "https://api.github.com/repos/utopia-php/framework/zipball/5412a080f6fdf99310f20a803a797ae97de8b539", + "reference": "5412a080f6fdf99310f20a803a797ae97de8b539", "shasum": "" }, "require": { @@ -1636,7 +1636,7 @@ "php", "upf" ], - "time": "2020-06-25T18:21:48+00:00" + "time": "2020-06-28T16:54:35+00:00" }, { "name": "utopia-php/locale", @@ -1942,16 +1942,16 @@ "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "a491d65139e2411c75704e871dd02bdddf5a4bdc" + "reference": "5796d127b0c4ff505b77455148ea9d5269d99758" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/a491d65139e2411c75704e871dd02bdddf5a4bdc", - "reference": "a491d65139e2411c75704e871dd02bdddf5a4bdc", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5796d127b0c4ff505b77455148ea9d5269d99758", + "reference": "5796d127b0c4ff505b77455148ea9d5269d99758", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.1 || ^8.0" }, "replace": { "myclabs/deep-copy": "self.version" @@ -1982,7 +1982,7 @@ "object", "object graph" ], - "time": "2020-03-12T21:49:07+00:00" + "time": "2020-06-28T07:02:41+00:00" }, { "name": "phar-io/manifest", From 655aecd29dbb2a5be540e4962c7c907b78c6a4a8 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 28 Jun 2020 22:28:18 +0300 Subject: [PATCH 11/27] Cleaned up globals --- app/controllers/api/account.php | 6 ++-- app/controllers/api/avatars.php | 6 ++-- app/controllers/api/database.php | 5 ++- app/controllers/api/graphql.php | 2 -- app/controllers/api/health.php | 2 +- app/controllers/api/locale.php | 4 +-- app/controllers/api/projects.php | 56 +++++++++++++++--------------- app/controllers/api/storage.php | 18 +++++----- app/controllers/api/teams.php | 2 +- app/controllers/api/users.php | 2 +- app/controllers/shared/api.php | 2 +- src/Appwrite/Database/Database.php | 2 +- 12 files changed, 52 insertions(+), 55 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 57feefcdbd..a794cb36c4 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1,6 +1,6 @@ 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('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, $utopia, $project) { + 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', ''); @@ -347,7 +347,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->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, $utopia, $user, $projectDB, $project, $audit, $oauthDefaultSuccess) { + 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' => '']; diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index 5396f5b44b..9aa07827f0 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -1,6 +1,6 @@ label('sdk.methodType', 'location') ->label('sdk.description', '/docs/references/avatars/get-favicon.md') ->action( - function ($url) use ($response, $request) { + function ($url) use ($response) { $width = 56; $height = 56; $quality = 80; @@ -246,7 +246,7 @@ App::get('/v1/avatars/favicon') CURLOPT_URL => $url, CURLOPT_USERAGENT => \sprintf(APP_USERAGENT, Config::getParam('version'), - $request->getServer('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY) + App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY) ), ]); diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index c194b3e35b..fc505bcf08 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -1,11 +1,10 @@ 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, $utopia) { + function ($collectionId, $filters, $offset, $limit, $orderField, $orderType, $orderCast, $search) use ($response, $projectDB) { $collection = $projectDB->getDocument($collectionId, false); if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { diff --git a/app/controllers/api/graphql.php b/app/controllers/api/graphql.php index 295f6e7dd6..cf72731c6a 100644 --- a/app/controllers/api/graphql.php +++ b/app/controllers/api/graphql.php @@ -1,7 +1,5 @@ label('sdk.method', 'get') ->label('sdk.description', '/docs/references/locale/get-locale.md') ->action( - function () use ($response, $request, $utopia) { + 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'); diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 3729e967d2..4b366efa2e 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -1,6 +1,6 @@ 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, $user, $consoleDB, $projectDB) { + function ($name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId) use ($response, $consoleDB, $projectDB) { $team = $projectDB->getDocument($teamId); if (empty($team->getId()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) { @@ -94,7 +94,7 @@ App::get('/v1/projects') ->label('sdk.namespace', 'projects') ->label('sdk.method', 'list') ->action( - function () use ($request, $response, $consoleDB) { + function () use ($response, $consoleDB) { $results = $consoleDB->getCollection([ 'limit' => 20, 'offset' => 0, @@ -111,7 +111,7 @@ App::get('/v1/projects') $secret = \json_decode($project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}'), true); if (!empty($secret) && isset($secret['version'])) { - $key = $request->getServer('_APP_OPENSSL_KEY_V'.$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']))); } } @@ -129,7 +129,7 @@ App::get('/v1/projects/:projectId') ->label('sdk.method', 'get') ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.') ->action( - function ($projectId) use ($request, $response, $consoleDB) { + function ($projectId) use ($response, $consoleDB) { $project = $consoleDB->getDocument($projectId); if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { @@ -140,7 +140,7 @@ App::get('/v1/projects/:projectId') $secret = \json_decode($project->getAttribute('usersOauth2'.\ucfirst($provider).'Secret', '{}'), true); if (!empty($secret) && isset($secret['version'])) { - $key = $request->getServer('_APP_OPENSSL_KEY_V'.$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']))); } } @@ -368,14 +368,14 @@ App::patch('/v1/projects/:projectId/oauth2') ->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 ($request, $response, $consoleDB) { + function ($projectId, $provider, $appId, $secret) use ($response, $consoleDB) { $project = $consoleDB->getDocument($projectId); if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { throw new Exception('Project not found', 404); } - $key = $request->getServer('_APP_OPENSSL_KEY_V1'); + $key = App::getEnv('_APP_OPENSSL_KEY_V1'); $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); $tag = null; $secret = \json_encode([ @@ -459,7 +459,7 @@ App::post('/v1/projects/:projectId/webhooks') ->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 ($request, $response, $consoleDB) { + function ($projectId, $name, $events, $url, $security, $httpUser, $httpPass) use ($response, $consoleDB) { $project = $consoleDB->getDocument($projectId); if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { @@ -467,7 +467,7 @@ App::post('/v1/projects/:projectId/webhooks') } $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); - $key = $request->getServer('_APP_OPENSSL_KEY_V1'); + $key = App::getEnv('_APP_OPENSSL_KEY_V1'); $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); $tag = null; $httpPass = \json_encode([ @@ -519,7 +519,7 @@ App::get('/v1/projects/:projectId/webhooks') ->label('sdk.method', 'listWebhooks') ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.') ->action( - function ($projectId) use ($request, $response, $consoleDB) { + function ($projectId) use ($response, $consoleDB) { $project = $consoleDB->getDocument($projectId); if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { @@ -535,7 +535,7 @@ App::get('/v1/projects/:projectId/webhooks') continue; } - $key = $request->getServer('_APP_OPENSSL_KEY_V'.$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']))); } @@ -553,7 +553,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId') ->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 ($request, $response, $consoleDB) { + function ($projectId, $webhookId) use ($response, $consoleDB) { $project = $consoleDB->getDocument($projectId); if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { @@ -569,7 +569,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId') $httpPass = \json_decode($webhook->getAttribute('httpPass', '{}'), true); if (!empty($httpPass) && isset($httpPass['version'])) { - $key = $request->getServer('_APP_OPENSSL_KEY_V'.$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']))); } @@ -592,7 +592,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') ->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 ($request, $response, $consoleDB) { + function ($projectId, $webhookId, $name, $events, $url, $security, $httpUser, $httpPass) use ($response, $consoleDB) { $project = $consoleDB->getDocument($projectId); if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { @@ -600,7 +600,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') } $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); - $key = $request->getServer('_APP_OPENSSL_KEY_V1'); + $key = App::getEnv('_APP_OPENSSL_KEY_V1'); $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); $tag = null; $httpPass = \json_encode([ @@ -843,7 +843,7 @@ App::post('/v1/projects/:projectId/tasks') ->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 ($request, $response, $consoleDB) { + function ($projectId, $name, $status, $schedule, $security, $httpMethod, $httpUrl, $httpHeaders, $httpUser, $httpPass) use ($response, $consoleDB) { $project = $consoleDB->getDocument($projectId); if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { @@ -854,7 +854,7 @@ App::post('/v1/projects/:projectId/tasks') $next = ($status == 'play') ? $cron->getNextRunDate()->format('U') : null; $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); - $key = $request->getServer('_APP_OPENSSL_KEY_V1'); + $key = App::getEnv('_APP_OPENSSL_KEY_V1'); $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); $tag = null; $httpPass = \json_encode([ @@ -918,7 +918,7 @@ App::get('/v1/projects/:projectId/tasks') ->label('sdk.method', 'listTasks') ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.') ->action( - function ($projectId) use ($request, $response, $consoleDB) { + function ($projectId) use ($response, $consoleDB) { $project = $consoleDB->getDocument($projectId); if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { @@ -934,7 +934,7 @@ App::get('/v1/projects/:projectId/tasks') continue; } - $key = $request->getServer('_APP_OPENSSL_KEY_V'.$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']))); } @@ -952,7 +952,7 @@ App::get('/v1/projects/:projectId/tasks/:taskId') ->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 ($request, $response, $consoleDB) { + function ($projectId, $taskId) use ($response, $consoleDB) { $project = $consoleDB->getDocument($projectId); if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { @@ -968,7 +968,7 @@ App::get('/v1/projects/:projectId/tasks/:taskId') $httpPass = \json_decode($task->getAttribute('httpPass', '{}'), true); if (!empty($httpPass) && isset($httpPass['version'])) { - $key = $request->getServer('_APP_OPENSSL_KEY_V'.$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']))); } @@ -994,7 +994,7 @@ App::put('/v1/projects/:projectId/tasks/:taskId') ->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 ($request, $response, $consoleDB) { + function ($projectId, $taskId, $name, $status, $schedule, $security, $httpMethod, $httpUrl, $httpHeaders, $httpUser, $httpPass) use ($response, $consoleDB) { $project = $consoleDB->getDocument($projectId); if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { @@ -1011,7 +1011,7 @@ App::put('/v1/projects/:projectId/tasks/:taskId') $next = ($status == 'play') ? $cron->getNextRunDate()->format('U') : null; $security = ($security === '1' || $security === 'true' || $security === 1 || $security === true); - $key = $request->getServer('_APP_OPENSSL_KEY_V1'); + $key = App::getEnv('_APP_OPENSSL_KEY_V1'); $iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM)); $tag = null; $httpPass = \json_encode([ @@ -1264,7 +1264,7 @@ App::post('/v1/projects/:projectId/domains') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('domain', null, function () { return new DomainValidator(); }, 'Domain name.') ->action( - function ($projectId, $domain) use ($request, $response, $consoleDB) { + function ($projectId, $domain) use ($response, $consoleDB) { $project = $consoleDB->getDocument($projectId); if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { @@ -1277,7 +1277,7 @@ App::post('/v1/projects/:projectId/domains') throw new Exception('Domain already exists', 409); } - $target = new Domain($request->getServer('_APP_DOMAIN_TARGET', '')); + $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); @@ -1374,7 +1374,7 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') ->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 ($request, $response, $consoleDB) { + function ($projectId, $domainId) use ($response, $consoleDB) { $project = $consoleDB->getDocument($projectId); if (empty($project->getId()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) { @@ -1387,7 +1387,7 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') throw new Exception('Domain not found', 404); } - $target = new Domain($request->getServer('_APP_DOMAIN_TARGET', '')); + $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); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 14b4ac2e1c..82083a23c7 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -1,6 +1,6 @@ getServer('_APP_STORAGE_LIMIT', 0)); + $fileSize = new FileSize(App::getEnv('_APP_STORAGE_LIMIT', 0)); $upload = new Upload(); if (empty($file)) { @@ -200,7 +200,7 @@ App::post('/v1/storage/files') $mimeType = $device->getFileMimeType($path); // Get mime-type before compression and encryption - if ($request->getServer('_APP_STORAGE_ANTIVIRUS') === 'enabled') { // Check if scans are enabled + 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 @@ -214,7 +214,7 @@ App::post('/v1/storage/files') $compressor = new GZIP(); $data = $device->read($path); $data = $compressor->compress($data); - $key = $request->getServer('_APP_OPENSSL_KEY_V1'); + $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); @@ -411,7 +411,7 @@ App::get('/v1/storage/files/:fileId/preview') $source = OpenSSL::decrypt( $source, $file->getAttribute('fileOpenSSLCipher'), - $request->getServer('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')), + App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')), 0, \hex2bin($file->getAttribute('fileOpenSSLIV')), \hex2bin($file->getAttribute('fileOpenSSLTag')) @@ -461,7 +461,7 @@ App::get('/v1/storage/files/:fileId/download') ->label('sdk.methodType', 'location') ->param('fileId', '', function () { return new UID(); }, 'File unique ID.') ->action( - function ($fileId) use ($response, $request, $projectDB) { + function ($fileId) use ($response, $projectDB) { $file = $projectDB->getDocument($fileId); if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { @@ -483,7 +483,7 @@ App::get('/v1/storage/files/:fileId/download') $source = OpenSSL::decrypt( $source, $file->getAttribute('fileOpenSSLCipher'), - $request->getServer('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')), + App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')), 0, \hex2bin($file->getAttribute('fileOpenSSLIV')), \hex2bin($file->getAttribute('fileOpenSSLTag')) @@ -516,7 +516,7 @@ 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, $request, $projectDB, $mimes) { + function ($fileId, $as) use ($response, $projectDB, $mimes) { $file = $projectDB->getDocument($fileId); if (empty($file->getId()) || Database::SYSTEM_COLLECTION_FILES != $file->getCollection()) { @@ -544,7 +544,7 @@ App::get('/v1/storage/files/:fileId/view') $source = OpenSSL::decrypt( $source, $file->getAttribute('fileOpenSSLCipher'), - $request->getServer('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')), + App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')), 0, \hex2bin($file->getAttribute('fileOpenSSLIV')), \hex2bin($file->getAttribute('fileOpenSSLTag')) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 7317b74c7b..76a61aef3c 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -1,6 +1,6 @@ check() && $request->getServer('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') { + if ($abuse->check() && App::getEnv('_APP_OPTIONS_ABUSE', 'enabled') !== 'disabled') { throw new Exception('Too many requests', 429); } }, 'api'); \ No newline at end of file diff --git a/src/Appwrite/Database/Database.php b/src/Appwrite/Database/Database.php index 45b61cd089..22d41f07ee 100644 --- a/src/Appwrite/Database/Database.php +++ b/src/Appwrite/Database/Database.php @@ -118,7 +118,7 @@ class Database /** * @param array $options * - * @return Document[]|Document + * @return Document[] */ public function getCollection(array $options) { From 0caff20e8005a5b250457ffa6d0ce0cfdfd33e57 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 28 Jun 2020 23:45:36 +0300 Subject: [PATCH 12/27] Removed more global vars --- app/app.php | 121 +++++++++++++++++++++++++++++++- app/config/collections.php | 10 +-- app/controllers/api/account.php | 3 +- app/controllers/api/health.php | 4 +- app/controllers/api/storage.php | 2 +- app/controllers/shared/web.php | 4 +- app/controllers/web/console.php | 18 ++--- app/controllers/web/home.php | 6 +- app/init.php | 119 ++----------------------------- app/tasks/init.php | 43 ++++++------ app/tasks/migrate.php | 2 +- app/workers/certificates.php | 6 +- app/workers/deletes.php | 2 - app/workers/tasks.php | 5 +- app/workers/webhooks.php | 3 +- public/index.php | 6 +- tests/unit/Event/EventTest.php | 7 +- 17 files changed, 182 insertions(+), 179 deletions(-) diff --git a/app/app.php b/app/app.php index 3348198ee0..007e046bb0 100644 --- a/app/app.php +++ b/app/app.php @@ -2,24 +2,131 @@ require_once __DIR__.'/init.php'; -global $utopia, $request, $response, $register, $project; +global $request, $response, $register, $project; use Utopia\App; use Utopia\Request; +use Utopia\Response; 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\Event\Event; use Appwrite\Network\Validator\Origin; +$request = new Request(); +$response = new Response(); + +$locale = $request->getParam('locale', $request->getHeader('X-Appwrite-Locale', '')); + +if (\in_array($locale, Config::getParam('locales'))) { + Locale::setDefault($locale); +} + +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)); + +\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::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') + ->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'); @@ -58,6 +165,13 @@ App::init(function () use ($utopia, $request, $response, &$user, $project, $cons throw new Exception('Missing or unknown project ID', 400); } + $console->setAttribute('platforms', [ // Allways allow current host + '$collection' => Database::SYSTEM_COLLECTION_PLATFORMS, + 'name' => 'Current Host', + 'type' => 'web', + 'hostname' => \parse_url('https://'.$request->getServer('HTTP_HOST'), PHP_URL_HOST), + ]); + $referrer = $request->getServer('HTTP_REFERER', ''); $origin = \parse_url($request->getServer('HTTP_ORIGIN', $referrer), PHP_URL_HOST); $protocol = \parse_url($request->getServer('HTTP_ORIGIN', $referrer), PHP_URL_SCHEME); @@ -430,4 +544,9 @@ 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;}); + $utopia->run($request, $response); \ No newline at end of file diff --git a/app/config/collections.php b/app/config/collections.php index 4e00c36c35..60f359c48b 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1,7 +1,5 @@ 'Localhost', 'type' => 'web', 'hostname' => 'localhost', - ], - [ - '$collection' => Database::SYSTEM_COLLECTION_PLATFORMS, - 'name' => 'Current Host', - 'type' => 'web', - 'hostname' => \parse_url('https://'.$request->getServer('HTTP_HOST'), PHP_URL_HOST), - ], + ], // Current host is added on app init ], 'legalName' => '', 'legalCountry' => '', diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index a794cb36c4..5887cb46f9 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -537,8 +537,9 @@ 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 () use ($response, &$user, $oauth2Keys) { + function ($response) use (&$user, $oauth2Keys) { $response->json(\array_merge($user->getArrayCopy(\array_merge( [ '$id', diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index 1817c30385..e8c0296190 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -236,8 +236,8 @@ App::get('/v1/health/anti-virus') ->label('sdk.method', 'getAntiVirus') ->label('sdk.description', '/docs/references/health/get-storage-anti-virus.md') ->action( - function () use ($request, $response) { - if ($request->getServer('_APP_STORAGE_ANTIVIRUS') === 'disabled') { // Check if scans are enabled + function () use ($response) { + if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'disabled') { // Check if scans are enabled throw new Exception('Anitvirus is disabled'); } diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 82083a23c7..0673751497 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -696,7 +696,7 @@ App::delete('/v1/storage/files/:fileId') // $source = OpenSSL::decrypt( // $source, // $file->getAttribute('fileOpenSSLCipher'), -// $request->getServer('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')), +// App::getEnv('_APP_OPENSSL_KEY_V'.$file->getAttribute('fileOpenSSLVersion')), // 0, // hex2bin($file->getAttribute('fileOpenSSLIV')), // hex2bin($file->getAttribute('fileOpenSSLTag')) diff --git a/app/controllers/shared/web.php b/app/controllers/shared/web.php index 9bf879c126..1a15890c87 100644 --- a/app/controllers/shared/web.php +++ b/app/controllers/shared/web.php @@ -16,8 +16,8 @@ App::init(function () use ($utopia, $response, $request, $layout) { ->setParam('title', APP_NAME) ->setParam('protocol', Config::getParam('protocol')) ->setParam('domain', Config::getParam('domain')) - ->setParam('home', $request->getServer('_APP_HOME')) - ->setParam('setup', $request->getServer('_APP_SETUP')) + ->setParam('home', App::getEnv('_APP_HOME')) + ->setParam('setup', App::getEnv('_APP_SETUP')) ->setParam('class', 'unknown') ->setParam('icon', '/images/favicon.png') ->setParam('roles', [ diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php index 756263d7eb..d3d28c52a1 100644 --- a/app/controllers/web/console.php +++ b/app/controllers/web/console.php @@ -18,12 +18,12 @@ App::init(function () use ($layout) { ; }, 'console'); -App::shutdown(function () use ($response, $request, $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'); $footer - ->setParam('home', $request->getServer('_APP_HOME', '')) + ->setParam('home', App::getEnv('_APP_HOME', '')) ->setParam('version', Config::getParam('version')) ; @@ -56,11 +56,11 @@ App::get('/console') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') - ->action(function () use ($layout, $request) { + ->action(function () use ($layout) { $page = new View(__DIR__.'/../../views/console/index.phtml'); $page - ->setParam('home', $request->getServer('_APP_HOME', '')) + ->setParam('home', App::getEnv('_APP_HOME', '')) ; $layout @@ -114,8 +114,8 @@ App::get('/console/settings') ->groups(['web', 'console']) ->label('permission', 'public') ->label('scope', 'console') - ->action(function () use ($request, $layout) { - $target = new Domain($request->getServer('_APP_DOMAIN_TARGET', '')); + ->action(function () use ($layout) { + $target = new Domain(App::getEnv('_APP_DOMAIN_TARGET', '')); $page = new View(__DIR__.'/../../views/console/settings/index.phtml'); @@ -254,9 +254,9 @@ App::get('/console/storage') $page = new View(__DIR__.'/../../views/console/storage/index.phtml'); $page - ->setParam('home', $request->getServer('_APP_HOME', 0)) - ->setParam('fileLimit', $request->getServer('_APP_STORAGE_LIMIT', 0)) - ->setParam('fileLimitHuman', Storage::human($request->getServer('_APP_STORAGE_LIMIT', 0))) + ->setParam('home', App::getEnv('_APP_HOME', 0)) + ->setParam('fileLimit', App::getEnv('_APP_STORAGE_LIMIT', 0)) + ->setParam('fileLimitHuman', Storage::human(App::getEnv('_APP_STORAGE_LIMIT', 0))) ; $layout diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index ff59a44e3a..977da411e0 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -168,7 +168,7 @@ App::get('/open-api-2.json') ->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, $request, $utopia) { + function ($platform, $extensions, $tests) use ($response, $utopia) { $services = Config::getParam('services', []); function fromCamelCase($input) @@ -293,14 +293,14 @@ App::get('/open-api-2.json') 'contact' => [ 'name' => 'Appwrite Team', 'url' => 'https://appwrite.io/support', - 'email' => $request->getServer('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM), + '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($request->getServer('_APP_HOME', Config::getParam('domain')), PHP_URL_HOST), + 'host' => \parse_url(App::getEnv('_APP_HOME', Config::getParam('domain')), PHP_URL_HOST), 'basePath' => '/v1', 'schemes' => ['https'], 'consumes' => ['application/json', 'multipart/form-data'], diff --git a/app/init.php b/app/init.php index 1d81e48806..077a85457c 100644 --- a/app/init.php +++ b/app/init.php @@ -12,15 +12,10 @@ if (\file_exists(__DIR__.'/../vendor/autoload.php')) { } use Utopia\App; -use Utopia\Request; -use Utopia\Response; use Utopia\Config\Config; use Utopia\Locale\Locale; use Utopia\Registry\Registry; -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\Event\Event; @@ -49,9 +44,6 @@ const APP_SOCIAL_DISCORD = 'https://discord.gg/GSeTUeA'; const APP_SOCIAL_DEV = 'https://dev.to/appwrite'; $register = new Registry(); -$request = new Request(); -$response = new Response(); -$utopia = new App('Asia/Tel_Aviv'); App::setMode(App::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION)); @@ -69,32 +61,13 @@ 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::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)); - Resque::setBackend(App::getEnv('_APP_REDIS_HOST', '') .':'.App::getEnv('_APP_REDIS_PORT', '')); -\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); - /* * Registry */ -$register->set('db', function () use ($utopia) { // Register DB connection +$register->set('db', function () { // Register DB connection $dbHost = App::getEnv('_APP_DB_HOST', ''); $dbUser = App::getEnv('_APP_DB_USER', ''); $dbPass = App::getEnv('_APP_DB_PASS', ''); @@ -111,7 +84,7 @@ $register->set('db', function () use ($utopia) { // Register DB connection return $pdo; }); -$register->set('influxdb', function () use ($utopia) { // Register DB connection +$register->set('influxdb', function () { // Register DB connection $host = App::getEnv('_APP_INFLUXDB_HOST', ''); $port = App::getEnv('_APP_INFLUXDB_PORT', ''); @@ -123,7 +96,7 @@ $register->set('influxdb', function () use ($utopia) { // Register DB connection return $client; }); -$register->set('statsd', function () use ($utopia) { // Register DB connection +$register->set('statsd', function () { // Register DB connection $host = App::getEnv('_APP_STATSD_HOST', 'telegraf'); $port = App::getEnv('_APP_STATSD_PORT', 8125); @@ -132,7 +105,7 @@ $register->set('statsd', function () use ($utopia) { // Register DB connection return $statsd; }); -$register->set('cache', function () use ($utopia) { // Register cache connection +$register->set('cache', function () { // Register cache connection $redis = new Redis(); $redis->connect(App::getEnv('_APP_REDIS_HOST', ''), @@ -140,7 +113,7 @@ $register->set('cache', function () use ($utopia) { // Register cache connection return $redis; }); -$register->set('smtp', function () use ($utopia) { +$register->set('smtp', function () { $mail = new PHPMailer(true); $mail->isSMTP(); @@ -187,10 +160,7 @@ $register->set('queue-deletes', function () { /* * Localization */ -$locale = $request->getParam('locale', $request->getHeader('X-Appwrite-Locale', '')); - 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'); @@ -240,10 +210,6 @@ Locale::setLanguage('zh-tw', include __DIR__.'/config/locales/zh-tw.php'); Locale::setDefault('en'); -if (\in_array($locale, Config::getParam('locales'))) { - Locale::setDefault($locale); -} - \stream_context_set_default([ // Set global user agent and http settings 'http' => [ 'method' => 'GET', @@ -261,77 +227,4 @@ $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', [])); -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') - ->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') - ) - ); +$consoleDB->setMocks(Config::getParam('collections', [])); \ No newline at end of file diff --git a/app/tasks/init.php b/app/tasks/init.php index a6e6af101d..84d7afd3d7 100644 --- a/app/tasks/init.php +++ b/app/tasks/init.php @@ -3,11 +3,10 @@ require_once __DIR__.'/../init.php'; -global $request; - use Appwrite\ClamAV\Network; use Appwrite\Storage\Device\Local; use Appwrite\Storage\Storage; +use Utopia\App; use Utopia\CLI\CLI; use Utopia\CLI\Console; use Utopia\Domains\Domain; @@ -17,8 +16,8 @@ $cli = new CLI(); $cli ->task('ssl') ->desc('Validate server certificates') - ->action(function () use ($request) { - $domain = $request->getServer('_APP_DOMAIN', ''); + ->action(function () { + $domain = App::getEnv('_APP_DOMAIN', ''); Console::log('Issue a TLS certificate for master domain ('.$domain.')'); @@ -33,17 +32,17 @@ $cli $cli ->task('doctor') ->desc('Validate server health') - ->action(function () use ($request, $register) { + ->action(function () use ($register) { Console::log(" __ ____ ____ _ _ ____ __ ____ ____ __ __ / _\ ( _ \( _ \/ )( \( _ \( )(_ _)( __) ( )/ \ / \ ) __/ ) __/\ /\ / ) / )( )( ) _) _ )(( O ) \_/\_/(__) (__) (_/\_)(__\_)(__) (__) (____)(_)(__)\__/ "); - Console::log("\n".'👩‍⚕️ Running '.APP_NAME.' Doctor for version '.$request->getServer('_APP_VERSION', 'UNKNOWN').' ...'."\n"); + Console::log("\n".'👩‍⚕️ Running '.APP_NAME.' Doctor for version '.App::getEnv('_APP_VERSION', 'UNKNOWN').' ...'."\n"); Console::log('Checking for production best practices...'); - $domain = new Domain($request->getServer('_APP_DOMAIN')); + $domain = new Domain(App::getEnv('_APP_DOMAIN')); if(!$domain->isKnown() || $domain->isTest()) { Console::log('🔴 Hostname has a public suffix'); @@ -52,7 +51,7 @@ $cli Console::log('🟢 Hostname has a public suffix'); } - $domain = new Domain($request->getServer('_APP_DOMAIN_TARGET')); + $domain = new Domain(App::getEnv('_APP_DOMAIN_TARGET')); if(!$domain->isKnown() || $domain->isTest()) { Console::log('🔴 CNAME target has a public suffix'); @@ -61,30 +60,30 @@ $cli Console::log('🟢 CNAME target has a public suffix'); } - if($request->getServer('_APP_OPENSSL_KEY_V1', 'your-secret-key') === 'your-secret-key') { + if(App::getEnv('_APP_OPENSSL_KEY_V1', 'your-secret-key') === 'your-secret-key') { Console::log('🔴 Using a unique secret key for encryption'); } else { Console::log('🟢 Using a unique secret key for encryption'); } - if($request->getServer('_APP_ENV', 'development') === 'development') { + if(App::getEnv('_APP_ENV', 'development') === 'development') { Console::log('🔴 App enviornment is set for production'); } else { Console::log('🟢 App enviornment is set for production'); } - if($request->getServer('_APP_OPTIONS_ABUSE', 'disabled') === 'disabled') { + if(App::getEnv('_APP_OPTIONS_ABUSE', 'disabled') === 'disabled') { Console::log('🔴 Abuse protection is enabled'); } else { Console::log('🟢 Abuse protection is enabled'); } - $authWhitelistEmails = $request->getServer('_APP_CONSOLE_WHITELIST_EMAILS', null); - $authWhitelistIPs = $request->getServer('_APP_CONSOLE_WHITELIST_IPS', null); - $authWhitelistDomains = $request->getServer('_APP_CONSOLE_WHITELIST_DOMAINS', null); + $authWhitelistEmails = App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null); + $authWhitelistIPs = App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null); + $authWhitelistDomains = App::getEnv('_APP_CONSOLE_WHITELIST_DOMAINS', null); if(empty($authWhitelistEmails) && empty($authWhitelistDomains) @@ -96,7 +95,7 @@ $cli Console::log('🟢 Console access limits are enabled'); } - if(empty($request->getServer('_APP_OPTIONS_FORCE_HTTPS', null))) { + if(empty(App::getEnv('_APP_OPTIONS_FORCE_HTTPS', null))) { Console::log('🔴 HTTP force option is disabled'); } else { @@ -132,7 +131,7 @@ $cli Console::error('Cache............disconnected 👎'); } - if($request->getServer('_APP_STORAGE_ANTIVIRUS') === 'enabled') { // Check if scans are enabled + if(App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'enabled') { // Check if scans are enabled try { $antiVirus = new Network('clamav', 3310); @@ -161,8 +160,8 @@ $cli Console::error('SMTP.............disconnected 👎'); } - $host = $request->getServer('_APP_STATSD_HOST', 'telegraf'); - $port = $request->getServer('_APP_STATSD_PORT', 8125); + $host = App::getEnv('_APP_STATSD_HOST', 'telegraf'); + $port = App::getEnv('_APP_STATSD_PORT', 8125); if($fp = @\fsockopen('udp://'.$host, $port, $errCode, $errStr, 2)){ Console::success('StatsD..............connected 👍'); @@ -171,8 +170,8 @@ $cli Console::error('StatsD...........disconnected 👎'); } - $host = $request->getServer('_APP_INFLUXDB_HOST', ''); - $port = $request->getServer('_APP_INFLUXDB_PORT', ''); + $host = App::getEnv('_APP_INFLUXDB_HOST', ''); + $port = App::getEnv('_APP_INFLUXDB_PORT', ''); if($fp = @\fsockopen($host, $port, $errCode, $errStr, 2)){ Console::success('InfluxDB............connected 👍'); @@ -238,10 +237,10 @@ $cli try { Console::log(''); - $version = \json_decode(@\file_get_contents($request->getServer('_APP_HOME', 'http://localhost').'/v1/health/version'), true); + $version = \json_decode(@\file_get_contents(App::getEnv('_APP_HOME', 'http://localhost').'/v1/health/version'), true); if($version && isset($version['version'])) { - if(\version_compare($version['version'], $request->getServer('_APP_VERSION', 'UNKNOWN')) === 0) { + if(\version_compare($version['version'], App::getEnv('_APP_VERSION', 'UNKNOWN')) === 0) { Console::info('You are running the latest version of '.APP_NAME.'! 🥳'); } else { diff --git a/app/tasks/migrate.php b/app/tasks/migrate.php index fe833e27f6..3e3eec30c4 100644 --- a/app/tasks/migrate.php +++ b/app/tasks/migrate.php @@ -3,7 +3,7 @@ require_once __DIR__.'/../init.php'; -global $register, $projectDB, $console, $request; +global $register, $projectDB, $console; use Utopia\Config\Config; use Utopia\CLI\CLI; diff --git a/app/workers/certificates.php b/app/workers/certificates.php index 22c3a14e63..617cef3a98 100644 --- a/app/workers/certificates.php +++ b/app/workers/certificates.php @@ -23,7 +23,7 @@ class CertificatesV1 public function perform() { - global $request, $consoleDB; + global $consoleDB; /** * 1. Get new domain document - DONE @@ -62,7 +62,7 @@ class CertificatesV1 } if($validateTarget) { - $target = new Domain($request->getServer('_APP_DOMAIN_TARGET', '')); + $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.'); @@ -107,7 +107,7 @@ class CertificatesV1 $staging = (Config::getParam('env') === App::MODE_TYPE_PRODUCTION) ? '' : ' --dry-run'; $response = \shell_exec("certbot certonly --webroot --noninteractive --agree-tos{$staging} \ - --email ".$request->getServer('_APP_SYSTEM_EMAIL_ADDRESS', 'security@localhost.test')." \ + --email ".App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', 'security@localhost.test')." \ -w ".APP_STORAGE_CERTIFICATES." \ -d {$domain->get()}"); diff --git a/app/workers/deletes.php b/app/workers/deletes.php index aa0cbe6c2d..1ff72d3e01 100644 --- a/app/workers/deletes.php +++ b/app/workers/deletes.php @@ -20,8 +20,6 @@ class DeletesV1 public function perform() { - global $consoleDB, $request; - $document = $this->args['document']; $document = new Document($document); diff --git a/app/workers/tasks.php b/app/workers/tasks.php index db664d567e..625bcfed6f 100644 --- a/app/workers/tasks.php +++ b/app/workers/tasks.php @@ -2,6 +2,7 @@ require_once __DIR__.'/../init.php'; +use Utopia\App; use Utopia\Config\Config; use Appwrite\Database\Database; use Appwrite\Database\Validator\Authorization; @@ -24,7 +25,7 @@ class TasksV1 public function perform() { - global $consoleDB, $request; + global $consoleDB; /* * 1. Get Original Task @@ -96,7 +97,7 @@ class TasksV1 \curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); \curl_setopt($ch, CURLOPT_USERAGENT, \sprintf(APP_USERAGENT, Config::getParam('version'), - $request->getServer('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY) + App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY) )); \curl_setopt( $ch, diff --git a/app/workers/webhooks.php b/app/workers/webhooks.php index ecd9360d26..ab861dfd46 100644 --- a/app/workers/webhooks.php +++ b/app/workers/webhooks.php @@ -9,6 +9,7 @@ echo APP_NAME.' webhooks worker v1 has started'; use Utopia\Config\Config; use Appwrite\Database\Database; use Appwrite\Database\Validator\Authorization; +use Utopia\App; class WebhooksV1 { @@ -61,7 +62,7 @@ class WebhooksV1 \curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); \curl_setopt($ch, CURLOPT_USERAGENT, \sprintf(APP_USERAGENT, Config::getParam('version'), - $request->getServer('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY) + App::getEnv('_APP_SYSTEM_SECURITY_EMAIL_ADDRESS', APP_EMAIL_SECURITY) )); \curl_setopt( $ch, diff --git a/public/index.php b/public/index.php index 6c0dfd59c2..00966df979 100644 --- a/public/index.php +++ b/public/index.php @@ -11,8 +11,8 @@ error_reporting(0); ini_set('display_errors', 0); -// ini_set('display_errors', 1); -// ini_set('display_startup_errors', 1); -// error_reporting(E_ALL); +ini_set('display_errors', 1); +ini_set('display_startup_errors', 1); +error_reporting(E_ALL); include __DIR__ . '/../app/app.php'; diff --git a/tests/unit/Event/EventTest.php b/tests/unit/Event/EventTest.php index 151dab1233..3a21838b60 100644 --- a/tests/unit/Event/EventTest.php +++ b/tests/unit/Event/EventTest.php @@ -2,9 +2,9 @@ namespace Appwrite\Tests; -use Utopia\Request; use Appwrite\Event\Event; use PHPUnit\Framework\TestCase; +use Utopia\App; class EventTest extends TestCase { @@ -20,9 +20,8 @@ class EventTest extends TestCase public function setUp() { - $request = new Request(); - $redisHost = $request->getServer('_APP_REDIS_HOST', ''); - $redisPort = $request->getServer('_APP_REDIS_PORT', ''); + $redisHost = App::getEnv('_APP_REDIS_HOST', ''); + $redisPort = App::getEnv('_APP_REDIS_PORT', ''); \Resque::setBackend($redisHost.':'.$redisPort); $this->queue = 'v1-tests' . uniqid(); From 2de01c3e2422637a1c108271bec69f67e9af243b Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 29 Jun 2020 00:06:29 +0300 Subject: [PATCH 13/27] Fixed current platform set attrs --- app/app.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/app.php b/app/app.php index 007e046bb0..f5230cf5fb 100644 --- a/app/app.php +++ b/app/app.php @@ -170,7 +170,7 @@ App::init(function () use ($utopia, $request, $response, &$user, $project, $cons 'name' => 'Current Host', 'type' => 'web', 'hostname' => \parse_url('https://'.$request->getServer('HTTP_HOST'), PHP_URL_HOST), - ]); + ], Document::SET_TYPE_APPEND); $referrer = $request->getServer('HTTP_REFERER', ''); $origin = \parse_url($request->getServer('HTTP_ORIGIN', $referrer), PHP_URL_HOST); From 44e7f50ca32f772293eef5e5c4e063e095c1150f Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 29 Jun 2020 11:32:46 +0300 Subject: [PATCH 14/27] Removed unused var --- app/app.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/app.php b/app/app.php index f5230cf5fb..05606f1cf4 100644 --- a/app/app.php +++ b/app/app.php @@ -381,7 +381,6 @@ App::options(function () use ($request, $response) { }); App::error(function ($error /* @var $error Exception */) use ($request, $response, $utopia, $project) { - $env = Config::getParam('env'); $version = Config::getParam('version'); switch ($error->getCode()) { @@ -403,7 +402,7 @@ App::error(function ($error /* @var $error Exception */) use ($request, $respons $_SERVER = []; // Reset before reporting to error log to avoid keys being compromised - $output = ((App::MODE_TYPE_DEVELOPMENT == $env)) ? [ + $output = ((App::isDevelopment())) ? [ 'message' => $error->getMessage(), 'code' => $error->getCode(), 'file' => $error->getFile(), From 7440213a958a6db243683f9545f68ec40e1a3a7f Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 30 Jun 2020 00:39:16 +0300 Subject: [PATCH 15/27] 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 16/27] 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 17/27] 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 18/27] 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 19/27] 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 20/27] 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 21/27] 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 22/27] 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 23/27] 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 24/27] 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 25/27] 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 26/27] 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 27/27] 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