diff --git a/.env b/.env index f8777509a3..fc6f388a99 100644 --- a/.env +++ b/.env @@ -18,6 +18,6 @@ _APP_STATSD_HOST=telegraf _APP_STATSD_PORT=8125 _APP_SMTP_HOST=maildev _APP_SMTP_PORT=25 -_APP_STORAGE_LIMIT=100000000 +_APP_STORAGE_LIMIT=10000000 _APP_FUNCTIONS_TIMEOUT=900 _APP_FUNCTIONS_CONTAINERS=10 \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md index 44fd40aec6..b1c6906286 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,8 @@ - Added pagination for projects list on the console home page. - Updated storage calculation to match IEC standards - Now using Alpine as base Docker image +- Upgraded device detctor to version 3.12.6 +- Upgraded MariaDB to version 10.5.5 - User name max length is now 128 chars and not 100 for better API consistency - Team name max length is now 128 chars and not 100 for better API consistency - Collection name max length is now 128 chars and not 256 for better API consistency @@ -30,6 +32,7 @@ - API Key name max length is now 128 chars and not 256 for better API consistency - Task name max length is now 128 chars and not 256 for better API consistency - Platform name max length is now 128 chars and not 256 for better API consistency +- Webhooks payloads are now exactly the same as any of the API response objects - New and consistent response format for all API object + new response examples in the docs - Removed user roles attribute from user object (can be fetched from /v1/teams/memberships) ** - Removed type attribute from session object response (used only internally) @@ -38,6 +41,8 @@ ## Breaking Changes (Read before upgrading!) - **Deprecated** `first` and `last` query params for documents list route in the database API - **Deprecated** Deprectaed Pubjabi Translations ('pn') +- **Deprecated** `PATCH /account/prefs` is now updating the prefs payload and not just merging it +- **Deprecated** `PATCH /users/:userId/prefs` is now updating the prefs payload and not just merging it - Switched order of limit and offset params in all the SDKs `listDocuments` method for better consistency - Default `limit` param value in all the SDKs `listDocuments` method is now 25 for better consistency diff --git a/Dockerfile b/Dockerfile index 1a2798c450..f71dc57ac1 100755 --- a/Dockerfile +++ b/Dockerfile @@ -15,7 +15,7 @@ RUN composer update --ignore-platform-reqs --optimize-autoloader \ FROM php:7.4-cli-alpine as step1 ENV TZ=Asia/Tel_Aviv \ - PHP_REDIS_VERSION=5.3.0 \ + PHP_REDIS_VERSION=5.3.1 \ PHP_SWOOLE_VERSION=4.5.3 \ PHP_XDEBUG_VERSION=sdebug_2_9-beta @@ -68,7 +68,7 @@ ENV TZ=Asia/Tel_Aviv \ _APP_OPTIONS_ABUSE=enabled \ _APP_OPTIONS_FORCE_HTTPS=disabled \ _APP_OPENSSL_KEY_V1=your-secret-key \ - _APP_STORAGE_LIMIT=100000000 \ + _APP_STORAGE_LIMIT=10000000 \ _APP_STORAGE_ANTIVIRUS=enabled \ _APP_REDIS_HOST=redis \ _APP_REDIS_PORT=6379 \ @@ -148,6 +148,7 @@ RUN chmod +x /usr/local/bin/doctor && \ chmod +x /usr/local/bin/schedule && \ chmod +x /usr/local/bin/ssl && \ chmod +x /usr/local/bin/test && \ + chmod +x /usr/local/bin/vars && \ chmod +x /usr/local/bin/worker-audits && \ chmod +x /usr/local/bin/worker-certificates && \ chmod +x /usr/local/bin/worker-deletes && \ @@ -166,7 +167,7 @@ RUN echo extension=redis.so >> /usr/local/etc/php/conf.d/redis.ini RUN echo "opcache.preload_user=www-data" >> /usr/local/etc/php/conf.d/appwrite.ini RUN echo "opcache.preload=/usr/src/code/app/preload.php" >> /usr/local/etc/php/conf.d/appwrite.ini -RUN echo "opcache.enable_cli = 1" >> /usr/local/etc/php/conf.d/appwrite.ini +RUN echo "opcache.enable_cli=1" >> /usr/local/etc/php/conf.d/appwrite.ini EXPOSE 80 diff --git a/Dockerfile.nginx b/Dockerfile.nginx index 3bd88a7d34..b7acd23a7f 100644 --- a/Dockerfile.nginx +++ b/Dockerfile.nginx @@ -60,7 +60,7 @@ ENV TZ=Asia/Tel_Aviv \ _APP_OPTIONS_ABUSE=enabled \ _APP_OPTIONS_FORCE_HTTPS=disabled \ _APP_OPENSSL_KEY_V1=your-secret-key \ - _APP_STORAGE_LIMIT=100000000 \ + _APP_STORAGE_LIMIT=10000000 \ _APP_STORAGE_ANTIVIRUS=enabled \ _APP_REDIS_HOST=redis \ _APP_REDIS_PORT=6379 \ diff --git a/app/cli.php b/app/cli.php index 9eede27a34..c887493bbb 100644 --- a/app/cli.php +++ b/app/cli.php @@ -14,6 +14,7 @@ include 'tasks/install.php'; include 'tasks/migrate.php'; include 'tasks/sdks.php'; include 'tasks/ssl.php'; +include 'tasks/vars.php'; $cli ->task('version') diff --git a/app/config/collections.php b/app/config/collections.php index 5720b66d4f..546f7eb30f 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -4,7 +4,7 @@ use Utopia\App; use Utopia\Config\Config; use Appwrite\Database\Database; -$providers = Config::getParam('providers'); +$providers = Config::getParam('providers', []); $collections = [ 'console' => [ @@ -234,6 +234,7 @@ $collections = [ 'default' => '', 'required' => false, 'array' => false, + 'filter' => ['json'] ], [ '$collection' => Database::SYSTEM_COLLECTION_RULES, @@ -336,6 +337,123 @@ $collections = [ 'required' => true, 'array' => false, ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'OS Code', + 'key' => 'osCode', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => '', + 'required' => false, + 'array' => false, + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'OS Name', + 'key' => 'osName', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => '', + 'required' => false, + 'array' => false, + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'OS Version', + 'key' => 'osVersion', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => '', + 'required' => false, + 'array' => false, + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'Client Type', + 'key' => 'clientType', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => '', + 'required' => false, + 'array' => false, + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'Client Code', + 'key' => 'clientCode', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => '', + 'required' => false, + 'array' => false, + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'Client Name', + 'key' => 'clientName', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => '', + 'required' => false, + 'array' => false, + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'Client Version', + 'key' => 'clientVersion', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => '', + 'required' => false, + 'array' => false, + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'Client Engine', + 'key' => 'clientEngine', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => '', + 'required' => false, + 'array' => false, + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'Client Engine Version', + 'key' => 'clientEngineVersion', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => '', + 'required' => false, + 'array' => false, + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'Device Name', + 'key' => 'deviceName', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => '', + 'required' => false, + 'array' => false, + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'Device Brand', + 'key' => 'deviceBrand', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => '', + 'required' => false, + 'array' => false, + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'Device Model', + 'key' => 'deviceModel', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => '', + 'required' => false, + 'array' => false, + ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'Country Code', + 'key' => 'countryCode', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => '', + 'required' => false, + 'array' => false, + ], ], ], Database::SYSTEM_COLLECTION_MEMBERSHIPS => [ @@ -1335,7 +1453,7 @@ $collections = [ [ '$collection' => Database::SYSTEM_COLLECTION_RULES, 'label' => 'Code Path', - 'key' => 'codePath', + 'key' => 'path', 'type' => Database::SYSTEM_VAR_TYPE_TEXT, 'default' => '', 'required' => false, @@ -1344,7 +1462,7 @@ $collections = [ [ '$collection' => Database::SYSTEM_COLLECTION_RULES, 'label' => 'Code Size', - 'key' => 'codeSize', + 'key' => 'size', 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, 'default' => '', 'required' => false, @@ -1386,6 +1504,15 @@ $collections = [ 'required' => false, 'array' => false, ], + [ + '$collection' => Database::SYSTEM_COLLECTION_RULES, + 'label' => 'Trigger', + 'key' => 'trigger', + 'type' => Database::SYSTEM_VAR_TYPE_TEXT, + 'default' => '', + 'required' => false, + 'array' => false, + ], [ '$collection' => Database::SYSTEM_COLLECTION_RULES, 'label' => 'Status', diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 8aa88df4da..6f63670331 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -28,23 +28,10 @@ use Utopia\Validator\ArrayList; $oauthDefaultSuccess = App::getEnv('_APP_HOME').'/auth/oauth2/success'; $oauthDefaultFailure = App::getEnv('_APP_HOME').'/auth/oauth2/failure'; -$oauth2Keys = []; - -App::init(function() use (&$oauth2Keys) { - foreach (Config::getParam('providers') as $key => $provider) { - if (!$provider['enabled']) { - continue; - } - - $oauth2Keys[] = 'oauth2'.\ucfirst($key); - $oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken'; - } -}, [], 'account'); - App::post('/v1/account') ->desc('Create Account') ->groups(['api', 'account']) - ->label('webhook', 'account.create') + ->label('event', 'account.create') ->label('scope', 'public') ->label('sdk.platform', [APP_PLATFORM_CLIENT]) ->label('sdk.namespace', 'account') @@ -54,9 +41,9 @@ 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(128); }, 'User name. Max length: 128 chars.', true) - ->action(function ($email, $password, $name, $request, $response, $project, $projectDB, $webhooks, $audits) use ($oauth2Keys) { - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + ->action(function ($email, $password, $name, $request, $response, $project, $projectDB, $webhooks, $audits) { + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $webhooks */ @@ -120,36 +107,24 @@ App::post('/v1/account') throw new Exception('Failed saving user to DB', 500); } - $webhooks - ->setParam('payload', [ - 'name' => $name, - 'email' => $email, - ]) - ; - $audits ->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()])); + $user + ->setAttribute('roles', Authorization::getRoles()) + ; + + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic($user, Response::MODEL_USER); }, ['request', 'response', 'project', 'projectDB', 'webhooks', 'audits']); App::post('/v1/account/sessions') ->desc('Create Account Session') ->groups(['api', 'account']) - ->label('webhook', 'account.sessions.create') + ->label('event', 'account.sessions.create') ->label('scope', 'public') ->label('sdk.platform', [APP_PLATFORM_CLIENT]) ->label('sdk.namespace', 'account') @@ -159,10 +134,12 @@ 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, $request, $response, $projectDB, $webhooks, $audits) { - /** @var Appwrite\Swoole\Request $request */ + ->action(function ($email, $password, $request, $response, $projectDB, $locale, $geodb, $webhooks, $audits) { + /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Locale\Locale $locale */ + /** @var GeoIp2\Database\Reader $geodb */ /** @var Appwrite\Event\Event $webhooks */ /** @var Appwrite\Event\Event $audits */ @@ -185,6 +162,23 @@ App::post('/v1/account/sessions') throw new Exception('Invalid credentials', 401); // Wrong password or username } + $dd = new DeviceDetector($request->getUserAgent('UNKNOWN')); + + $dd->parse(); + + $os = $dd->getOs(); + $osCode = (isset($os['short_name'])) ? $os['short_name'] : ''; + $osName = (isset($os['name'])) ? $os['name'] : ''; + $osVersion = (isset($os['version'])) ? $os['version'] : ''; + + $client = $dd->getClient(); + $clientType = (isset($client['type'])) ? $client['type'] : ''; + $clientCode = (isset($client['short_name'])) ? $client['short_name'] : ''; + $clientName = (isset($client['name'])) ? $client['name'] : ''; + $clientVersion = (isset($client['version'])) ? $client['version'] : ''; + $clientEngine = (isset($client['engine'])) ? $client['engine'] : ''; + $clientEngineVersion = (isset($client['engine_version'])) ? $client['engine_version'] : ''; + $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; $secret = Auth::tokenGenerator(); $session = new Document([ @@ -195,8 +189,32 @@ App::post('/v1/account/sessions') 'expire' => $expiry, 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), + + 'osCode' => $osCode, + 'osName' => $osName, + 'osVersion' => $osVersion, + 'clientType' => $clientType, + 'clientCode' => $clientCode, + 'clientName' => $clientName, + 'clientVersion' => $clientVersion, + 'clientEngine' => $clientEngine, + 'clientEngineVersion' => $clientEngineVersion, + 'deviceName' => $dd->getDeviceName(), + 'deviceBrand' => $dd->getBrandName(), + 'deviceModel' => $dd->getModel(), ]); + try { + $record = $geodb->country($request->getIP()); + $session + ->setAttribute('countryCode', \strtolower($record->country->isoCode)) + ; + } catch (\Exception $e) { + $session + ->setAttribute('countryCode', '--') + ; + } + Authorization::setRole('user:'.$profile->getId()); $session = $projectDB->createDocument($session->getArrayCopy()); @@ -212,14 +230,7 @@ App::post('/v1/account/sessions') if (false === $profile) { throw new Exception('Failed saving user to DB', 500); } - - $webhooks - ->setParam('payload', [ - 'name' => $profile->getAttribute('name', ''), - 'email' => $profile->getAttribute('email', ''), - ]) - ; - + $audits ->setParam('userId', $profile->getId()) ->setParam('event', 'account.sessions.create') @@ -237,10 +248,14 @@ App::post('/v1/account/sessions') ->addCookie(Auth::$cookieName, Auth::encodeSession($profile->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->setStatusCode(Response::STATUS_CODE_CREATED) ; + + $session + ->setAttribute('current', true) + ->setAttribute('countryName', (isset($countries[$session->getAttribute('countryCode')])) ? $countries[$session->getAttribute('countryCode')] : $locale->getText('locale.country.unknown')) + ; $response->dynamic($session, Response::MODEL_SESSION); - ; - }, ['request', 'response', 'projectDB', 'webhooks', 'audits']); + }, ['request', 'response', 'projectDB', 'locale', 'geodb', 'webhooks', 'audits']); App::get('/v1/account/sessions/oauth2/:provider') ->desc('Create Account Session with OAuth2') @@ -261,8 +276,8 @@ App::get('/v1/account/sessions/oauth2/:provider') ->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, $request, $response, $project) { - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ $protocol = $request->getProtocol(); @@ -306,8 +321,8 @@ App::get('/v1/account/sessions/oauth2/callback/:provider/:projectId') ->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, $request, $response) { - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ $domain = $request->getHostname(); $protocol = $request->getProtocol(); @@ -331,8 +346,8 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId') ->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, $request, $response) { - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ $domain = $request->getHostname(); $protocol = $request->getProtocol(); @@ -348,7 +363,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->desc('OAuth2 Redirect') ->groups(['api', 'account']) ->label('error', __DIR__.'/../../views/general/error.phtml') - ->label('webhook', 'account.sessions.create') + ->label('event', 'account.sessions.create') ->label('scope', 'public') ->label('abuse-limit', 50) ->label('abuse-key', 'ip:{ip}') @@ -356,12 +371,13 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->param('provider', '', function () { return new WhiteList(\array_keys(Config::getParam('providers')), true); }, '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, $request, $response, $project, $user, $projectDB, $audits) use ($oauthDefaultSuccess) { - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + ->action(function ($provider, $code, $state, $request, $response, $project, $user, $projectDB, $geodb, $audits) use ($oauthDefaultSuccess) { + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $projectDB */ + /** @var GeoIp2\Database\Reader $geodb */ /** @var Appwrite\Event\Event $audits */ $protocol = $request->getProtocol(); @@ -482,6 +498,24 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') // Create session token, verify user account and update OAuth2 ID and Access Token + + $dd = new DeviceDetector($request->getUserAgent('UNKNOWN')); + + $dd->parse(); + + $os = $dd->getOs(); + $osCode = (isset($os['short_name'])) ? $os['short_name'] : ''; + $osName = (isset($os['name'])) ? $os['name'] : ''; + $osVersion = (isset($os['version'])) ? $os['version'] : ''; + + $client = $dd->getClient(); + $clientType = (isset($client['type'])) ? $client['type'] : ''; + $clientCode = (isset($client['short_name'])) ? $client['short_name'] : ''; + $clientName = (isset($client['name'])) ? $client['name'] : ''; + $clientVersion = (isset($client['version'])) ? $client['version'] : ''; + $clientEngine = (isset($client['engine'])) ? $client['engine'] : ''; + $clientEngineVersion = (isset($client['engine_version'])) ? $client['engine_version'] : ''; + $secret = Auth::tokenGenerator(); $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; $session = new Document([ @@ -492,8 +526,32 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'expire' => $expiry, 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), + + 'osCode' => $osCode, + 'osName' => $osName, + 'osVersion' => $osVersion, + 'clientType' => $clientType, + 'clientCode' => $clientCode, + 'clientName' => $clientName, + 'clientVersion' => $clientVersion, + 'clientEngine' => $clientEngine, + 'clientEngineVersion' => $clientEngineVersion, + 'deviceName' => $dd->getDeviceName(), + 'deviceBrand' => $dd->getBrandName(), + 'deviceModel' => $dd->getModel(), ]); + try { + $record = $geodb->country($request->getIP()); + $session + ->setAttribute('countryCode', \strtolower($record->country->isoCode)) + ; + } catch (\Exception $e) { + $session + ->setAttribute('countryCode', '--') + ; + } + $user ->setAttribute('oauth2'.\ucfirst($provider), $oauth2ID) ->setAttribute('oauth2'.\ucfirst($provider).'AccessToken', $accessToken) @@ -523,7 +581,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') } // Add keys for non-web platforms - TODO - add verification phase to aviod session sniffing - if (parse_url($state['success'], PHP_URL_PATH) === $oauthDefaultSuccess) { + if (parse_url($state['success'], PHP_URL_PATH) === parse_url($oauthDefaultSuccess, PHP_URL_PATH)) { $state['success'] = URLParser::parse($state['success']); $query = URLParser::parseQuery($state['success']['query']); $query['project'] = $project->getId(); @@ -541,7 +599,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) ->redirect($state['success']) ; - }, ['request', 'response', 'project', 'user', 'projectDB', 'audits']); + }, ['request', 'response', 'project', 'user', 'projectDB', 'geodb', 'audits']); App::get('/v1/account') ->desc('Get Account') @@ -552,20 +610,15 @@ App::get('/v1/account') ->label('sdk.method', 'get') ->label('sdk.description', '/docs/references/account/get.md') ->label('sdk.response', ['200' => 'user']) - ->action(function ($response, $user) use ($oauth2Keys) { - /** @var Utopia\Response $response */ + ->action(function ($response, $user) { + /** @var Appwrite\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()])); + $user + ->setAttribute('roles', Authorization::getRoles()) + ; + + $response->dynamic($user, Response::MODEL_USER); }, ['response', 'user']); App::get('/v1/account/prefs') @@ -577,17 +630,10 @@ App::get('/v1/account/prefs') ->label('sdk.method', 'getPrefs') ->label('sdk.description', '/docs/references/account/get-prefs.md') ->action(function ($response, $user) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ - $prefs = $user->getAttribute('prefs', '{}'); - - try { - $prefs = \json_decode($prefs, true); - $prefs = ($prefs) ? $prefs : []; - } catch (\Exception $error) { - throw new Exception('Failed to parse prefs', 500); - } + $prefs = $user->getAttribute('prefs', new \stdClass); $response->json($prefs); }, ['response', 'user']); @@ -600,58 +646,34 @@ App::get('/v1/account/sessions') ->label('sdk.namespace', 'account') ->label('sdk.method', 'getSessions') ->label('sdk.description', '/docs/references/account/get-sessions.md') - ->action(function ($response, $user, $locale, $geodb) { - /** @var Utopia\Response $response */ + ->action(function ($response, $user, $locale) { + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ /** @var Utopia\Locale\Locale $locale */ - /** @var GeoIp2\Database\Reader $geodb */ $tokens = $user->getAttribute('tokens', []); $sessions = []; - $current = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_LOGIN, Auth::$secret); - $index = 0; $countries = $locale->getText('countries'); + $current = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_LOGIN, Auth::$secret); 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'; + $token->setAttribute('countryName', (isset($countries[$token->getAttribute('contryCode')])) + ? $countries[$token->getAttribute('contryCode')] + : $locale->getText('locale.country.unknown')); + $token->setAttribute('current', ($current == $token->getId()) ? true : false); - $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 = $geodb->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; + $sessions[] = $token; } - $response->json($sessions); - }, ['response', 'user', 'locale', 'geodb']); + $response->dynamic(new Document([ + 'sum' => count($sessions), + 'sessions' => $sessions + ]), Response::MODEL_SESSION_LIST); + }, ['response', 'user', 'locale']); App::get('/v1/account/logs') ->desc('Get Account Logs') @@ -662,7 +684,7 @@ App::get('/v1/account/logs') ->label('sdk.method', 'getLogs') ->label('sdk.description', '/docs/references/account/get-logs.md') ->action(function ($response, $register, $project, $user, $locale, $geodb) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ /** @var Appwrite\Database\Document $user */ /** @var Utopia\Locale\Locale $locale */ @@ -703,44 +725,63 @@ App::get('/v1/account/logs') $dd->parse(); - $output[$i] = [ + $os = $dd->getOs(); + $osCode = (isset($os['short_name'])) ? $os['short_name'] : ''; + $osName = (isset($os['name'])) ? $os['name'] : ''; + $osVersion = (isset($os['version'])) ? $os['version'] : ''; + + $client = $dd->getClient(); + $clientType = (isset($client['type'])) ? $client['type'] : ''; + $clientCode = (isset($client['short_name'])) ? $client['short_name'] : ''; + $clientName = (isset($client['name'])) ? $client['name'] : ''; + $clientVersion = (isset($client['version'])) ? $client['version'] : ''; + $clientEngine = (isset($client['engine'])) ? $client['engine'] : ''; + $clientEngineVersion = (isset($client['engine_version'])) ? $client['engine_version'] : ''; + + $output[$i] = new Document([ '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' => [], - ]; + + 'osCode' => $osCode, + 'osName' => $osName, + 'osVersion' => $osVersion, + 'clientType' => $clientType, + 'clientCode' => $clientCode, + 'clientName' => $clientName, + 'clientVersion' => $clientVersion, + 'clientEngine' => $clientEngine, + 'clientEngineVersion' => $clientEngineVersion, + 'deviceName' => $dd->getDeviceName(), + 'deviceBrand' => $dd->getBrandName(), + 'deviceModel' => $dd->getModel(), + ]); try { $record = $geodb->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'); + $output[$i]->setAttribute('countryCode', \strtolower($record->country->isoCode)); + $output[$i]->setAttribute('countryName', (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]->setAttribute('countryCode', '--'); + $output[$i]->setAttribute('countryName', $locale->getText('locale.country.unknown')); } } - $response->json($output); + $response->dynamic(new Document(['logs' => $output]), Response::MODEL_LOG_LIST); }, ['response', 'register', 'project', 'user', 'locale', 'geodb']); App::patch('/v1/account/name') ->desc('Update Account Name') ->groups(['api', 'account']) - ->label('webhook', 'account.update.name') + ->label('event', 'account.update.name') ->label('scope', 'account') ->label('sdk.platform', [APP_PLATFORM_CLIENT]) ->label('sdk.namespace', 'account') ->label('sdk.method', 'updateName') ->label('sdk.description', '/docs/references/account/update-name.md') ->param('name', '', function () { return new Text(128); }, 'User name. Max length: 128 chars.') - ->action(function ($name, $response, $user, $projectDB, $audits) use ($oauth2Keys) { - /** @var Utopia\Response $response */ + ->action(function ($name, $response, $user, $projectDB, $audits) { + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $audits */ @@ -753,27 +794,21 @@ App::patch('/v1/account/name') throw new Exception('Failed saving user to DB', 500); } + $user->setAttribute('roles', Authorization::getRoles()); + $audits ->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->dynamic($user, Response::MODEL_USER); }, ['response', 'user', 'projectDB', 'audits']); App::patch('/v1/account/password') ->desc('Update Account Password') ->groups(['api', 'account']) - ->label('webhook', 'account.update.password') + ->label('event', 'account.update.password') ->label('scope', 'account') ->label('sdk.platform', [APP_PLATFORM_CLIENT]) ->label('sdk.namespace', 'account') @@ -781,8 +816,8 @@ 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, $response, $user, $projectDB, $audits) use ($oauth2Keys) { - /** @var Utopia\Response $response */ + ->action(function ($password, $oldPassword, $response, $user, $projectDB, $audits) { + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $audits */ @@ -799,27 +834,21 @@ App::patch('/v1/account/password') throw new Exception('Failed saving user to DB', 500); } + $user->setAttribute('roles', Authorization::getRoles()); + $audits ->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->dynamic($user, Response::MODEL_USER); }, ['response', 'user', 'projectDB', 'audits']); App::patch('/v1/account/email') ->desc('Update Account Email') ->groups(['api', 'account']) - ->label('webhook', 'account.update.email') + ->label('event', 'account.update.email') ->label('scope', 'account') ->label('sdk.platform', [APP_PLATFORM_CLIENT]) ->label('sdk.namespace', 'account') @@ -827,8 +856,8 @@ 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, $response, $user, $projectDB, $audits) use ($oauth2Keys) { - /** @var Utopia\Response $response */ + ->action(function ($email, $password, $response, $user, $projectDB, $audits) { + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $audits */ @@ -860,44 +889,35 @@ App::patch('/v1/account/email') throw new Exception('Failed saving user to DB', 500); } + $user->setAttribute('roles', Authorization::getRoles()); + $audits ->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->dynamic($user, Response::MODEL_USER); }, ['response', 'user', 'projectDB', 'audits']); App::patch('/v1/account/prefs') ->desc('Update Account Preferences') ->groups(['api', 'account']) - ->label('webhook', 'account.update.prefs') + ->label('event', 'account.update.prefs') ->label('scope', 'account') ->label('sdk.platform', [APP_PLATFORM_CLIENT]) ->label('sdk.namespace', 'account') ->label('sdk.method', 'updatePrefs') - ->param('prefs', '', function () { return new Assoc();}, 'Prefs key-value JSON object.') + ->param('prefs', [], function () { return new Assoc();}, 'Prefs key-value JSON object.') ->label('sdk.description', '/docs/references/account/update-prefs.md') ->action(function ($prefs, $response, $user, $projectDB, $audits) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $audits */ - - $old = \json_decode($user->getAttribute('prefs', '{}'), true); - $old = ($old) ? $old : []; - + $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ - 'prefs' => \json_encode(\array_merge($old, $prefs)), + 'prefs' => $prefs, ])); if (false === $user) { @@ -909,14 +929,7 @@ App::patch('/v1/account/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); - } + $prefs = $user->getAttribute('prefs', new \stdClass); $response->json($prefs); }, ['response', 'user', 'projectDB', 'audits']); @@ -924,15 +937,15 @@ App::patch('/v1/account/prefs') App::delete('/v1/account') ->desc('Delete Account') ->groups(['api', 'account']) - ->label('webhook', 'account.delete') + ->label('event', 'account.delete') ->label('scope', 'account') ->label('sdk.platform', [APP_PLATFORM_CLIENT]) ->label('sdk.namespace', 'account') ->label('sdk.method', 'delete') ->label('sdk.description', '/docs/references/account/delete.md') ->action(function ($request, $response, $user, $projectDB, $audits, $webhooks) { - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $audits */ @@ -963,10 +976,7 @@ App::delete('/v1/account') ; $webhooks - ->setParam('payload', [ - 'name' => $user->getAttribute('name', ''), - 'email' => $user->getAttribute('email', ''), - ]) + ->setParam('payload', $response->output($user, Response::MODEL_USER)) ; if (!Config::getParam('domainVerification')) { @@ -986,7 +996,7 @@ App::delete('/v1/account/sessions/:sessionId') ->desc('Delete Account Session') ->groups(['api', 'account']) ->label('scope', 'account') - ->label('webhook', 'account.sessions.delete') + ->label('event', 'account.sessions.delete') ->label('sdk.platform', [APP_PLATFORM_CLIENT]) ->label('sdk.namespace', 'account') ->label('sdk.method', 'deleteSession') @@ -994,8 +1004,8 @@ App::delete('/v1/account/sessions/:sessionId') ->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, $request, $response, $user, $projectDB, $audits, $webhooks) { - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $audits */ @@ -1021,10 +1031,7 @@ App::delete('/v1/account/sessions/:sessionId') ; $webhooks - ->setParam('payload', [ - 'name' => $user->getAttribute('name', ''), - 'email' => $user->getAttribute('email', ''), - ]) + ->setParam('payload', $response->output($user, Response::MODEL_USER)) ; if (!Config::getParam('domainVerification')) { @@ -1051,15 +1058,15 @@ App::delete('/v1/account/sessions') ->desc('Delete All Account Sessions') ->groups(['api', 'account']) ->label('scope', 'account') - ->label('webhook', 'account.sessions.delete') + ->label('event', '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 ($request, $response, $user, $projectDB, $audits, $webhooks) { - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $audits */ @@ -1078,12 +1085,9 @@ App::delete('/v1/account/sessions') ->setParam('event', 'account.sessions.delete') ->setParam('resource', '/user/'.$user->getId()) ; - + $webhooks - ->setParam('payload', [ - 'name' => $user->getAttribute('name', ''), - 'email' => $user->getAttribute('email', ''), - ]) + ->setParam('payload', $response->output($user, Response::MODEL_USER)) ; if (!Config::getParam('domainVerification')) { @@ -1116,8 +1120,8 @@ App::post('/v1/account/recovery') ->param('email', '', function () { return new Email(); }, 'User 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, $mails, $audits) { - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Database\Document $project */ /** @var Utopia\Locale\Locale $locale */ @@ -1224,7 +1228,7 @@ App::put('/v1/account/recovery') ->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, $response, $projectDB, $audits) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $audits */ @@ -1293,8 +1297,8 @@ App::post('/v1/account/verification') ->label('abuse-key', 'url:{url},email:{param-email}') ->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, $audits, $mails) { - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $projectDB */ @@ -1389,7 +1393,7 @@ App::put('/v1/account/verification') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->param('secret', '', function () { return new Text(256); }, 'Valid verification token.') ->action(function ($userId, $secret, $response, $user, $projectDB, $audits) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $audits */ diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index bf765f520c..93a3ecac7f 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -19,7 +19,7 @@ use Utopia\Config\Config; use Utopia\Validator\HexColor; $avatarCallback = function ($type, $code, $width, $height, $quality, $response) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ $code = \strtolower($code); $type = \strtolower($type); @@ -145,7 +145,7 @@ App::get('/v1/avatars/image') ->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 */ + /** @var Appwrite\Utopia\Response $response */ $quality = 80; $output = 'png'; @@ -209,7 +209,7 @@ App::get('/v1/avatars/favicon') ->label('sdk.description', '/docs/references/avatars/get-favicon.md') ->param('url', '', function () { return new URL(); }, 'Website URL which you want to fetch the favicon from.') ->action(function ($url, $response) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ $width = 56; $height = 56; @@ -362,7 +362,7 @@ App::get('/v1/avatars/qr') ->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 */ + /** @var Appwrite\Utopia\Response $response */ $download = ($download === '1' || $download === 'true' || $download === 1 || $download === true); @@ -399,7 +399,7 @@ App::get('/v1/avatars/initials') ->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\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ $themes = [ diff --git a/app/controllers/api/database.php b/app/controllers/api/database.php index deb00b4c76..45385034ab 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -2,7 +2,6 @@ use Utopia\App; use Utopia\Exception; -use Utopia\Response; use Utopia\Validator\Range; use Utopia\Validator\WhiteList; use Utopia\Validator\Text; @@ -20,11 +19,12 @@ use Appwrite\Database\Validator\Collection; use Appwrite\Database\Validator\Authorization; use Appwrite\Database\Exception\Authorization as AuthorizationException; use Appwrite\Database\Exception\Structure as StructureException; +use Appwrite\Utopia\Response; App::post('/v1/database/collections') ->desc('Create Collection') ->groups(['api', 'database']) - ->label('webhook', 'database.collections.create') + ->label('event', 'database.collections.create') ->label('scope', 'collections.write') ->label('sdk.namespace', 'database') ->label('sdk.platform', [APP_PLATFORM_SERVER]) @@ -35,7 +35,7 @@ App::post('/v1/database/collections') ->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 ($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, $webhooks, $audits) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $webhooks */ /** @var Appwrite\Event\Event $audits */ @@ -77,25 +77,14 @@ App::post('/v1/database/collections') throw new Exception('Failed saving collection to DB', 500); } - $data = $data->getArrayCopy(); - - $webhooks - ->setParam('payload', $data) - ; - $audits ->setParam('event', 'database.collections.create') - ->setParam('resource', 'database/collection/'.$data['$id']) - ->setParam('data', $data) + ->setParam('resource', 'database/collection/'.$data->getId()) + ->setParam('data', $data->getArrayCopy()) ; - /* - * View - */ - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($data) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic($data, Response::MODEL_COLLECTION); }, ['response', 'projectDB', 'webhooks', 'audits']); App::get('/v1/database/collections') @@ -111,7 +100,7 @@ App::get('/v1/database/collections') ->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'], true); }, 'Order result by ASC or DESC order.', true) ->action(function ($search, $limit, $offset, $orderType, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $results = $projectDB->getCollection([ @@ -126,7 +115,10 @@ App::get('/v1/database/collections') ], ]); - $response->json(['sum' => $projectDB->getSum(), 'collections' => $results]); + $response->dynamic(new Document([ + 'sum' => $projectDB->getSum(), + 'collections' => $results + ]), Response::MODEL_COLLECTION_LIST); }, ['response', 'projectDB']); App::get('/v1/database/collections/:collectionId') @@ -139,7 +131,7 @@ App::get('/v1/database/collections/:collectionId') ->label('sdk.description', '/docs/references/database/get-collection.md') ->param('collectionId', '', function () { return new UID(); }, 'Collection unique ID.') ->action(function ($collectionId, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $collection = $projectDB->getDocument($collectionId, false); @@ -148,79 +140,14 @@ App::get('/v1/database/collections/:collectionId') throw new Exception('Collection not found', 404); } - $response->json($collection->getArrayCopy()); + $response->dynamic($collection, Response::MODEL_COLLECTION); }, ['response', 'projectDB']); -// App::get('/v1/database/collections/:collectionId/logs') -// ->desc('Get Collection Logs') -// ->groups(['api', 'database']) -// ->label('scope', 'collections.read') -// ->label('sdk.platform', [APP_PLATFORM_SERVER]) -// ->label('sdk.namespace', 'database') -// ->label('sdk.method', 'getCollectionLogs') -// ->label('sdk.description', '/docs/references/database/get-collection-logs.md') -// ->param('collectionId', '', function () { return new UID(); }, 'Collection unique ID.') -// ->action( -// function ($collectionId) use ($response, $register, $projectDB, $project) { -// $collection = $projectDB->getDocument($collectionId, false); - -// if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { -// throw new Exception('Collection not found', 404); -// } - -// $adapter = new AuditAdapter($register->get('db')); -// $adapter->setNamespace('app_'.$project->getId()); - -// $audit = new Audit($adapter); - -// $countries = Locale::getText('countries'); - -// $logs = $audit->getLogsByResource('database/collection/'.$collection->getId()); - -// $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); -// } -// ); - App::put('/v1/database/collections/:collectionId') ->desc('Update Collection') ->groups(['api', 'database']) ->label('scope', 'collections.write') - ->label('webhook', 'database.collections.update') + ->label('event', 'database.collections.update') ->label('sdk.namespace', 'database') ->label('sdk.platform', [APP_PLATFORM_SERVER]) ->label('sdk.method', 'updateCollection') @@ -231,7 +158,7 @@ App::put('/v1/database/collections/:collectionId') ->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 ($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, $webhooks, $audits) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $webhooks */ /** @var Appwrite\Event\Event $audits */ @@ -277,33 +204,27 @@ App::put('/v1/database/collections/:collectionId') throw new Exception('Failed saving collection to DB', 500); } - $data = $collection->getArrayCopy(); - - $webhooks - ->setParam('payload', $data) - ; - $audits ->setParam('event', 'database.collections.update') - ->setParam('resource', 'database/collections/'.$data['$id']) - ->setParam('data', $data) + ->setParam('resource', 'database/collections/'.$collection->getId()) + ->setParam('data', $collection->getArrayCopy()) ; - $response->json($collection->getArrayCopy()); + $response->dynamic($collection, Response::MODEL_COLLECTION); }, ['response', 'projectDB', 'webhooks', 'audits']); App::delete('/v1/database/collections/:collectionId') ->desc('Delete Collection') ->groups(['api', 'database']) ->label('scope', 'collections.write') - ->label('webhook', 'database.collections.delete') + ->label('event', 'database.collections.delete') ->label('sdk.namespace', 'database') ->label('sdk.platform', [APP_PLATFORM_SERVER]) ->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, $response, $projectDB, $webhooks, $audits) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $webhooks */ /** @var Appwrite\Event\Event $audits */ @@ -318,16 +239,14 @@ App::delete('/v1/database/collections/:collectionId') throw new Exception('Failed to remove collection from DB', 500); } - $data = $collection->getArrayCopy(); - $webhooks - ->setParam('payload', $data) + ->setParam('payload', $response->output($collection, Response::MODEL_COLLECTION)) ; $audits ->setParam('event', 'database.collections.delete') - ->setParam('resource', 'database/collections/'.$data['$id']) - ->setParam('data', $data) + ->setParam('resource', 'database/collections/'.$collection->getId()) + ->setParam('data', $collection->getArrayCopy()) ; $response->noContent(); @@ -336,7 +255,7 @@ App::delete('/v1/database/collections/:collectionId') App::post('/v1/database/collections/:collectionId/documents') ->desc('Create Document') ->groups(['api', 'database']) - ->label('webhook', 'database.documents.create') + ->label('event', 'database.documents.create') ->label('scope', 'documents.write') ->label('sdk.namespace', 'database') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) @@ -350,7 +269,7 @@ App::post('/v1/database/collections/:collectionId/documents') ->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], true); }, '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, $response, $projectDB, $webhooks, $audits) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $webhooks */ /** @var Appwrite\Event\Event $audits */ @@ -387,11 +306,11 @@ App::post('/v1/database/collections/:collectionId/documents') } /* - * 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) - */ + * 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); @@ -436,25 +355,17 @@ App::post('/v1/database/collections/:collectionId/documents') throw new Exception('Failed saving document to DB'.$exception->getMessage(), 500); } - $data = $data->getArrayCopy(); - - $webhooks - ->setParam('payload', $data) - ; - $audits ->setParam('event', 'database.documents.create') ->setParam('resource', 'database/document/'.$data['$id']) - ->setParam('data', $data) + ->setParam('data', $data->getArrayCopy()) ; - /* - * View - */ $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($data) ; + + $response->dynamic($data, Response::MODEL_ANY); }, ['response', 'projectDB', 'webhooks', 'audits']); App::get('/v1/database/collections/:collectionId/documents') @@ -474,7 +385,7 @@ App::get('/v1/database/collections/:collectionId/documents') ->param('orderCast', 'string', function () { return new WhiteList(['int', 'string', 'date', 'time', 'datetime'], true); }, '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. Max length: 256 chars.', true) ->action(function ($collectionId, $filters, $limit, $offset, $orderField, $orderType, $orderCast, $search, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $collection = $projectDB->getDocument($collectionId, false); @@ -512,9 +423,6 @@ App::get('/v1/database/collections/:collectionId/documents') ->setAttribute('documents', $list) ; - /* - * View - */ $response->json($collection->getArrayCopy(/*['$id', '$collection', 'name', 'documents']*/[], ['rules'])); }, ['response', 'projectDB']); @@ -529,8 +437,8 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId') ->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, $request, $response, $projectDB) { - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $document = $projectDB->getDocument($documentId, false); @@ -540,36 +448,13 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId') 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); + $response->dynamic($document, Response::MODEL_ANY); }, ['request', 'response', 'projectDB']); App::patch('/v1/database/collections/:collectionId/documents/:documentId') ->desc('Update Document') ->groups(['api', 'database']) - ->label('webhook', 'database.documents.update') + ->label('event', 'database.documents.update') ->label('scope', 'documents.write') ->label('sdk.namespace', 'database') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) @@ -581,7 +466,7 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') ->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, $response, $projectDB, $webhooks, $audits) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $webhooks */ /** @var Appwrite\Event\Event $audits */ @@ -592,7 +477,7 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array if (!\is_array($data)) { - throw new Exception('Data param should be a valid JSON', 400); + throw new Exception('Data param should be a valid JSON object', 400); } if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { @@ -621,6 +506,7 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') if (empty($data)) { throw new Exception('Missing payload', 400); } + try { $data = $projectDB->updateDocument($data); } catch (AuthorizationException $exception) { @@ -631,29 +517,20 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') throw new Exception('Failed saving document to DB', 500); } - $data = $data->getArrayCopy(); - - $webhooks - ->setParam('payload', $data) - ; - $audits ->setParam('event', 'database.documents.update') - ->setParam('resource', 'database/document/'.$data['$id']) - ->setParam('data', $data) + ->setParam('resource', 'database/document/'.$data->getId()) + ->setParam('data', $data->getArrayCopy()) ; - /* - * View - */ - $response->json($data); + $response->dynamic($data, Response::MODEL_ANY); }, ['response', 'projectDB', 'webhooks', 'audits']); App::delete('/v1/database/collections/:collectionId/documents/:documentId') ->desc('Delete Document') ->groups(['api', 'database']) ->label('scope', 'documents.write') - ->label('webhook', 'database.documents.delete') + ->label('event', 'database.documents.delete') ->label('sdk.namespace', 'database') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.method', 'deleteDocument') @@ -661,7 +538,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') ->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, $response, $projectDB, $webhooks, $audits) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $webhooks */ /** @var Appwrite\Event\Event $audits */ @@ -687,16 +564,14 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') throw new Exception('Failed to remove document from DB', 500); } - $data = $document->getArrayCopy(); - $webhooks - ->setParam('payload', $data) + ->setParam('payload', $response->output($document, Response::MODEL_ANY)) ; - + $audits ->setParam('event', 'database.documents.delete') - ->setParam('resource', 'database/document/'.$data['$id']) - ->setParam('data', $data) // Audit document in case of malicious or disastrous action + ->setParam('resource', 'database/document/'.$document->getId()) + ->setParam('data', $document->getArrayCopy()) // Audit document in case of malicious or disastrous action ; $response->noContent(); diff --git a/app/controllers/api/functions.php b/app/controllers/api/functions.php index 3bcd2fdf2d..92e0ac84f9 100644 --- a/app/controllers/api/functions.php +++ b/app/controllers/api/functions.php @@ -1,15 +1,16 @@ time(), 'dateUpdated' => time(), - 'status' => 'paused', + 'status' => 'disabled', 'name' => $name, 'env' => $env, 'tag' => '', @@ -59,10 +60,8 @@ App::post('/v1/functions') throw new Exception('Failed saving function to DB', 500); } - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($function->getArrayCopy()) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic($function, Response::MODEL_FUNCTION); }, ['response', 'projectDB']); App::get('/v1/functions') @@ -90,7 +89,10 @@ App::get('/v1/functions') ], ]); - $response->json(['sum' => $projectDB->getSum(), 'functions' => $results]); + $response->dynamic(new Document([ + 'sum' => $projectDB->getSum(), + 'functions' => $results + ]), Response::MODEL_FUNCTION_LIST); }, ['response', 'projectDB']); App::get('/v1/functions/:functionId') @@ -106,12 +108,125 @@ App::get('/v1/functions/:functionId') $function = $projectDB->getDocument($functionId); if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { - throw new Exception('function not found', 404); + throw new Exception('Function not found', 404); } - $response->json($function->getArrayCopy()); + $response->dynamic($function, Response::MODEL_FUNCTION); }, ['response', 'projectDB']); +App::get('/v1/functions/:functionId/usage') + ->desc('Get Function Usage') + ->groups(['api', 'functions']) + ->label('scope', 'functions.read') + ->label('sdk.platform', [APP_PLATFORM_CONSOLE]) + ->label('sdk.namespace', 'functions') + ->label('sdk.method', 'getUsage') + ->param('functionId', '', function () { return new UID(); }, 'Function unique ID.') + ->param('range', '30d', function () { return new WhiteList(['24h', '7d', '30d', '90d']); }, 'Date range.', true) + ->action(function ($functionId, $range, $response, $project, $projectDB, $register) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Document $project */ + /** @var Appwrite\Database\Database $consoleDB */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Registry\Registry $register */ + + $function = $projectDB->getDocument($functionId); + + if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { + throw new Exception('Function not found', 404); + } + + $period = [ + '24h' => [ + 'start' => DateTime::createFromFormat('U', \strtotime('-24 hours')), + 'end' => DateTime::createFromFormat('U', \strtotime('+1 hour')), + 'group' => '30m', + ], + '7d' => [ + 'start' => DateTime::createFromFormat('U', \strtotime('-7 days')), + 'end' => DateTime::createFromFormat('U', \strtotime('now')), + 'group' => '1d', + ], + '30d' => [ + 'start' => DateTime::createFromFormat('U', \strtotime('-30 days')), + 'end' => DateTime::createFromFormat('U', \strtotime('now')), + 'group' => '1d', + ], + '90d' => [ + 'start' => DateTime::createFromFormat('U', \strtotime('-90 days')), + 'end' => DateTime::createFromFormat('U', \strtotime('now')), + 'group' => '1d', + ], + ]; + + $client = $register->get('influxdb'); + + $executions = []; + $failures = []; + $compute = []; + + if ($client) { + $start = $period[$range]['start']->format(DateTime::RFC3339); + $end = $period[$range]['end']->format(DateTime::RFC3339); + $database = $client->selectDB('telegraf'); + + // Executions + $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' AND "functionId"=\''.$function->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)'); + $points = $result->getPoints(); + + foreach ($points as $point) { + $executions[] = [ + 'value' => (!empty($point['value'])) ? $point['value'] : 0, + 'date' => \strtotime($point['time']), + ]; + } + + // Failures + $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_all" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' AND "functionId"=\''.$function->getId().'\' AND "functionStatus"=\'failed\' GROUP BY time('.$period[$range]['group'].') FILL(null)'); + $points = $result->getPoints(); + + foreach ($points as $point) { + $failures[] = [ + 'value' => (!empty($point['value'])) ? $point['value'] : 0, + 'date' => \strtotime($point['time']), + ]; + } + + // Compute + $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_time" WHERE time > \''.$start.'\' AND time < \''.$end.'\' AND "metric_type"=\'counter\' AND "project"=\''.$project->getId().'\' AND "functionId"=\''.$function->getId().'\' GROUP BY time('.$period[$range]['group'].') FILL(null)'); + $points = $result->getPoints(); + + foreach ($points as $point) { + $compute[] = [ + 'value' => round((!empty($point['value'])) ? $point['value'] / 1000 : 0, 2), // minutes + 'date' => \strtotime($point['time']), + ]; + } + } + + $response->json([ + 'range' => $range, + 'executions' => [ + 'data' => $executions, + 'total' => \array_sum(\array_map(function ($item) { + return $item['value']; + }, $executions)), + ], + 'failures' => [ + 'data' => $failures, + 'total' => \array_sum(\array_map(function ($item) { + return $item['value']; + }, $failures)), + ], + 'compute' => [ + 'data' => $compute, + 'total' => \array_sum(\array_map(function ($item) { + return $item['value']; + }, $compute)), + ], + ]); + }, ['response', 'project', 'projectDB', 'register']); + App::put('/v1/functions/:functionId') ->groups(['api', 'functions']) ->desc('Update Function') @@ -151,7 +266,7 @@ App::put('/v1/functions/:functionId') throw new Exception('Failed saving function to DB', 500); } - $response->json($function->getArrayCopy()); + $response->dynamic($function, Response::MODEL_FUNCTION); }, ['response', 'projectDB']); App::patch('/v1/functions/:functionId/tag') @@ -189,7 +304,7 @@ App::patch('/v1/functions/:functionId/tag') throw new Exception('Failed saving function to DB', 500); } - $response->json($function->getArrayCopy()); + $response->dynamic($function, Response::MODEL_FUNCTION); }, ['response', 'projectDB']); App::delete('/v1/functions/:functionId') @@ -201,7 +316,12 @@ App::delete('/v1/functions/:functionId') ->label('sdk.method', 'delete') ->label('sdk.description', '/docs/references/functions/delete-function.md') ->param('functionId', '', function () { return new UID(); }, 'Function unique ID.') - ->action(function ($functionId, $response, $projectDB) { + ->action(function ($functionId, $response, $project, $projectDB, $deletes) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Document $project */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $deletes */ + $function = $projectDB->getDocument($functionId); if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) { @@ -212,8 +332,12 @@ App::delete('/v1/functions/:functionId') throw new Exception('Failed to remove function from DB', 500); } + $deletes + ->setParam('document', $function->getArrayCopy()) + ; + $response->noContent(); - }, ['response', 'projectDB']); + }, ['response', 'project', 'projectDB', 'deletes']); App::post('/v1/functions/:functionId/tags') ->groups(['api', 'functions']) @@ -276,11 +400,11 @@ App::post('/v1/functions/:functionId/tags') 'read' => [], 'write' => [], ], - 'dateCreated' => time(), 'functionId' => $function->getId(), + 'dateCreated' => time(), 'command' => $command, - 'codePath' => $path, - 'codeSize' => $size, + 'path' => $path, + 'size' => $size, ]); if (false === $tag) { @@ -288,13 +412,11 @@ App::post('/v1/functions/:functionId/tags') } $usage - ->setParam('storage', $tag->getAttribute('codeSize', 0)) + ->setParam('storage', $tag->getAttribute('size', 0)) ; - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($tag->getArrayCopy()) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic($tag, Response::MODEL_TAG); }, ['request', 'response', 'projectDB', 'usage']); App::get('/v1/functions/:functionId/tags') @@ -330,7 +452,10 @@ App::get('/v1/functions/:functionId/tags') ], ]); - $response->json(['sum' => $projectDB->getSum(), 'tags' => $results]); + $response->dynamic(new Document([ + 'sum' => $projectDB->getSum(), + 'tags' => $results + ]), Response::MODEL_TAG_LIST); }, ['response', 'projectDB']); App::get('/v1/functions/:functionId/tags/:tagId') @@ -360,7 +485,7 @@ App::get('/v1/functions/:functionId/tags/:tagId') throw new Exception('Tag not found', 404); } - $response->json($tag->getArrayCopy()); + $response->dynamic($tag, Response::MODEL_TAG); }, ['response', 'projectDB']); App::delete('/v1/functions/:functionId/tags/:tagId') @@ -392,14 +517,24 @@ App::delete('/v1/functions/:functionId/tags/:tagId') $device = Storage::getDevice('functions'); - if ($device->delete($tag->getAttribute('codePath', ''))) { + if ($device->delete($tag->getAttribute('path', ''))) { if (!$projectDB->deleteDocument($tag->getId())) { throw new Exception('Failed to remove tag from DB', 500); } } + if($function->getAttribute('tag') === $tag->getId()) { // Reset function tag + $function = $projectDB->updateDocument(array_merge($function->getArrayCopy(), [ + 'tag' => '', + ])); + + if (false === $function) { + throw new Exception('Failed saving function to DB', 500); + } + } + $usage - ->setParam('storage', $tag->getAttribute('codeSize', 0) * -1) + ->setParam('storage', $tag->getAttribute('size', 0) * -1) ; $response->noContent(); @@ -414,9 +549,9 @@ App::post('/v1/functions/:functionId/executions') ->label('sdk.method', 'createExecution') ->label('sdk.description', '/docs/references/functions/create-execution.md') ->param('functionId', '', function () { return new UID(); }, 'Function unique ID.') - ->param('async', 1, function () { return new Range(0, 1); }, 'Execute code asynchronously. Pass 1 for true, 0 for false. Default value is 1.', true) - ->action(function ($functionId, $async, $response, $project, $projectDB) { - /** @var Utopia\Response $response */ + // ->param('async', 1, function () { return new Range(0, 1); }, 'Execute code asynchronously. Pass 1 for true, 0 for false. Default value is 1.', true) + ->action(function ($functionId, /*$async,*/ $response, $project, $projectDB) { + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ /** @var Appwrite\Database\Database $projectDB */ @@ -444,6 +579,7 @@ App::post('/v1/functions/:functionId/executions') ], 'dateCreated' => time(), 'functionId' => $function->getId(), + 'trigger' => 'http', // http / schedule / event 'status' => 'waiting', // waiting / processing / completed / failed 'exitCode' => 0, 'stdout' => '', @@ -454,22 +590,17 @@ App::post('/v1/functions/:functionId/executions') if (false === $execution) { throw new Exception('Failed saving execution to DB', 500); } - - if((bool)$async) { - // Issue a TLS certificate when domain is verified - Resque::enqueue('v1-functions', 'FunctionsV1', [ - 'projectId' => $project->getId(), - 'functionId' => $function->getId(), - 'executionId' => $execution->getId(), - 'functionTag' => $tag->getId(), - 'functionTrigger' => 'API', - ]); - } + + // Issue a TLS certificate when domain is verified + Resque::enqueue('v1-functions', 'FunctionsV1', [ + 'projectId' => $project->getId(), + 'functionId' => $function->getId(), + 'executionId' => $execution->getId(), + 'trigger' => 'http', + ]); - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($execution->getArrayCopy()) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic($execution, Response::MODEL_EXECUTION); }, ['response', 'project', 'projectDB']); App::get('/v1/functions/:functionId/executions') @@ -505,7 +636,10 @@ App::get('/v1/functions/:functionId/executions') ], ]); - $response->json(['sum' => $projectDB->getSum(), 'executions' => $results]); + $response->dynamic(new Document([ + 'sum' => $projectDB->getSum(), + 'executions' => $results + ]), Response::MODEL_EXECUTION_LIST); }, ['response', 'projectDB']); App::get('/v1/functions/:functionId/executions/:executionId') @@ -535,5 +669,5 @@ App::get('/v1/functions/:functionId/executions/:executionId') throw new Exception('Execution not found', 404); } - $response->json($execution->getArrayCopy()); + $response->dynamic($execution, Response::MODEL_EXECUTION); }, ['response', 'projectDB']); \ No newline at end of file diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index b8955a1989..4caeda20d4 100644 --- a/app/controllers/api/health.php +++ b/app/controllers/api/health.php @@ -15,7 +15,7 @@ App::get('/v1/health') ->label('sdk.method', 'get') ->label('sdk.description', '/docs/references/health/get.md') ->action(function ($response) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ $response->json(['status' => 'OK']); }, ['response']); @@ -25,7 +25,7 @@ App::get('/v1/health/version') ->groups(['api', 'health']) ->label('scope', 'public') ->action(function ($response) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ $response->json(['version' => APP_VERSION_STABLE]); }, ['response']); @@ -39,7 +39,7 @@ App::get('/v1/health/db') ->label('sdk.method', 'getDB') ->label('sdk.description', '/docs/references/health/get-db.md') ->action(function ($response, $register) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Registry\Registry $register */ $register->get('db'); /* @var $db PDO */ @@ -56,7 +56,7 @@ App::get('/v1/health/cache') ->label('sdk.method', 'getCache') ->label('sdk.description', '/docs/references/health/get-cache.md') ->action(function ($response, $register) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Registry\Registry $register */ $register->get('cache'); /* @var $cache Predis\Client */ @@ -72,7 +72,7 @@ App::get('/v1/health/time') ->label('sdk.method', 'getTime') ->label('sdk.description', '/docs/references/health/get-time.md') ->action(function ($response) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /* * Code from: @see https://www.beliefmedia.com.au/query-ntp-time-server @@ -120,7 +120,7 @@ App::get('/v1/health/queue/webhooks') ->label('sdk.method', 'getQueueWebhooks') ->label('sdk.description', '/docs/references/health/get-queue-webhooks.md') ->action(function ($response) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ $response->json(['size' => Resque::size('v1-webhooks')]); }, ['response']); @@ -134,7 +134,7 @@ App::get('/v1/health/queue/tasks') ->label('sdk.method', 'getQueueTasks') ->label('sdk.description', '/docs/references/health/get-queue-tasks.md') ->action(function ($response) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ $response->json(['size' => Resque::size('v1-tasks')]); }, ['response']); @@ -148,7 +148,7 @@ App::get('/v1/health/queue/logs') ->label('sdk.method', 'getQueueLogs') ->label('sdk.description', '/docs/references/health/get-queue-logs.md') ->action(function ($response) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ $response->json(['size' => Resque::size('v1-audit')]); }, ['response']); @@ -162,7 +162,7 @@ App::get('/v1/health/queue/usage') ->label('sdk.method', 'getQueueUsage') ->label('sdk.description', '/docs/references/health/get-queue-usage.md') ->action(function ($response) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ $response->json(['size' => Resque::size('v1-usage')]); }, ['response']); @@ -176,7 +176,7 @@ App::get('/v1/health/queue/certificates') ->label('sdk.method', 'getQueueCertificates') ->label('sdk.description', '/docs/references/health/get-queue-certificates.md') ->action(function ($response) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ $response->json(['size' => Resque::size('v1-certificates')]); }, ['response']); @@ -190,7 +190,7 @@ App::get('/v1/health/queue/functions') ->label('sdk.method', 'getQueueFunctions') ->label('sdk.description', '/docs/references/health/get-queue-functions.md') ->action(function ($response) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ $response->json(['size' => Resque::size('v1-functions')]); }, ['response']); @@ -204,7 +204,7 @@ App::get('/v1/health/storage/local') ->label('sdk.method', 'getStorageLocal') ->label('sdk.description', '/docs/references/health/get-storage-local.md') ->action(function ($response) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ foreach ([ 'Uploads' => APP_STORAGE_UPLOADS, @@ -235,7 +235,7 @@ App::get('/v1/health/anti-virus') ->label('sdk.method', 'getAntiVirus') ->label('sdk.description', '/docs/references/health/get-storage-anti-virus.md') ->action(function ($response) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ if (App::getEnv('_APP_STORAGE_ANTIVIRUS') === 'disabled') { // Check if scans are enabled throw new Exception('Anitvirus is disabled'); @@ -258,7 +258,7 @@ App::get('/v1/health/stats') // Currently only used internally // ->label('sdk.method', 'getStats') ->label('docs', false) ->action(function ($response, $register) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Registry\Registry $register */ $device = Storage::getDevice('files'); diff --git a/app/controllers/api/locale.php b/app/controllers/api/locale.php index 8431e00f6a..a33eea5d64 100644 --- a/app/controllers/api/locale.php +++ b/app/controllers/api/locale.php @@ -1,5 +1,7 @@ label('sdk.method', 'get') ->label('sdk.description', '/docs/references/locale/get-locale.md') ->action(function ($request, $response, $locale, $geodb) { - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Locale\Locale $locale */ /** @var GeoIp2\Database\Reader $geodb */ @@ -25,10 +27,6 @@ App::get('/v1/locale') $countries = $locale->getText('countries'); $continents = $locale->getText('continents'); - if (!App::isProduction()) { - $ip = '79.177.241.94'; - } - $output['ip'] = $ip; $currency = null; @@ -61,7 +59,8 @@ App::get('/v1/locale') $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); + ; + $response->dynamic(new Document($output), Response::MODEL_LOCALE); }, ['request', 'response', 'locale', 'geodb']); App::get('/v1/locale/countries') @@ -73,14 +72,22 @@ App::get('/v1/locale/countries') ->label('sdk.method', 'getCountries') ->label('sdk.description', '/docs/references/locale/get-countries.md') ->action(function ($response, $locale) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Locale\Locale $locale */ $list = $locale->getText('countries'); /* @var $list array */ + $output = []; - \asort($list); + \asort($list); // sort by abc per locale - $response->json($list); + foreach ($list as $key => $value) { + $output[] = new Document([ + 'name' => $value, + 'code' => $key, + ]); + } + + $response->dynamic(new Document(['countries' => $output, 'sum' => \count($output)]), Response::MODEL_COUNTRY_LIST); }, ['response', 'locale']); App::get('/v1/locale/countries/eu') @@ -92,22 +99,25 @@ App::get('/v1/locale/countries/eu') ->label('sdk.method', 'getCountriesEU') ->label('sdk.description', '/docs/references/locale/get-countries-eu.md') ->action(function ($response, $locale) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Locale\Locale $locale */ - $countries = $locale->getText('countries'); /* @var $countries array */ + $list = $locale->getText('countries'); /* @var $countries array */ $eu = Config::getParam('locale-eu'); - $list = []; - - foreach ($eu as $code) { - if (\array_key_exists($code, $countries)) { - $list[$code] = $countries[$code]; - } - } + $output = []; \asort($list); - $response->json($list); + foreach ($eu as $code) { + if (\array_key_exists($code, $list)) { + $output[] = new Document([ + 'name' => $list[$code], + 'code' => $code, + ]); + } + } + + $response->dynamic(new Document(['countries' => $output, 'sum' => \count($output)]), Response::MODEL_COUNTRY_LIST); }, ['response', 'locale']); App::get('/v1/locale/countries/phones') @@ -119,22 +129,26 @@ App::get('/v1/locale/countries/phones') ->label('sdk.method', 'getCountriesPhones') ->label('sdk.description', '/docs/references/locale/get-countries-phones.md') ->action(function ($response, $locale) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Locale\Locale $locale */ $list = Config::getParam('locale-phones'); /* @var $list array */ - $countries = $locale->getText('countries'); /* @var $countries array */ + $output = []; + + \asort($list); foreach ($list as $code => $name) { if (\array_key_exists($code, $countries)) { - $list[$code] = '+'.$list[$code]; + $output[] = new Document([ + 'code' => '+'.$list[$code], + 'countryCode' => $code, + 'countryName' => $countries[$code], + ]); } } - \asort($list); - - $response->json($list); + $response->dynamic(new Document(['phones' => $output, 'sum' => \count($output)]), Response::MODEL_PHONE_LIST); }, ['response', 'locale']); App::get('/v1/locale/continents') @@ -146,14 +160,21 @@ App::get('/v1/locale/continents') ->label('sdk.method', 'getContinents') ->label('sdk.description', '/docs/references/locale/get-continents.md') ->action(function ($response, $locale) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Locale\Locale $locale */ $list = $locale->getText('continents'); /* @var $list array */ \asort($list); + + foreach ($list as $key => $value) { + $output[] = new Document([ + 'name' => $value, + 'code' => $key, + ]); + } - $response->json($list); + $response->dynamic(new Document(['continents' => $output, 'sum' => \count($output)]), Response::MODEL_CONTINENT_LIST); }, ['response', 'locale']); App::get('/v1/locale/currencies') @@ -165,11 +186,15 @@ App::get('/v1/locale/currencies') ->label('sdk.method', 'getCurrencies') ->label('sdk.description', '/docs/references/locale/get-currencies.md') ->action(function ($response) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ - $currencies = Config::getParam('locale-currencies'); + $list = Config::getParam('locale-currencies'); - $response->json($currencies); + $list = array_map(function($node) { + return new Document($node); + }, $list); + + $response->dynamic(new Document(['currencies' => $list, 'sum' => \count($list)]), Response::MODEL_CURRENCY_LIST); }, ['response']); @@ -182,9 +207,13 @@ App::get('/v1/locale/languages') ->label('sdk.method', 'getLanguages') ->label('sdk.description', '/docs/references/locale/get-languages.md') ->action(function ($response) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ - $languages = Config::getParam('locale-languages'); + $list = Config::getParam('locale-languages'); - $response->json($languages); + $list = array_map(function($node) { + return new Document($node); + }, $list); + + $response->dynamic(new Document(['languages' => $list, 'sum' => \count($list)]), Response::MODEL_LANGUAGE_LIST); }, ['response']); \ No newline at end of file diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 7ac60477c6..34536ea3b7 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -2,7 +2,6 @@ use Utopia\App; use Utopia\Exception; -use Utopia\Response; use Utopia\Validator\ArrayList; use Utopia\Validator\Boolean; use Utopia\Validator\Text; @@ -19,6 +18,7 @@ use Appwrite\Database\Validator\UID; use Appwrite\OpenSSL\OpenSSL; use Appwrite\Network\Validator\CNAME; use Appwrite\Network\Validator\Domain as DomainValidator; +use Appwrite\Utopia\Response; use Cron\CronExpression; App::post('/v1/projects') @@ -39,7 +39,7 @@ App::post('/v1/projects') ->param('legalAddress', '', function () { return new Text(256); }, 'Project legal Address. Max length: 256 chars.', true) ->param('legalTaxId', '', function () { return new Text(256); }, 'Project legal Tax ID. Max length: 256 chars.', true) ->action(function ($name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $consoleDB, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ /** @var Appwrite\Database\Database $projectDB */ @@ -97,7 +97,7 @@ App::get('/v1/projects') ->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'], true); }, 'Order result by ASC or DESC order.', true) ->action(function ($search, $limit, $offset, $orderType, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $results = $consoleDB->getCollection([ @@ -133,7 +133,7 @@ App::get('/v1/projects/:projectId') ->label('sdk.method', 'get') ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.') ->action(function ($projectId, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -161,9 +161,9 @@ App::get('/v1/projects/:projectId/usage') ->label('sdk.namespace', 'projects') ->label('sdk.method', 'getUsage') ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.') - ->param('range', 'last30', function () { return new WhiteList(['daily', 'monthly', 'last30', 'last90'], true); }, 'Date range.', true) + ->param('range', '30d', function () { return new WhiteList(['24h', '7d', '30d', '90d']); }, 'Date range.', true) ->action(function ($projectId, $range, $response, $consoleDB, $projectDB, $register) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ /** @var Appwrite\Database\Database $projectDB */ /** @var Utopia\Registry\Registry $register */ @@ -175,37 +175,33 @@ App::get('/v1/projects/:projectId/usage') } $period = [ - 'daily' => [ - 'start' => DateTime::createFromFormat('U', \strtotime('today')), - 'end' => DateTime::createFromFormat('U', \strtotime('tomorrow')), - 'group' => '1m', + '24h' => [ + 'start' => DateTime::createFromFormat('U', \strtotime('-24 hours')), + 'end' => DateTime::createFromFormat('U', \strtotime('+1 hour')), + 'group' => '30m', ], - 'monthly' => [ - 'start' => DateTime::createFromFormat('U', \strtotime('midnight first day of this month')), - 'end' => DateTime::createFromFormat('U', \strtotime('midnight last day of this month')), + '7d' => [ + 'start' => DateTime::createFromFormat('U', \strtotime('-7 days')), + 'end' => DateTime::createFromFormat('U', \strtotime('now')), 'group' => '1d', ], - 'last30' => [ + '30d' => [ 'start' => DateTime::createFromFormat('U', \strtotime('-30 days')), - 'end' => DateTime::createFromFormat('U', \strtotime('tomorrow')), + 'end' => DateTime::createFromFormat('U', \strtotime('now')), 'group' => '1d', ], - 'last90' => [ + '90d' => [ 'start' => DateTime::createFromFormat('U', \strtotime('-90 days')), - 'end' => DateTime::createFromFormat('U', \strtotime('today')), + 'end' => DateTime::createFromFormat('U', \strtotime('now')), '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 = []; + $functions = []; if ($client) { $start = $period[$range]['start']->format(DateTime::RFC3339); @@ -233,6 +229,17 @@ App::get('/v1/projects/:projectId/usage') 'date' => \strtotime($point['time']), ]; } + + // Functions + $result = $database->query('SELECT sum(value) AS "value" FROM "appwrite_usage_executions_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) { + $functions[] = [ + 'value' => (!empty($point['value'])) ? $point['value'] : 0, + 'date' => \strtotime($point['time']), + ]; + } } // Users @@ -277,6 +284,7 @@ App::get('/v1/projects/:projectId/usage') $tasksTotal = \count($project->getAttribute('tasks', [])); $response->json([ + 'range' => $range, 'requests' => [ 'data' => $requests, 'total' => \array_sum(\array_map(function ($item) { @@ -289,6 +297,12 @@ App::get('/v1/projects/:projectId/usage') return $item['value']; }, $network)), ], + 'functions' => [ + 'data' => $functions, + 'total' => \array_sum(\array_map(function ($item) { + return $item['value']; + }, $functions)), + ], 'collections' => [ 'data' => $collections, 'total' => $collectionsTotal, @@ -318,7 +332,7 @@ App::get('/v1/projects/:projectId/usage') ) + $projectDB->getCount( [ - 'attribute' => 'codeSize', + 'attribute' => 'size', 'filters' => [ '$collection='.Database::SYSTEM_COLLECTION_TAGS, ], @@ -346,7 +360,7 @@ App::patch('/v1/projects/:projectId') ->param('legalAddress', '', function () { return new Text(256); }, 'Project legal address. Max length: 256 chars.', true) ->param('legalTaxId', '', function () { return new Text(256); }, 'Project legal tax ID. Max length: 256 chars.', true) ->action(function ($projectId, $name, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -386,7 +400,7 @@ App::patch('/v1/projects/:projectId/oauth2') ->param('appId', '', function () { return new Text(256); }, 'Provider app ID. Max length: 256 chars.', true) ->param('secret', '', function () { return new text(512); }, 'Provider secret key. Max length: 512 chars.', true) ->action(function ($projectId, $provider, $appId, $secret, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -427,7 +441,7 @@ App::delete('/v1/projects/:projectId') ->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, $response, $user, $consoleDB, $deletes) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $consoleDB */ /** @var Appwrite\Event\Event $deletes */ @@ -481,7 +495,7 @@ App::post('/v1/projects/:projectId/webhooks') ->param('httpUser', '', function () { return new Text(256); }, 'Webhook HTTP user. Max length: 256 chars.', true) ->param('httpPass', '', function () { return new Text(256); }, 'Webhook HTTP password. Max length: 256 chars.', true) ->action(function ($projectId, $name, $events, $url, $security, $httpUser, $httpPass, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -528,10 +542,8 @@ App::post('/v1/projects/:projectId/webhooks') throw new Exception('Failed saving project to DB', 500); } - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($webhook->getArrayCopy()) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic($webhook, Response::MODEL_WEBHOOK); }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/webhooks') @@ -542,7 +554,7 @@ App::get('/v1/projects/:projectId/webhooks') ->label('sdk.method', 'listWebhooks') ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.') ->action(function ($projectId, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -565,7 +577,10 @@ App::get('/v1/projects/:projectId/webhooks') $webhook->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag']))); } - $response->json($webhooks); + $response->dynamic(new Document([ + 'sum' => count($webhooks), + 'webhooks' => $webhooks + ]), Response::MODEL_WEBHOOK_LIST); }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/webhooks/:webhookId') @@ -577,7 +592,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, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -599,7 +614,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId') $webhook->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag']))); } - $response->json($webhook->getArrayCopy()); + $response->dynamic($webhook, Response::MODEL_WEBHOOK); }, ['response', 'consoleDB']); App::put('/v1/projects/:projectId/webhooks/:webhookId') @@ -617,7 +632,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') ->param('httpUser', '', function () { return new Text(256); }, 'Webhook HTTP user. Max length: 256 chars.', true) ->param('httpPass', '', function () { return new Text(256); }, 'Webhook HTTP password. Max length: 256 chars.', true) ->action(function ($projectId, $webhookId, $name, $events, $url, $security, $httpUser, $httpPass, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -657,7 +672,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') throw new Exception('Failed saving webhook to DB', 500); } - $response->json($webhook->getArrayCopy()); + $response->dynamic($webhook, Response::MODEL_WEBHOOK); }, ['response', 'consoleDB']); App::delete('/v1/projects/:projectId/webhooks/:webhookId') @@ -669,7 +684,7 @@ App::delete('/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, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -703,7 +718,7 @@ App::post('/v1/projects/:projectId/keys') ->param('name', null, function () { return new Text(128); }, 'Key name. Max length: 128 chars.') ->param('scopes', null, function () { return new ArrayList(new WhiteList(Config::getParam('scopes'), true)); }, 'Key scopes list.') ->action(function ($projectId, $name, $scopes, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -735,10 +750,8 @@ App::post('/v1/projects/:projectId/keys') throw new Exception('Failed saving project to DB', 500); } - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($key->getArrayCopy()) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic($key, Response::MODEL_KEY); }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/keys') @@ -749,7 +762,7 @@ App::get('/v1/projects/:projectId/keys') ->label('sdk.method', 'listKeys') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->action(function ($projectId, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -758,7 +771,12 @@ App::get('/v1/projects/:projectId/keys') throw new Exception('Project not found', 404); } - $response->json($project->getAttribute('keys', [])); //FIXME make sure array objects return correctly + $keys = $project->getAttribute('keys', []); + + $response->dynamic(new Document([ + 'sum' => count($keys), + 'keys' => $keys + ]), Response::MODEL_KEY_LIST); }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/keys/:keyId') @@ -782,7 +800,7 @@ App::get('/v1/projects/:projectId/keys/:keyId') throw new Exception('Key not found', 404); } - $response->json($key->getArrayCopy()); + $response->dynamic($key, Response::MODEL_KEY); }, ['response', 'consoleDB']); App::put('/v1/projects/:projectId/keys/:keyId') @@ -796,7 +814,7 @@ App::put('/v1/projects/:projectId/keys/:keyId') ->param('name', null, function () { return new Text(128); }, 'Key name. Max length: 128 chars.') ->param('scopes', null, function () { return new ArrayList(new WhiteList(Config::getParam('scopes'), true)); }, 'Key scopes list') ->action(function ($projectId, $keyId, $name, $scopes, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -820,7 +838,7 @@ App::put('/v1/projects/:projectId/keys/:keyId') throw new Exception('Failed saving key to DB', 500); } - $response->json($key->getArrayCopy()); + $response->dynamic($key, Response::MODEL_KEY); }, ['response', 'consoleDB']); App::delete('/v1/projects/:projectId/keys/:keyId') @@ -832,7 +850,7 @@ App::delete('/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.') ->action(function ($projectId, $keyId, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -873,7 +891,7 @@ App::post('/v1/projects/:projectId/tasks') ->param('httpUser', '', function () { return new Text(256); }, 'Task HTTP user. Max length: 256 chars.', true) ->param('httpPass', '', function () { return new Text(256); }, 'Task HTTP password. Max length: 256 chars.', true) ->action(function ($projectId, $name, $status, $schedule, $security, $httpMethod, $httpUrl, $httpHeaders, $httpUser, $httpPass, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -935,10 +953,8 @@ App::post('/v1/projects/:projectId/tasks') ResqueScheduler::enqueueAt($next, 'v1-tasks', 'TasksV1', $task->getArrayCopy()); } - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($task->getArrayCopy()) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic($task, Response::MODEL_TASK); }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/tasks') @@ -949,7 +965,7 @@ App::get('/v1/projects/:projectId/tasks') ->label('sdk.method', 'listTasks') ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.') ->action(function ($projectId, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -972,7 +988,10 @@ App::get('/v1/projects/:projectId/tasks') $task->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag']))); } - $response->json($tasks); + $response->dynamic(new Document([ + 'sum' => count($tasks), + 'tasks' => $tasks + ]), Response::MODEL_TASK_LIST); }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/tasks/:taskId') @@ -984,7 +1003,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, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -1006,7 +1025,7 @@ App::get('/v1/projects/:projectId/tasks/:taskId') $task->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, \hex2bin($httpPass['iv']), \hex2bin($httpPass['tag']))); } - $response->json($task->getArrayCopy()); + $response->dynamic($task, Response::MODEL_TASK); }, ['response', 'consoleDB']); App::put('/v1/projects/:projectId/tasks/:taskId') @@ -1027,7 +1046,7 @@ App::put('/v1/projects/:projectId/tasks/:taskId') ->param('httpUser', '', function () { return new Text(256); }, 'Task HTTP user. Max length: 256 chars.', true) ->param('httpPass', '', function () { return new Text(256); }, 'Task HTTP password. Max length: 256 chars.', true) ->action(function ($projectId, $taskId, $name, $status, $schedule, $security, $httpMethod, $httpUrl, $httpHeaders, $httpUser, $httpPass, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -1079,7 +1098,7 @@ App::put('/v1/projects/:projectId/tasks/:taskId') ResqueScheduler::enqueueAt($next, 'v1-tasks', 'TasksV1', $task->getArrayCopy()); } - $response->json($task->getArrayCopy()); + $response->dynamic($task, Response::MODEL_TASK); }, ['response', 'consoleDB']); App::delete('/v1/projects/:projectId/tasks/:taskId') @@ -1091,7 +1110,7 @@ App::delete('/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, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -1128,7 +1147,7 @@ App::post('/v1/projects/:projectId/platforms') ->param('store', '', function () { return new Text(256); }, 'App store or Google Play store ID. Max length: 256 chars.', true) ->param('hostname', '', function () { return new Text(256); }, 'Platform client hostname. Max length: 256 chars.', true) ->action(function ($projectId, $type, $name, $key, $store, $hostname, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -1164,10 +1183,8 @@ App::post('/v1/projects/:projectId/platforms') throw new Exception('Failed saving project to DB', 500); } - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($platform->getArrayCopy()) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic($platform, Response::MODEL_PLATFORM); }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/platforms') @@ -1178,7 +1195,7 @@ App::get('/v1/projects/:projectId/platforms') ->label('sdk.method', 'listPlatforms') ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.') ->action(function ($projectId, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -1189,7 +1206,10 @@ App::get('/v1/projects/:projectId/platforms') $platforms = $project->getAttribute('platforms', []); - $response->json($platforms); + $response->dynamic(new Document([ + 'sum' => count($platforms), + 'platforms' => $platforms + ]), Response::MODEL_PLATFORM_LIST); }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/platforms/:platformId') @@ -1201,7 +1221,7 @@ App::get('/v1/projects/:projectId/platforms/:platformId') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('platformId', null, function () { return new UID(); }, 'Platform unique ID.') ->action(function ($projectId, $platformId, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -1216,7 +1236,7 @@ App::get('/v1/projects/:projectId/platforms/:platformId') throw new Exception('Platform not found', 404); } - $response->json($platform->getArrayCopy()); + $response->dynamic($platform, Response::MODEL_PLATFORM); }, ['response', 'consoleDB']); App::put('/v1/projects/:projectId/platforms/:platformId') @@ -1232,7 +1252,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId') ->param('store', '', function () { return new Text(256); }, 'App store or Google Play store ID. Max length: 256 chars.', true) ->param('hostname', '', function () { return new Text(256); }, 'Platform client URL. Max length: 256 chars.', true) ->action(function ($projectId, $platformId, $name, $key, $store, $hostname, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -1259,7 +1279,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId') throw new Exception('Failed saving platform to DB', 500); } - $response->json($platform->getArrayCopy()); + $response->dynamic($platform, Response::MODEL_PLATFORM); }, ['response', 'consoleDB']); App::delete('/v1/projects/:projectId/platforms/:platformId') @@ -1271,7 +1291,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('platformId', null, function () { return new UID(); }, 'Platform unique ID.') ->action(function ($projectId, $platformId, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -1304,7 +1324,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, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -1353,10 +1373,8 @@ App::post('/v1/projects/:projectId/domains') throw new Exception('Failed saving project to DB', 500); } - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($domain->getArrayCopy()) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic($domain, Response::MODEL_DOMAIN); }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/domains') @@ -1367,7 +1385,7 @@ App::get('/v1/projects/:projectId/domains') ->label('sdk.method', 'listDomains') ->param('projectId', '', function () { return new UID(); }, 'Project unique ID.') ->action(function ($projectId, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -1377,8 +1395,11 @@ App::get('/v1/projects/:projectId/domains') } $domains = $project->getAttribute('domains', []); - - $response->json($domains); + + $response->dynamic(new Document([ + 'sum' => count($domains), + 'domains' => $domains + ]), Response::MODEL_DOMAIN_LIST); }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/domains/:domainId') @@ -1390,7 +1411,7 @@ App::get('/v1/projects/:projectId/domains/:domainId') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('domainId', null, function () { return new UID(); }, 'Domain unique ID.') ->action(function ($projectId, $domainId, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -1405,7 +1426,7 @@ App::get('/v1/projects/:projectId/domains/:domainId') throw new Exception('Domain not found', 404); } - $response->json($domain->getArrayCopy()); + $response->dynamic($domain, Response::MODEL_DOMAIN); }, ['response', 'consoleDB']); App::patch('/v1/projects/:projectId/domains/:domainId/verification') @@ -1417,7 +1438,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, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); @@ -1439,7 +1460,7 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') } if ($domain->getAttribute('verification') === true) { - return $response->json($domain->getArrayCopy()); + return $response->dynamic($domain, Response::MODEL_DOMAIN); } // Verify Domain with DNS records @@ -1463,7 +1484,7 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') 'domain' => $domain->getAttribute('domain'), ]); - $response->json($domain->getArrayCopy()); + $response->dynamic($domain, Response::MODEL_DOMAIN); }, ['response', 'consoleDB']); App::delete('/v1/projects/:projectId/domains/:domainId') @@ -1475,7 +1496,7 @@ App::delete('/v1/projects/:projectId/domains/:domainId') ->param('projectId', null, function () { return new UID(); }, 'Project unique ID.') ->param('domainId', null, function () { return new UID(); }, 'Domain unique ID.') ->action(function ($projectId, $domainId, $response, $consoleDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $consoleDB */ $project = $consoleDB->getDocument($projectId); diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index 75851132ce..f519a74342 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -2,7 +2,6 @@ use Utopia\App; use Utopia\Exception; -use Utopia\Response; use Utopia\Validator\ArrayList; use Utopia\Validator\WhiteList; use Utopia\Validator\Range; @@ -12,6 +11,7 @@ use Utopia\Cache\Cache; use Utopia\Cache\Adapter\Filesystem; use Appwrite\ClamAV\Network; use Appwrite\Database\Database; +use Appwrite\Database\Document; use Appwrite\Database\Validator\UID; use Appwrite\Storage\Storage; use Appwrite\Storage\Validator\File; @@ -20,13 +20,14 @@ use Appwrite\Storage\Validator\Upload; use Appwrite\Storage\Compression\Algorithms\GZIP; use Appwrite\Resize\Resize; use Appwrite\OpenSSL\OpenSSL; +use Appwrite\Utopia\Response; use Utopia\Config\Config; App::post('/v1/storage/files') ->desc('Create File') ->groups(['api', 'storage']) ->label('scope', 'files.write') - ->label('webhook', 'storage.files.create') + ->label('event', 'storage.files.create') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'createFile') @@ -37,8 +38,8 @@ App::post('/v1/storage/files') ->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 ($file, $read, $write, $request, $response, $user, $projectDB, $webhooks, $audits, $usage) { - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $webhooks */ @@ -140,10 +141,6 @@ App::post('/v1/storage/files') throw new Exception('Failed saving file to DB', 500); } - $webhooks - ->setParam('payload', $file->getArrayCopy()) - ; - $audits ->setParam('event', 'storage.files.create') ->setParam('resource', 'storage/files/'.$file->getId()) @@ -153,10 +150,8 @@ App::post('/v1/storage/files') ->setParam('storage', $sizeActual) ; - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($file->getArrayCopy()) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic($file, Response::MODEL_FILE); }, ['request', 'response', 'user', 'projectDB', 'webhooks', 'audits', 'usage']); App::get('/v1/storage/files') @@ -172,7 +167,7 @@ App::get('/v1/storage/files') ->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'], true); }, 'Order result by ASC or DESC order.', true) ->action(function ($search, $limit, $offset, $orderType, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $results = $projectDB->getCollection([ @@ -187,11 +182,10 @@ App::get('/v1/storage/files') ], ]); - $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->dynamic(new Document([ + 'sum' => $projectDB->getSum(), + 'files' => $results + ]), Response::MODEL_FILE_LIST); }, ['response', 'projectDB']); App::get('/v1/storage/files/:fileId') @@ -204,7 +198,7 @@ App::get('/v1/storage/files/:fileId') ->label('sdk.description', '/docs/references/storage/get-file.md') ->param('fileId', '', function () { return new UID(); }, 'File unique ID.') ->action(function ($fileId, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $file = $projectDB->getDocument($fileId); @@ -213,7 +207,7 @@ App::get('/v1/storage/files/:fileId') throw new Exception('File not found', 404); } - $response->json($file->getArrayCopy(['$id', '$permissions', 'name', 'dateCreated', 'signature', 'mimeType', 'sizeOriginal'])); + $response->dynamic($file, Response::MODEL_FILE); }, ['response', 'projectDB']); App::get('/v1/storage/files/:fileId/preview') @@ -233,8 +227,8 @@ 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', '', function () { return new WhiteList(\array_keys(Config::getParam('storage-outputs')), true); }, 'Output format type (jpeg, jpg, png, gif and webp).', true) ->action(function ($fileId, $width, $height, $quality, $background, $output, $request, $response, $project, $projectDB) { - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ /** @var Appwrite\Database\Database $projectDB */ @@ -354,7 +348,7 @@ App::get('/v1/storage/files/:fileId/download') ->label('sdk.methodType', 'location') ->param('fileId', '', function () { return new UID(); }, 'File unique ID.') ->action(function ($fileId, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $file = $projectDB->getDocument($fileId); @@ -410,7 +404,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'], true); }, '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, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $file = $projectDB->getDocument($fileId); @@ -474,7 +468,7 @@ App::put('/v1/storage/files/:fileId') ->desc('Update File') ->groups(['api', 'storage']) ->label('scope', 'files.write') - ->label('webhook', 'storage.files.update') + ->label('event', 'storage.files.update') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'updateFile') @@ -483,7 +477,7 @@ App::put('/v1/storage/files/:fileId') ->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 ($fileId, $read, $write, $response, $projectDB, $webhooks, $audits) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $webhooks */ /** @var Appwrite\Event\Event $audits */ @@ -506,30 +500,26 @@ App::put('/v1/storage/files/:fileId') throw new Exception('Failed saving file to DB', 500); } - $webhooks - ->setParam('payload', $file->getArrayCopy()) - ; - $audits ->setParam('event', 'storage.files.update') ->setParam('resource', 'storage/files/'.$file->getId()) ; - $response->json($file->getArrayCopy()); + $response->dynamic($file, Response::MODEL_FILE); }, ['response', 'projectDB', 'webhooks', 'audits']); App::delete('/v1/storage/files/:fileId') ->desc('Delete File') ->groups(['api', 'storage']) ->label('scope', 'files.write') - ->label('webhook', 'storage.files.delete') + ->label('event', 'storage.files.delete') ->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'storage') ->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, $response, $projectDB, $webhooks, $audits, $usage) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $webhooks */ /** @var Appwrite\Event\Event $audits */ @@ -548,11 +538,7 @@ App::delete('/v1/storage/files/:fileId') throw new Exception('Failed to remove file from DB', 500); } } - - $webhooks - ->setParam('payload', $file->getArrayCopy()) - ; - + $audits ->setParam('event', 'storage.files.delete') ->setParam('resource', 'storage/files/'.$file->getId()) @@ -613,6 +599,5 @@ App::delete('/v1/storage/files/:fileId') // //var_dump($antiVirus->version()); // //var_dump($antiVirus->fileScan('/storage/uploads/app-1/5/9/f/e/59fecaed49645.pdf')); -// //$response->json($antiVirus->continueScan($device->getRoot())); // } // ); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 34701bb132..cf2912bc54 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -18,6 +18,7 @@ use Appwrite\Database\Exception\Duplicate; use Appwrite\Database\Validator\Key; use Appwrite\Template\Template; use Appwrite\Utopia\Response; +use DeviceDetector\DeviceDetector; App::post('/v1/teams') ->desc('Create Team') @@ -30,7 +31,7 @@ App::post('/v1/teams') ->param('name', null, function () { return new Text(128); }, 'Team name. Max length: 128 chars.') ->param('roles', ['owner'], function () { return new ArrayList(new Key()); }, '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). Max length for each role is 32 chars.', true) ->action(function ($name, $roles, $response, $user, $projectDB, $mode) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $projectDB */ /** @var bool $mode */ @@ -80,10 +81,8 @@ App::post('/v1/teams') } } - $response - ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($team->getArrayCopy()) - ; + $response->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic($team, Response::MODEL_TEAM); }, ['response', 'user', 'projectDB', 'mode']); App::get('/v1/teams') @@ -99,7 +98,7 @@ App::get('/v1/teams') ->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'], true); }, 'Order result by ASC or DESC order.', true) ->action(function ($search, $limit, $offset, $orderType, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $results = $projectDB->getCollection([ @@ -114,7 +113,10 @@ App::get('/v1/teams') ], ]); - $response->json(['sum' => $projectDB->getSum(), 'teams' => $results]); + $response->dynamic(new Document([ + 'sum' => $projectDB->getSum(), + 'teams' => $results + ]), Response::MODEL_TEAM_LIST); }, ['response', 'projectDB']); App::get('/v1/teams/:teamId') @@ -127,7 +129,7 @@ App::get('/v1/teams/:teamId') ->label('sdk.description', '/docs/references/teams/get-team.md') ->param('teamId', '', function () { return new UID(); }, 'Team unique ID.') ->action(function ($teamId, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $team = $projectDB->getDocument($teamId); @@ -136,7 +138,7 @@ App::get('/v1/teams/:teamId') throw new Exception('Team not found', 404); } - $response->json($team->getArrayCopy([])); + $response->dynamic($team, Response::MODEL_TEAM); }, ['response', 'projectDB']); App::put('/v1/teams/:teamId') @@ -150,7 +152,7 @@ App::put('/v1/teams/:teamId') ->param('teamId', '', function () { return new UID(); }, 'Team unique ID.') ->param('name', null, function () { return new Text(128); }, 'Team name. Max length: 128 chars.') ->action(function ($teamId, $name, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $team = $projectDB->getDocument($teamId); @@ -166,8 +168,8 @@ App::put('/v1/teams/:teamId') if (false === $team) { throw new Exception('Failed saving team to DB', 500); } - - $response->json($team->getArrayCopy()); + + $response->dynamic($team, Response::MODEL_TEAM); }, ['response', 'projectDB']); App::delete('/v1/teams/:teamId') @@ -180,7 +182,7 @@ App::delete('/v1/teams/:teamId') ->label('sdk.description', '/docs/references/teams/delete-team.md') ->param('teamId', '', function () { return new UID(); }, 'Team unique ID.') ->action(function ($teamId, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $team = $projectDB->getDocument($teamId); @@ -225,7 +227,7 @@ App::post('/v1/teams/:teamId/memberships') ->param('roles', [], function () { return new ArrayList(new Key()); }, '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). Max length for each role is 32 chars.') ->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, $audits, $mails, $mode) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $projectDB */ @@ -391,21 +393,12 @@ App::post('/v1/teams/:teamId/memberships') ->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->setStatusCode(Response::STATUS_CODE_CREATED); + + $response->dynamic(new Document(\array_merge($membership->getArrayCopy(), [ + 'email' => $email, + 'name' => $name, + ])), Response::MODEL_MEMBERSHIP); }, ['response', 'project', 'user', 'projectDB', 'locale', 'audits', 'mails', 'mode']); App::get('/v1/teams/:teamId/memberships') @@ -422,7 +415,7 @@ App::get('/v1/teams/:teamId/memberships') ->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'], true); }, 'Order result by ASC or DESC order.', true) ->action(function ($teamId, $search, $limit, $offset, $orderType, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $team = $projectDB->getDocument($teamId); @@ -443,7 +436,6 @@ App::get('/v1/teams/:teamId/memberships') 'teamId='.$teamId, ], ]); - $users = []; foreach ($memberships as $membership) { @@ -453,18 +445,10 @@ App::get('/v1/teams/:teamId/memberships') $temp = $projectDB->getDocument($membership->getAttribute('userId', null))->getArrayCopy(['email', 'name']); - $users[] = \array_merge($temp, $membership->getArrayCopy([ - '$id', - 'userId', - 'teamId', - 'roles', - 'invited', - 'joined', - 'confirm', - ])); + $users[] = new Document(\array_merge($temp, $membership->getArrayCopy())); } - $response->json(['sum' => $projectDB->getSum(), 'memberships' => $users]); + $response->dynamic(new Document(['sum' => $projectDB->getSum(), 'memberships' => $users]), Response::MODEL_MEMBERSHIP_LIST); }, ['response', 'projectDB']); App::patch('/v1/teams/:teamId/memberships/:inviteId/status') @@ -479,11 +463,12 @@ 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, $request, $response, $user, $projectDB, $audits) { - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + ->action(function ($teamId, $inviteId, $userId, $secret, $request, $response, $user, $projectDB, $geodb, $audits) { + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $user */ /** @var Appwrite\Database\Database $projectDB */ + /** @var GeoIp2\Database\Reader $geodb */ /** @var Appwrite\Event\Event $audits */ $protocol = $request->getProtocol(); @@ -540,10 +525,28 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') ; // Log user in + + $dd = new DeviceDetector($request->getUserAgent('UNKNOWN')); + + $dd->parse(); + + $os = $dd->getOs(); + $osCode = (isset($os['short_name'])) ? $os['short_name'] : ''; + $osName = (isset($os['name'])) ? $os['name'] : ''; + $osVersion = (isset($os['version'])) ? $os['version'] : ''; + + $client = $dd->getClient(); + $clientType = (isset($client['type'])) ? $client['type'] : ''; + $clientCode = (isset($client['short_name'])) ? $client['short_name'] : ''; + $clientName = (isset($client['name'])) ? $client['name'] : ''; + $clientVersion = (isset($client['version'])) ? $client['version'] : ''; + $clientEngine = (isset($client['engine'])) ? $client['engine'] : ''; + $clientEngineVersion = (isset($client['engine_version'])) ? $client['engine_version'] : ''; + $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; $secret = Auth::tokenGenerator(); - $user->setAttribute('tokens', new Document([ + $session = new Document([ '$collection' => Database::SYSTEM_COLLECTION_TOKENS, '$permissions' => ['read' => ['user:'.$user->getId()], 'write' => ['user:'.$user->getId()]], 'type' => Auth::TOKEN_TYPE_LOGIN, @@ -551,7 +554,33 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') 'expire' => $expiry, 'userAgent' => $request->getUserAgent('UNKNOWN'), 'ip' => $request->getIP(), - ]), Document::SET_TYPE_APPEND); + + 'osCode' => $osCode, + 'osName' => $osName, + 'osVersion' => $osVersion, + 'clientType' => $clientType, + 'clientCode' => $clientCode, + 'clientName' => $clientName, + 'clientVersion' => $clientVersion, + 'clientEngine' => $clientEngine, + 'clientEngineVersion' => $clientEngineVersion, + 'deviceName' => $dd->getDeviceName(), + 'deviceBrand' => $dd->getBrandName(), + 'deviceModel' => $dd->getModel(), + ]); + + try { + $record = $geodb->country($request->getIP()); + $session + ->setAttribute('countryCode', \strtolower($record->country->isoCode)) + ; + } catch (\Exception $e) { + $session + ->setAttribute('countryCode', '--') + ; + } + + $user->setAttribute('tokens', $session, Document::SET_TYPE_APPEND); Authorization::setRole('user:'.$userId); @@ -594,8 +623,7 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') 'email' => $user->getAttribute('email'), 'name' => $user->getAttribute('name'), ])), Response::MODEL_MEMBERSHIP); - - }, ['request', 'response', 'user', 'projectDB', 'audits']); + }, ['request', 'response', 'user', 'projectDB', 'geodb', 'audits']); App::delete('/v1/teams/:teamId/memberships/:inviteId') ->desc('Delete Team Membership') @@ -608,7 +636,7 @@ App::delete('/v1/teams/:teamId/memberships/:inviteId') ->param('teamId', '', function () { return new UID(); }, 'Team unique ID.') ->param('inviteId', '', function () { return new UID(); }, 'Invite unique ID.') ->action(function ($teamId, $inviteId, $response, $projectDB, $audits) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $audits */ diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 4176dd4621..27fcec97dd 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -9,10 +9,10 @@ use Utopia\Validator\Text; use Utopia\Validator\Range; use Utopia\Audit\Audit; use Utopia\Audit\Adapters\MySQL as AuditAdapter; -use Utopia\Config\Config; use Appwrite\Auth\Auth; use Appwrite\Auth\Validator\Password; use Appwrite\Database\Database; +use Appwrite\Database\Document; use Appwrite\Database\Exception\Duplicate; use Appwrite\Database\Validator\UID; use Appwrite\Utopia\Response; @@ -30,7 +30,7 @@ App::post('/v1/users') ->param('password', '', function () { return new Password(); }, 'User password. Must be between 6 to 32 chars.') ->param('name', '', function () { return new Text(128); }, 'User name. Max length: 128 chars.', true) ->action(function ($email, $password, $name, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $profile = $projectDB->getCollectionFirst([ // Get user by email address @@ -65,27 +65,8 @@ App::post('/v1/users') 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->setStatusCode(Response::STATUS_CODE_CREATED); + $response->dynamic($user, Response::MODEL_USER); }, ['response', 'projectDB']); App::get('/v1/users') @@ -101,7 +82,7 @@ App::get('/v1/users') ->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'], true); }, 'Order result by ASC or DESC order.', true) ->action(function ($search, $limit, $offset, $orderType, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $results = $projectDB->getCollection([ @@ -116,32 +97,10 @@ App::get('/v1/users') ], ]); - $oauth2Keys = []; - - foreach (Config::getParam('providers') as $key => $provider) { - if (!$provider['enabled']) { - continue; - } - - $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->dynamic(new Document([ + 'sum' => $projectDB->getSum(), + 'users' => $results + ]), Response::MODEL_USER_LIST); }, ['response', 'projectDB']); App::get('/v1/users/:userId') @@ -154,7 +113,7 @@ App::get('/v1/users/:userId') ->label('sdk.description', '/docs/references/users/get-user.md') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->action(function ($userId, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $user = $projectDB->getDocument($userId); @@ -163,28 +122,7 @@ App::get('/v1/users/:userId') 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->dynamic($user, Response::MODEL_USER); }, ['response', 'projectDB']); App::get('/v1/users/:userId/prefs') @@ -197,7 +135,7 @@ App::get('/v1/users/:userId/prefs') ->label('sdk.description', '/docs/references/users/get-user-prefs.md') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->action(function ($userId, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $user = $projectDB->getDocument($userId); @@ -208,13 +146,6 @@ App::get('/v1/users/:userId/prefs') $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']); @@ -227,11 +158,10 @@ 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, $response, $projectDB, $locale, $geodb) { - /** @var Utopia\Response $response */ + ->action(function ($userId, $response, $projectDB, $locale) { + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ /** @var Utopia\Locale\Locale $locale */ - /** @var GeoIp2\Database\Reader $geodb */ $user = $projectDB->getDocument($userId); @@ -241,7 +171,6 @@ App::get('/v1/users/:userId/sessions') $tokens = $user->getAttribute('tokens', []); $sessions = []; - $index = 0; $countries = $locale->getText('countries'); foreach ($tokens as $token) { /* @var $token Document */ @@ -249,40 +178,19 @@ App::get('/v1/users/:userId/sessions') continue; } - $userAgent = (!empty($token->getAttribute('userAgent'))) ? $token->getAttribute('userAgent') : 'UNKNOWN'; + $token->setAttribute('countryName', (isset($countries[$token->getAttribute('contryCode')])) + ? $countries[$token->getAttribute('contryCode')] + : $locale->getText('locale.country.unknown')); + $token->setAttribute('current', false); - $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 = $geodb->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; + $sessions[] = $token; } - $response->json($sessions); - }, ['response', 'projectDB', 'locale', 'geodb']); + $response->dynamic(new Document([ + 'sum' => count($sessions), + 'sessions' => $sessions + ]), Response::MODEL_SESSION_LIST); + }, ['response', 'projectDB', 'locale']); App::get('/v1/users/:userId/logs') ->desc('Get User Logs') @@ -294,7 +202,7 @@ App::get('/v1/users/:userId/logs') ->label('sdk.description', '/docs/references/users/get-user-logs.md') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->action(function ($userId, $response, $register, $project, $projectDB, $locale, $geodb) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\Registry\Registry $register */ /** @var Appwrite\Database\Document $project */ /** @var Appwrite\Database\Database $projectDB */ @@ -343,30 +251,49 @@ App::get('/v1/users/:userId/logs') $dd->parse(); - $output[$i] = [ + $os = $dd->getOs(); + $osCode = (isset($os['short_name'])) ? $os['short_name'] : ''; + $osName = (isset($os['name'])) ? $os['name'] : ''; + $osVersion = (isset($os['version'])) ? $os['version'] : ''; + + $client = $dd->getClient(); + $clientType = (isset($client['type'])) ? $client['type'] : ''; + $clientCode = (isset($client['short_name'])) ? $client['short_name'] : ''; + $clientName = (isset($client['name'])) ? $client['name'] : ''; + $clientVersion = (isset($client['version'])) ? $client['version'] : ''; + $clientEngine = (isset($client['engine'])) ? $client['engine'] : ''; + $clientEngineVersion = (isset($client['engine_version'])) ? $client['engine_version'] : ''; + + $output[$i] = new Document([ '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' => [], - ]; + + 'osCode' => $osCode, + 'osName' => $osName, + 'osVersion' => $osVersion, + 'clientType' => $clientType, + 'clientCode' => $clientCode, + 'clientName' => $clientName, + 'clientVersion' => $clientVersion, + 'clientEngine' => $clientEngine, + 'clientEngineVersion' => $clientEngineVersion, + 'deviceName' => $dd->getDeviceName(), + 'deviceBrand' => $dd->getBrandName(), + 'deviceModel' => $dd->getModel(), + ]); try { $record = $geodb->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'); + $output[$i]->setAttribute('countryCode', \strtolower($record->country->isoCode)); + $output[$i]->setAttribute('countryName', (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]->setAttribute('countryCode', '--'); + $output[$i]->setAttribute('countryName', $locale->getText('locale.country.unknown')); } } - $response->json($output); + $response->dynamic(new Document(['logs' => $output]), Response::MODEL_LOG_LIST); }, ['response', 'register', 'project', 'projectDB', 'locale', 'geodb']); App::patch('/v1/users/:userId/status') @@ -380,7 +307,7 @@ App::patch('/v1/users/:userId/status') ->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], true); }, '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, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $user = $projectDB->getDocument($userId); @@ -396,27 +323,8 @@ App::patch('/v1/users/:userId/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->dynamic($user, Response::MODEL_USER); }, ['response', 'projectDB']); App::patch('/v1/users/:userId/prefs') @@ -430,7 +338,7 @@ App::patch('/v1/users/:userId/prefs') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->param('prefs', '', function () { return new Assoc();}, 'Prefs key-value JSON object.') ->action(function ($userId, $prefs, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $user = $projectDB->getDocument($userId); @@ -439,26 +347,14 @@ App::patch('/v1/users/:userId/prefs') 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)), + 'prefs' => $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']); @@ -474,7 +370,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId') ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->param('sessionId', null, function () { return new UID(); }, 'User unique session ID.') ->action(function ($userId, $sessionId, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $user = $projectDB->getDocument($userId); @@ -493,7 +389,7 @@ App::delete('/v1/users/:userId/sessions/:sessionId') } } - $response->json(array('result' => 'success')); + $response->noContent(); }, ['response', 'projectDB']); App::delete('/v1/users/:userId/sessions') @@ -507,7 +403,7 @@ App::delete('/v1/users/:userId/sessions') ->label('abuse-limit', 100) ->param('userId', '', function () { return new UID(); }, 'User unique ID.') ->action(function ($userId, $response, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ $user = $projectDB->getDocument($userId); @@ -524,7 +420,7 @@ App::delete('/v1/users/:userId/sessions') } } - $response->json(array('result' => 'success')); + $response->noContent(); }, ['response', 'projectDB']); App::delete('/v1/users/:userId') @@ -567,7 +463,9 @@ App::delete('/v1/users/:userId') throw new Exception('Failed saving reserved id to DB', 500); } - $deletes->setParam('document', $user); + $deletes + ->setParam('document', $user) + ; $response->noContent(); }, ['response', 'projectDB', 'deletes']); diff --git a/app/controllers/general.php b/app/controllers/general.php index 751e6cdb04..9db6c0f2f1 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -3,7 +3,7 @@ require_once __DIR__.'/../init.php'; use Utopia\App; -use Appwrite\Swoole\Request; +use Utopia\Swoole\Request; use Appwrite\Utopia\Response; use Utopia\View; use Utopia\Exception; @@ -21,8 +21,8 @@ Config::setParam('domainVerification', false); Config::setParam('cookieDomain', 'localhost'); Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE); -App::init(function ($utopia, $request, $response, $console, $project, $user, $locale, $webhooks, $audits, $usage, $clients) { - /** @var Appwrite\Swoole\Request $request */ +App::init(function ($utopia, $request, $response, $console, $project, $user, $locale, $webhooks, $audits, $usage, $deletes, $functions, $clients) { + /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $console */ /** @var Appwrite\Database\Document $project */ @@ -31,6 +31,8 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo /** @var Appwrite\Event\Event $webhooks */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $usage */ + /** @var Appwrite\Event\Event $deletes */ + /** @var Appwrite\Event\Event $functions */ /** @var bool $mode */ /** @var array $clients */ @@ -183,7 +185,9 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo Authorization::setDefaultStatus(false); // Cancel security segmentation for API keys. } - Authorization::setRole('user:'.$user->getId()); + if($user->getId()) { + Authorization::setRole('user:'.$user->getId()); + } Authorization::setRole('role:'.$role); \array_map(function ($node) { @@ -217,9 +221,18 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo /* * Background Jobs */ + $functions + ->setParam('projectId', $project->getId()) + ->setParam('event', $route->getLabel('event', '')) + ->setParam('payload', []) + ->setParam('functionId', null) + ->setParam('executionId', null) + ->setParam('trigger', 'event') + ; + $webhooks ->setParam('projectId', $project->getId()) - ->setParam('event', $route->getLabel('webhook', '')) + ->setParam('event', $route->getLabel('event', '')) ->setParam('payload', []) ; @@ -242,20 +255,37 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo ->setParam('networkResponseSize', 0) ->setParam('storage', 0) ; -}, ['utopia', 'request', 'response', 'console', 'project', 'user', 'locale', 'webhooks', 'audits', 'usage', 'clients']); + + $deletes + ->setParam('projectId', $project->getId()) + ; +}, ['utopia', 'request', 'response', 'console', 'project', 'user', 'locale', 'webhooks', 'audits', 'usage', 'deletes', 'functions', 'clients']); -App::shutdown(function ($utopia, $request, $response, $project, $webhooks, $audits, $usage, $deletes, $mode) { +App::shutdown(function ($utopia, $request, $response, $project, $webhooks, $audits, $usage, $deletes, $functions, $mode) { /** @var Utopia\App $utopia */ - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ /** @var Appwrite\Event\Event $webhooks */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $usage */ /** @var Appwrite\Event\Event $deletes */ + /** @var Appwrite\Event\Event $functions */ /** @var bool $mode */ + if (!empty($functions->getParam('event'))) { + if(empty($functions->getParam('payload'))) { + $functions->setParam('payload', $response->getPayload()); + } + + $functions->trigger(); + } + if (!empty($webhooks->getParam('event'))) { + if(empty($webhooks->getParam('payload'))) { + $webhooks->setParam('payload', $response->getPayload()); + } + $webhooks->trigger(); } @@ -279,10 +309,10 @@ App::shutdown(function ($utopia, $request, $response, $project, $webhooks, $audi ->trigger() ; } -}, ['utopia', 'request', 'response', 'project', 'webhooks', 'audits', 'usage', 'deletes', 'mode']); +}, ['utopia', 'request', 'response', 'project', 'webhooks', 'audits', 'usage', 'deletes', 'functions', 'mode']); App::options(function ($request, $response) { - /** @var Appwrite\Swoole\Request $request */ + /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ $origin = $request->getOrigin(); @@ -300,12 +330,17 @@ App::options(function ($request, $response) { App::error(function ($error, $utopia, $request, $response, $layout, $project) { /** @var Exception $error */ /** @var Utopia\App $utopia */ - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\View $layout */ /** @var Appwrite\Database\Document $project */ + $route = $utopia->match($request); + $template = ($route) ? $route->getLabel('error', null) : null; + if(php_sapi_name() === 'cli') { + var_dump($route->getMethod()); + var_dump($route->getURL()); var_dump(get_class($error)); var_dump($error->getMessage()); var_dump($error->getFile()); @@ -353,9 +388,6 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project) { ->setStatusCode($code) ; - $route = $utopia->match($request); - $template = ($route) ? $route->getLabel('error', null) : null; - if ($template) { $comp = new View($template); @@ -387,7 +419,7 @@ App::get('/manifest.json') ->label('scope', 'public') ->label('docs', false) ->action(function ($response) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ $response->json([ 'name' => APP_NAME, @@ -413,7 +445,7 @@ App::get('/robots.txt') ->label('scope', 'public') ->label('docs', false) ->action(function ($response) { - $template = new View(__DIR__.'/views/general/robots.phtml'); + $template = new View(__DIR__.'/../views/general/robots.phtml'); $response->text($template->render(false)); }, ['response']); @@ -422,7 +454,7 @@ App::get('/humans.txt') ->label('scope', 'public') ->label('docs', false) ->action(function ($response) { - $template = new View(__DIR__.'/views/general/humans.phtml'); + $template = new View(__DIR__.'/../views/general/humans.phtml'); $response->text($template->render(false)); }, ['response']); diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 3fdca7cf17..d64c46a2cd 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -7,8 +7,8 @@ use Utopia\Abuse\Adapters\TimeLimit; App::init(function ($utopia, $request, $response, $project, $user, $register) { /** @var Utopia\App $utopia */ - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ /** @var Appwrite\Database\Document $user */ /** @var Utopia\Registry\Registry $register */ diff --git a/app/controllers/shared/web.php b/app/controllers/shared/web.php index 3452cd3535..42d2a18c55 100644 --- a/app/controllers/shared/web.php +++ b/app/controllers/shared/web.php @@ -5,8 +5,8 @@ use Utopia\Config\Config; App::init(function ($utopia, $request, $response, $layout) { /** @var Utopia\App $utopia */ - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\View $layout */ /* AJAX check */ diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php index 2a68c69592..a3ef0185c7 100644 --- a/app/controllers/web/console.php +++ b/app/controllers/web/console.php @@ -19,7 +19,7 @@ App::init(function ($layout) { }, ['layout'], 'console'); App::shutdown(function ($response, $layout) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\View $layout */ $header = new View(__DIR__.'/../../views/console/comps/header.phtml'); @@ -213,7 +213,7 @@ App::get('/console/database/collection') ->label('scope', 'console') ->param('id', '', function () { return new UID(); }, 'Collection unique ID.') ->action(function ($id, $response, $layout, $projectDB) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\View $layout */ /** @var Appwrite\Database\Database $projectDB */ diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index 24202bc9d5..dd8429b578 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -28,7 +28,7 @@ App::init(function ($layout) { }, ['layout'], 'home'); App::shutdown(function ($response, $layout) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\View $layout */ $response->html($layout->render()); @@ -39,7 +39,7 @@ App::get('/') ->label('permission', 'public') ->label('scope', 'home') ->action(function ($response) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ $response->redirect('/auth/signin'); }, ['response']); @@ -189,8 +189,8 @@ App::get('/open-api-2.json') ->param('tests', 0, function () {return new Range(0, 1);}, 'Include only test services.', true) ->action(function ($platform, $extensions, $tests, $utopia, $request, $response) { /** @var Utopia\App $utopia */ - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ $security = [ APP_PLATFORM_CLIENT => ['Project' => []], diff --git a/app/http.php b/app/http.php index 9540375430..805ea0cb8a 100644 --- a/app/http.php +++ b/app/http.php @@ -2,9 +2,9 @@ require_once __DIR__.'/../vendor/autoload.php'; -use Appwrite\Swoole\Files; -use Appwrite\Swoole\Request; -use Appwrite\Swoole\Response; +use Utopia\Swoole\Files; +use Utopia\Swoole\Request; +use Appwrite\Utopia\Response; use Swoole\Process; use Swoole\Http\Server; use Swoole\Http\Request as SwooleRequest; @@ -23,7 +23,7 @@ sleep(2); $http = new Server("0.0.0.0", 80); -$payloadSize = max(4000000 /* 4mb */, App::getEnv('_APP_STORAGE_LIMIT', 100000000)); +$payloadSize = max(4000000 /* 4mb */, App::getEnv('_APP_STORAGE_LIMIT', 10000000)); $http ->set([ @@ -50,7 +50,7 @@ $http->on('AfterReload', function($serv, $workerId) { }); $http->on('start', function (Server $http) use ($payloadSize) { - Console::success('Server started succefully (max payload is '.$payloadSize.' bytes)'); + Console::success('Server started succefully (max payload is '.number_format($payloadSize).' bytes)'); Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}"); diff --git a/app/init.php b/app/init.php index 4d56ac3f58..9b4f1edc90 100644 --- a/app/init.php +++ b/app/init.php @@ -36,7 +36,7 @@ const APP_EMAIL_SECURITY = 'security@localhost.test'; // Default security email const APP_USERAGENT = APP_NAME.'-Server v%s. Please report abuse at %s'; const APP_MODE_ADMIN = 'admin'; const APP_PAGING_LIMIT = 12; -const APP_CACHE_BUSTER = 127; +const APP_CACHE_BUSTER = 138; const APP_VERSION_STABLE = '0.7.0'; const APP_STORAGE_UPLOADS = '/storage/uploads'; const APP_STORAGE_FUNCTIONS = '/storage/functions'; @@ -199,6 +199,9 @@ $register->set('smtp', function () { return $mail; }); +$register->set('geodb', function () { + return new Reader(__DIR__.'/db/DBIP/dbip-country-lite-2020-01.mmdb'); +}); $register->set('queue-webhooks', function () { return new Event('v1-webhooks', 'WebhooksV1'); }); @@ -350,8 +353,8 @@ App::setResource('clients', function($console, $project) { }, ['console', 'project']); App::setResource('user', function($mode, $project, $console, $request, $response, $projectDB, $consoleDB) { - /** @var Utopia\Request $request */ - /** @var Utopia\Response $response */ + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $project */ /** @var Appwrite\Database\Database $consoleDB */ /** @var Appwrite\Database\Database $projectDB */ @@ -412,7 +415,7 @@ App::setResource('user', function($mode, $project, $console, $request, $response }, ['mode', 'project', 'console', 'request', 'response', 'projectDB', 'consoleDB']); App::setResource('project', function($consoleDB, $request) { - /** @var Appwrite\Swoole\Request $request */ + /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Database\Database $consoleDB */ Authorization::disable(); @@ -433,7 +436,6 @@ 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', [])); return $consoleDB; @@ -449,11 +451,10 @@ App::setResource('projectDB', function($register, $project) { }, ['register', 'project']); App::setResource('mode', function($request) { - /** @var Utopia\Request $request */ + /** @var Utopia\Swoole\Request $request */ return $request->getParam('mode', $request->getHeader('x-appwrite-mode', 'default')); }, ['request']); -App::setResource('geodb', function($request) { - /** @var Utopia\Request $request */ - return new Reader(__DIR__.'/db/DBIP/dbip-country-lite-2020-01.mmdb'); -}, ['request']); +App::setResource('geodb', function($register) { + return $register->get('geodb'); +}, ['register']); diff --git a/app/preload.php b/app/preload.php index 63d08c3e13..89eafa8ee6 100644 --- a/app/preload.php +++ b/app/preload.php @@ -16,11 +16,12 @@ if (file_exists(__DIR__.'/../vendor/autoload.php')) { require __DIR__.'/../vendor/autoload.php'; } -use Appwrite\Preloader\Preloader; +use Utopia\Preloader\Preloader; include __DIR__.'/controllers/general.php'; -(new Preloader()) +$preloader = new Preloader(); +$preloader ->paths(realpath(__DIR__ . '/../app/config')) ->paths(realpath(__DIR__ . '/../app/controllers')) ->paths(realpath(__DIR__ . '/../src')) @@ -32,4 +33,6 @@ include __DIR__.'/controllers/general.php'; ->ignore(realpath(__DIR__ . '/../vendor/psr/log')) ->ignore(realpath(__DIR__ . '/../vendor/piwik')) ->ignore(realpath(__DIR__ . '/../vendor/symfony')) - ->load(); \ No newline at end of file + ->load(); + +echo 'Loaded '.$preloader->getCount()." file\n"; \ No newline at end of file diff --git a/app/sdks/console-web/docs/examples/functions/get-usage.md b/app/sdks/console-web/docs/examples/functions/get-usage.md new file mode 100644 index 0000000000..7515c0fe5c --- /dev/null +++ b/app/sdks/console-web/docs/examples/functions/get-usage.md @@ -0,0 +1,15 @@ +let sdk = new Appwrite(); + +sdk + .setEndpoint('https://[HOSTNAME_OR_IP]/v1') // Your API Endpoint + .setProject('5df5acd0d48c2') // Your project ID + .setKey('919c2d18fb5d4...a2ae413da83346ad2') // Your secret API key +; + +let promise = sdk.functions.getUsage('[FUNCTION_ID]'); + +promise.then(function (response) { + console.log(response); // Success +}, function (error) { + console.log(error); // Failure +}); \ No newline at end of file diff --git a/app/sdks/console-web/src/sdk.js b/app/sdks/console-web/src/sdk.js index b246db4cc5..4c6ac61ee9 100644 --- a/app/sdks/console-web/src/sdk.js +++ b/app/sdks/console-web/src/sdk.js @@ -2238,6 +2238,34 @@ .delete(path, { 'content-type': 'application/json', }, payload); + }, + + /** + * Get Function Usage + * + * + * @param {string} functionId + * @param {string} range + * @throws {Error} + * @return {Promise} + */ + getUsage: function(functionId, range = 'last30') { + if(functionId === undefined) { + throw new Error('Missing required parameter: "functionId"'); + } + + let path = '/functions/{functionId}/usage'.replace(new RegExp('{functionId}', 'g'), functionId); + + let payload = {}; + + if(range) { + payload['range'] = range; + } + + return http + .get(path, { + 'content-type': 'application/json', + }, payload); } }; diff --git a/app/sdks/console-web/src/sdk.min.js b/app/sdks/console-web/src/sdk.min.js index 6501248b84..d2b1d8e84a 100644 --- a/app/sdks/console-web/src/sdk.min.js +++ b/app/sdks/console-web/src/sdk.min.js @@ -191,7 +191,9 @@ return http.post(path,{'content-type':'multipart/form-data',},payload)},getTag:f if(tagId===undefined){throw new Error('Missing required parameter: "tagId"')} let path='/functions/{functionId}/tags/{tagId}'.replace(new RegExp('{functionId}','g'),functionId).replace(new RegExp('{tagId}','g'),tagId);let payload={};return http.get(path,{'content-type':'application/json',},payload)},deleteTag:function(functionId,tagId){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"')} if(tagId===undefined){throw new Error('Missing required parameter: "tagId"')} -let path='/functions/{functionId}/tags/{tagId}'.replace(new RegExp('{functionId}','g'),functionId).replace(new RegExp('{tagId}','g'),tagId);let payload={};return http.delete(path,{'content-type':'application/json',},payload)}};let health={get:function(){let path='/health';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getAntiVirus:function(){let path='/health/anti-virus';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getCache:function(){let path='/health/cache';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getDB:function(){let path='/health/db';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getQueueCertificates:function(){let path='/health/queue/certificates';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getQueueFunctions:function(){let path='/health/queue/functions';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getQueueLogs:function(){let path='/health/queue/logs';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getQueueTasks:function(){let path='/health/queue/tasks';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getQueueUsage:function(){let path='/health/queue/usage';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getQueueWebhooks:function(){let path='/health/queue/webhooks';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getStorageLocal:function(){let path='/health/storage/local';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getTime:function(){let path='/health/time';let payload={};return http.get(path,{'content-type':'application/json',},payload)}};let locale={get:function(){let path='/locale';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getContinents:function(){let path='/locale/continents';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getCountries:function(){let path='/locale/countries';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getCountriesEU:function(){let path='/locale/countries/eu';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getCountriesPhones:function(){let path='/locale/countries/phones';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getCurrencies:function(){let path='/locale/currencies';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getLanguages:function(){let path='/locale/languages';let payload={};return http.get(path,{'content-type':'application/json',},payload)}};let projects={list:function(search='',limit=25,offset=0,orderType='ASC'){let path='/projects';let payload={};if(search){payload.search=search} +let path='/functions/{functionId}/tags/{tagId}'.replace(new RegExp('{functionId}','g'),functionId).replace(new RegExp('{tagId}','g'),tagId);let payload={};return http.delete(path,{'content-type':'application/json',},payload)},getUsage:function(functionId,range='last30'){if(functionId===undefined){throw new Error('Missing required parameter: "functionId"')} +let path='/functions/{functionId}/usage'.replace(new RegExp('{functionId}','g'),functionId);let payload={};if(range){payload.range=range} +return http.get(path,{'content-type':'application/json',},payload)}};let health={get:function(){let path='/health';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getAntiVirus:function(){let path='/health/anti-virus';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getCache:function(){let path='/health/cache';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getDB:function(){let path='/health/db';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getQueueCertificates:function(){let path='/health/queue/certificates';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getQueueFunctions:function(){let path='/health/queue/functions';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getQueueLogs:function(){let path='/health/queue/logs';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getQueueTasks:function(){let path='/health/queue/tasks';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getQueueUsage:function(){let path='/health/queue/usage';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getQueueWebhooks:function(){let path='/health/queue/webhooks';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getStorageLocal:function(){let path='/health/storage/local';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getTime:function(){let path='/health/time';let payload={};return http.get(path,{'content-type':'application/json',},payload)}};let locale={get:function(){let path='/locale';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getContinents:function(){let path='/locale/continents';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getCountries:function(){let path='/locale/countries';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getCountriesEU:function(){let path='/locale/countries/eu';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getCountriesPhones:function(){let path='/locale/countries/phones';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getCurrencies:function(){let path='/locale/currencies';let payload={};return http.get(path,{'content-type':'application/json',},payload)},getLanguages:function(){let path='/locale/languages';let payload={};return http.get(path,{'content-type':'application/json',},payload)}};let projects={list:function(search='',limit=25,offset=0,orderType='ASC'){let path='/projects';let payload={};if(search){payload.search=search} if(limit){payload.limit=limit} if(offset){payload.offset=offset} if(orderType){payload.orderType=orderType} diff --git a/app/sdks/console-web/types/index.d.ts b/app/sdks/console-web/types/index.d.ts index b34f32c5bc..dc57de2e16 100644 --- a/app/sdks/console-web/types/index.d.ts +++ b/app/sdks/console-web/types/index.d.ts @@ -767,6 +767,17 @@ declare namespace Appwrite { */ deleteTag(functionId: string, tagId: string): Promise; + /** + * Get Function Usage + * + * + * @param {string} functionId + * @param {string} range + * @throws {Error} + * @return {Promise} + */ + getUsage(functionId: string, range: string): Promise; + } export interface Health { diff --git a/app/tasks/doctor.php b/app/tasks/doctor.php index 0836628bde..c28b4d2297 100644 --- a/app/tasks/doctor.php +++ b/app/tasks/doctor.php @@ -213,7 +213,6 @@ $cli Console::error('🔴 ' . $message); } } - try { Console::log(''); @@ -233,4 +232,4 @@ $cli } catch (\Throwable $th) { Console::error('Failed to check for a newer version'."\n"); } - }); \ No newline at end of file + }); diff --git a/app/tasks/migrate.php b/app/tasks/migrate.php index efa06e1fed..5434856fb8 100644 --- a/app/tasks/migrate.php +++ b/app/tasks/migrate.php @@ -7,6 +7,8 @@ use Utopia\CLI\Console; 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; $db = $register->get('db'); @@ -14,9 +16,9 @@ $callbacks = [ '0.4.0' => function() { Console::log('I got nothing to do.'); }, - '0.5.0' => function($project) use ($db, $projectDB, $requset) { + '0.5.0' => function(Document $project, $projectDB) use ($db) { - Console::log('Migrating project: '.$project->getId()); + Console::log('Migrating project: '.$project->getAttribute('name').' ('.$project->getId().')'); // Update all documents $uid -> $id @@ -47,6 +49,7 @@ $callbacks = [ try { $new = $projectDB->overwriteDocument($document->getArrayCopy()); } catch (\Throwable $th) { + var_dump($document); Console::error('Failed to update document: '.$th->getMessage()); continue; } @@ -108,6 +111,14 @@ function fixDocument(Document $document) { } } + if($document->getAttribute('$collection') === Database::SYSTEM_COLLECTION_WEBHOOKS){ + $document->setAttribute('security', ($document->getAttribute('security')) ? true : false); + } + + if($document->getAttribute('$collection') === Database::SYSTEM_COLLECTION_TASKS){ + $document->setAttribute('security', ($document->getAttribute('security')) ? true : false); + } + if($document->getAttribute('$collection') === Database::SYSTEM_COLLECTION_USERS) { foreach($providers as $key => $provider) { if(!empty($document->getAttribute('oauth'.\ucfirst($key)))) { @@ -166,26 +177,38 @@ function fixDocument(Document $document) { $cli ->task('migrate') - ->action(function () use ($console, $projectDB, $consoleDB, $callbacks) { + ->action(function () use ($register, $callbacks) { Console::success('Starting Data Migration'); + $consoleDB = new Database(); + $consoleDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)); + $consoleDB->setNamespace('app_console'); // Main DB + $consoleDB->setMocks(Config::getParam('collections', [])); + + $projectDB = new Database(); + $projectDB->setAdapter(new RedisAdapter(new MySQLAdapter($register), $register)); + $projectDB->setMocks(Config::getParam('collections', [])); + + $console = $consoleDB->getDocument('console'); + Authorization::disable(); $limit = 30; $sum = 30; $offset = 0; $projects = [$console]; + $count = 0; while ($sum >= 30) { foreach($projects as $project) { + $projectDB->setNamespace('app_'.$project->getId()); try { - $callbacks['0.5.0']($project); + $callbacks['0.5.0']($project, $projectDB); } catch (\Throwable $th) { + throw $th; Console::error('Failed to update project ("'.$project->getId().'") version with error: '.$th->getMessage()); - $projectDB->setNamespace('app_console'); - $projectDB->deleteDocument($project->getId()); } } @@ -202,8 +225,9 @@ $cli $sum = \count($projects); $offset = $offset + $limit; + $count = $count + $sum; - Console::log('Fetched '.$sum.' projects...'); + Console::log('Fetched '.$count.'/'.$consoleDB->getSum().' projects...'); } Console::success('Data Migration Completed'); diff --git a/app/tasks/vars.php b/app/tasks/vars.php new file mode 100644 index 0000000000..ed4a329735 --- /dev/null +++ b/app/tasks/vars.php @@ -0,0 +1,18 @@ +task('vars') + ->desc('List all the server environment variables') + ->action(function () { + $variables = Config::getParam('variables', []); + + foreach ($variables as $key => $value) { + Console::log('- '.$value['name'].'='.App::getEnv($value['name'], '')); + } + }); \ No newline at end of file diff --git a/app/views/console/account/index.phtml b/app/views/console/account/index.phtml index e14f73b749..d88f689a97 100644 --- a/app/views/console/account/index.phtml +++ b/app/views/console/account/index.phtml @@ -192,7 +192,7 @@ data-name="sessions" data-event="load,account.deleteRemoteSession"> - diff --git a/app/views/console/functions/function.phtml b/app/views/console/functions/function.phtml index 548a4c8a74..3d3e701d39 100644 --- a/app/views/console/functions/function.phtml +++ b/app/views/console/functions/function.phtml @@ -3,12 +3,11 @@ $fileLimit = $this->getParam('fileLimit', 0); $fileLimitHuman = $this->getParam('fileLimitHuman', 0); $events = array_keys($this->getParam('events', [])); $timeout = $this->getParam('timeout', 900); - ?>
@@ -64,7 +63,7 @@ $timeout = $this->getParam('timeout', 900); data-failure="alert" data-failure-param-alert-text="Failed to execute function" data-failure-param-alert-classname="error"> -   View Logs +   View Logs
@@ -113,7 +112,7 @@ $timeout = $this->getParam('timeout', 900);  
- +
getParam('timeout', 900);
-
  • -

    Usage

    +
  • + + + + -
    -
    -
    -
    + + +
    + +
    + + + +
    + +
    + + + +

    Monitors

    + +
    +
    +
    +
    + +
    + +
      +
    • Executions
    • +
    + +
    +
    +
    + +
    +
    +
    + +
      +
    • CPU Time
    • +
    + +
    +
    +
    + +
    +
    +
    + +
      +
    • Errors
    • +
    +
  • +
  • -
      -
    • Invocations
    • -
    • CPU Time
    • -
    +
    -

    Logs  

    +

    Logs

    getParam('timeout', 900); + Created Status - Date - Runtime + Trigger + Runtime @@ -291,35 +357,42 @@ $timeout = $this->getParam('timeout', 900); + + + - - + + - - +
    - - + +
  • diff --git a/app/views/console/home/index.phtml b/app/views/console/home/index.phtml index 50f4a30b07..6cea29611e 100644 --- a/app/views/console/home/index.phtml +++ b/app/views/console/home/index.phtml @@ -13,69 +13,105 @@ $graph = $this->getParam('graph', false);     -