diff --git a/.env b/.env index 30b4a83f7..9764cfe89 100644 --- a/.env +++ b/.env @@ -24,6 +24,6 @@ _APP_SMTP_PORT=25 _APP_SMTP_SECURE= _APP_SMTP_USERNAME= _APP_SMTP_PASSWORD= -_APP_STORAGE_LIMIT=100000000 +_APP_STORAGE_LIMIT=10000000 _APP_FUNCTIONS_TIMEOUT=900 _APP_FUNCTIONS_CONTAINERS=10 diff --git a/.travis.yml b/.travis.yml index a04ad6665..c1242c2fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,4 +39,4 @@ script: - docker-compose logs appwrite - docker exec appwrite doctor - docker exec appwrite vars -- docker exec appwrite test \ No newline at end of file +- docker exec appwrite test diff --git a/CHANGES.md b/CHANGES.md index 6263aa0d9..9aadc06e4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -44,6 +44,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/CONTRIBUTING.md b/CONTRIBUTING.md index a51f482f2..81d4e87bc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -117,10 +117,8 @@ Appwrite's current structure is a combination of both [Monolithic](https://en.wi │ ├── Extend │ ├── Network │ ├── OpenSSL -│ ├── Preloader │ ├── Resize │ ├── Storage -│ ├── Swoole │ ├── Task │ ├── Template │ ├── URL diff --git a/Dockerfile b/Dockerfile index 60db67596..1503f2a36 100755 --- a/Dockerfile +++ b/Dockerfile @@ -75,7 +75,7 @@ ENV _APP_SERVER=swoole \ _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 \ @@ -158,6 +158,7 @@ RUN chmod +x /usr/local/bin/doctor && \ chmod +x /usr/local/bin/install && \ chmod +x /usr/local/bin/migrate && \ chmod +x /usr/local/bin/schedule && \ + chmod +x /usr/local/bin/sdks && \ chmod +x /usr/local/bin/ssl && \ chmod +x /usr/local/bin/test && \ chmod +x /usr/local/bin/vars && \ @@ -180,7 +181,7 @@ RUN echo extension=maxminddb.so >> /usr/local/etc/php/conf.d/maxminddb.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 3bd88a7d3..b7acd23a7 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/README.md b/README.md index d7938a3d7..75ca031af 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![Hacktoberfest](https://badgen.net/badge/hacktoberfest/friendly/pink)](CONTRIBUTING.md) [![Discord](https://img.shields.io/discord/564160730845151244?label=discord)](https://appwrite.io/discord) -[![Docker Pulls](https://badgen.net/docker/pulls/appwrite/appwrite)](https://travis-ci.org/appwrite/appwrite) +[![Docker Pulls](https://badgen.net/docker/pulls/appwrite/appwrite)](https://hub.docker.com/r/appwrite/appwrite) [![Travis CI](https://badgen.net/travis/appwrite/appwrite?label=build)](https://travis-ci.com/appwrite/appwrite) [![Twitter Account](https://badgen.net/twitter/follow/appwrite_io?label=twitter)](https://twitter.com/appwrite_io) [![Follow Appwrite on StackShare](https://badgen.net/badge/follow%20on/stackshare/blue)](https://stackshare.io/appwrite) diff --git a/app/config/collections.php b/app/config/collections.php index 249f75f01..23d95af71 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 => [ @@ -1273,8 +1391,8 @@ $collections = [ ], [ '$collection' => Database::SYSTEM_COLLECTION_RULES, - 'label' => 'Previous', - 'key' => 'previous', + 'label' => 'Schedule Previous Run', + 'key' => 'schedulePrevious', 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, 'default' => '', 'required' => false, @@ -1282,8 +1400,8 @@ $collections = [ ], [ '$collection' => Database::SYSTEM_COLLECTION_RULES, - 'label' => 'Next', - 'key' => 'next', + 'label' => 'Schedule Next Run', + 'key' => 'scheduleNext', 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, 'default' => '', 'required' => false, @@ -1337,7 +1455,7 @@ $collections = [ [ '$collection' => Database::SYSTEM_COLLECTION_RULES, 'label' => 'Code Path', - 'key' => 'codePath', + 'key' => 'path', 'type' => Database::SYSTEM_VAR_TYPE_TEXT, 'default' => '', 'required' => false, @@ -1346,7 +1464,7 @@ $collections = [ [ '$collection' => Database::SYSTEM_COLLECTION_RULES, 'label' => 'Code Size', - 'key' => 'codeSize', + 'key' => 'size', 'type' => Database::SYSTEM_VAR_TYPE_NUMERIC, 'default' => '', 'required' => false, @@ -1388,6 +1506,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/config/events.php b/app/config/events.php index aeee6849f..34c4a4f2d 100644 --- a/app/config/events.php +++ b/app/config/events.php @@ -55,4 +55,16 @@ return [ 'storage.files.delete' => [ 'description' => 'This event triggers when a storage file is deleted.', ], + 'users.create' => [ + 'description' => 'This event triggers when a user is created from the users API.', + ], + 'users.update.status' => [ + 'description' => 'This event triggers when a user status is updated from the users API.', + ], + 'users.delete' => [ + 'description' => 'This event triggers when a user is deleted from users API.', + ], + 'users.sessions.delete' => [ + 'description' => 'This event triggers when a user session is deleted from users API.', + ], ]; \ No newline at end of file diff --git a/app/config/variables.php b/app/config/variables.php index 66308fcef..3da5a21e2 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -135,7 +135,7 @@ return [ ], [ 'name' => '_APP_STORAGE_LIMIT', - 'default' => '100000000', + 'default' => '10000000', 'required' => false, 'question' => '', ], diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 8083e56a3..821db145b 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -29,23 +29,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') @@ -55,12 +42,11 @@ App::post('/v1/account') ->param('email', '', new Email(), 'User email.') ->param('password', '', new Password(), 'User password. Must be between 6 to 32 chars.') ->param('name', '', 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, $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 */ /** @var Appwrite\Event\Event $audits */ if ('console' === $project->getId()) { @@ -121,36 +107,26 @@ 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()) ; + $user + ->setAttribute('roles', Authorization::getRoles()) + ; + $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json(\array_merge($user->getArrayCopy(\array_merge( - [ - '$id', - 'email', - 'registration', - 'name', - ], - $oauth2Keys - )), ['roles' => Authorization::getRoles()])); - }, ['request', 'response', 'project', 'projectDB', 'webhooks', 'audits']); + ->dynamic($user, Response::MODEL_USER) + ; + }, ['request', 'response', 'project', 'projectDB', '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') @@ -160,11 +136,12 @@ App::post('/v1/account/sessions') ->label('abuse-key', 'url:{url},email:{param-email}') ->param('email', '', new Email(), 'User email.') ->param('password', '', 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, $audits) { + /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ - /** @var Appwrite\Event\Event $webhooks */ + /** @var Utopia\Locale\Locale $locale */ + /** @var MaxMind\Db\Reader $geodb */ /** @var Appwrite\Event\Event $audits */ $protocol = $request->getProtocol(); @@ -186,6 +163,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([ @@ -196,8 +190,33 @@ 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(), ]); + $record = $geodb->get($request->getIP()); + + if($record) { + $session + ->setAttribute('countryCode', \strtolower($record['country']['iso_code'])) + ; + } else { + $session + ->setAttribute('countryCode', '--') + ; + } + Authorization::setRole('user:'.$profile->getId()); $session = $projectDB->createDocument($session->getArrayCopy()); @@ -213,14 +232,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') @@ -238,10 +250,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', 'audits']); App::get('/v1/account/sessions/oauth2/:provider') ->desc('Create Account Session with OAuth2') @@ -262,8 +278,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', [], 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(); @@ -307,8 +323,8 @@ App::get('/v1/account/sessions/oauth2/callback/:provider/:projectId') ->param('code', '', new Text(1024), 'OAuth2 code.') ->param('state', '', 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(); @@ -332,8 +348,8 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId') ->param('code', '', new Text(1024), 'OAuth2 code.') ->param('state', '', 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(); @@ -349,7 +365,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}') @@ -357,12 +373,13 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ->param('provider', '', new WhiteList(\array_keys(Config::getParam('providers')), true), 'OAuth2 provider.') ->param('code', '', new Text(1024), 'OAuth2 code.') ->param('state', '', 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 MaxMind\Db\Reader $geodb */ /** @var Appwrite\Event\Event $audits */ $protocol = $request->getProtocol(); @@ -483,6 +500,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([ @@ -493,8 +528,33 @@ 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(), ]); + $record = $geodb->get($request->getIP()); + + if($record) { + $session + ->setAttribute('countryCode', \strtolower($record['country']['iso_code'])) + ; + } else { + $session + ->setAttribute('countryCode', '--') + ; + } + $user ->setAttribute('oauth2'.\ucfirst($provider), $oauth2ID) ->setAttribute('oauth2'.\ucfirst($provider).'AccessToken', $accessToken) @@ -524,7 +584,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(); @@ -542,7 +602,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/jwt') ->desc('Create Account JWT') @@ -582,20 +642,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') @@ -607,17 +662,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']); @@ -630,65 +678,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 MaxMind\Db\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->get($token->getAttribute('ip', '')); - - if ($record) { - $sessions[$index]['geo']['isoCode'] = \strtolower($record['country']['iso_code']); - $sessions[$index]['geo']['country'] = (isset($countries[$record['country']['iso_code']])) ? $countries[$record['country']['iso_code']] : $locale->getText('locale.country.unknown'); - } else { - $sessions[$index]['geo']['isoCode'] = '--'; - $sessions[$index]['geo']['country'] = $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') @@ -699,7 +716,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 */ @@ -740,50 +757,65 @@ 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' => [], - ]; - try { - $record = $geodb->get($log['ip']); + '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(), + ]); - if ($record) { - $output[$i]['geo']['isoCode'] = \strtolower($record['country']['iso_code']); - $output[$i]['geo']['country'] = (isset($countries[$record['country']['iso_code']])) ? $countries[$record['country']['iso_code']] : $locale->getText('locale.country.unknown'); - } else { - $output[$i]['geo']['isoCode'] = '--'; - $output[$i]['geo']['country'] = $locale->getText('locale.country.unknown'); - } + $record = $geodb->get($log['ip']); - } catch (\Exception $e) { - $output[$i]['geo']['isoCode'] = '--'; - $output[$i]['geo']['country'] = $locale->getText('locale.country.unknown'); + if ($record) { + $output[$i]['countryCode'] = (isset($countries[$record['country']['iso_code']])) ? \strtolower($record['country']['iso_code']) : '--'; + $output[$i]['countryName'] = (isset($countries[$record['country']['iso_code']])) ? $countries[$record['country']['iso_code']] : $locale->getText('locale.country.unknown'); + } else { + $output[$i]['countryCode'] = '--'; + $output[$i]['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', '', 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 */ @@ -796,27 +828,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') @@ -824,8 +850,8 @@ App::patch('/v1/account/password') ->label('sdk.description', '/docs/references/account/update-password.md') ->param('password', '', new Password(), 'New user password. Must be between 6 to 32 chars.') ->param('oldPassword', '', 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 */ @@ -842,27 +868,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') @@ -870,8 +890,8 @@ App::patch('/v1/account/email') ->label('sdk.description', '/docs/references/account/update-email.md') ->param('email', '', new Email(), 'User email.') ->param('password', '', 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 */ @@ -903,44 +923,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', '', new Assoc(), 'Prefs key-value JSON object.') ->label('sdk.description', '/docs/references/account/update-prefs.md') + ->param('prefs', [], new Assoc(), 'Prefs key-value JSON object.') ->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) { @@ -952,14 +963,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']); @@ -967,15 +971,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 */ @@ -1006,10 +1010,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')) { @@ -1029,7 +1030,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') @@ -1037,8 +1038,8 @@ App::delete('/v1/account/sessions/:sessionId') ->label('abuse-limit', 100) ->param('sessionId', null, 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 */ @@ -1064,10 +1065,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')) { @@ -1094,15 +1092,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 */ @@ -1121,12 +1119,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')) { @@ -1159,8 +1154,8 @@ App::post('/v1/account/recovery') ->param('email', '', 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 */ @@ -1267,7 +1262,7 @@ App::put('/v1/account/recovery') ->param('password', '', new Password(), 'New password. Must be between 6 to 32 chars.') ->param('passwordAgain', '', 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 */ @@ -1336,8 +1331,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 */ @@ -1432,7 +1427,7 @@ App::put('/v1/account/verification') ->param('userId', '', new UID(), 'User unique ID.') ->param('secret', '', 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 5a3e9e48d..5b75073f2 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -17,7 +17,7 @@ use chillerlan\QRCode\QRCode; use chillerlan\QRCode\QROptions; $avatarCallback = function ($type, $code, $width, $height, $quality, $response) { - /** @var Utopia\Response $response */ + /** @var Appwrite\Utopia\Response $response */ $code = \strtolower($code); $type = \strtolower($type); @@ -143,7 +143,7 @@ App::get('/v1/avatars/image') ->param('width', 400, new Range(0, 2000), 'Resize preview image width, Pass an integer between 0 to 2000.', true) ->param('height', 400, 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'; @@ -207,7 +207,7 @@ App::get('/v1/avatars/favicon') ->label('sdk.description', '/docs/references/avatars/get-favicon.md') ->param('url', '', 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; @@ -360,7 +360,7 @@ App::get('/v1/avatars/qr') ->param('margin', 1, new Range(0, 10), 'Margin from edge. Pass an integer between 0 to 10. Defaults to 1.', true) ->param('download', false, 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); $options = new QROptions([ @@ -396,7 +396,7 @@ App::get('/v1/avatars/initials') ->param('color', '', new HexColor(), 'Changes text color. By default a random color will be picked and stay will persistent to the given name.', true) ->param('background', '', 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 bd1993575..f86200ab8 100644 --- a/app/controllers/api/database.php +++ b/app/controllers/api/database.php @@ -2,15 +2,11 @@ use Utopia\App; use Utopia\Exception; -use Utopia\Response; use Utopia\Validator\Range; use Utopia\Validator\WhiteList; use Utopia\Validator\Text; use Utopia\Validator\ArrayList; use Utopia\Validator\JSON; -// use Utopia\Locale\Locale; -// use Utopia\Audit\Audit; -// use Utopia\Audit\Adapters\MySQL as AuditAdapter; use Appwrite\Database\Database; use Appwrite\Database\Document; use Appwrite\Database\Validator\UID; @@ -20,11 +16,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]) @@ -34,10 +31,9 @@ App::post('/v1/database/collections') ->param('read', [], 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', [], 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 */ + ->action(function ($name, $read, $write, $rules, $response, $projectDB, $audits) { + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ - /** @var Appwrite\Event\Event $webhooks */ /** @var Appwrite\Event\Event $audits */ $parsedRules = []; @@ -77,26 +73,17 @@ 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) + ->dynamic($data, Response::MODEL_COLLECTION) ; - }, ['response', 'projectDB', 'webhooks', 'audits']); + }, ['response', 'projectDB', 'audits']); App::get('/v1/database/collections') ->desc('List Collections') @@ -111,7 +98,7 @@ App::get('/v1/database/collections') ->param('offset', 0, new Range(0, 40000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', 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 +113,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 +129,7 @@ App::get('/v1/database/collections/:collectionId') ->label('sdk.description', '/docs/references/database/get-collection.md') ->param('collectionId', '', 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 +138,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', '', 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') @@ -230,10 +155,9 @@ App::put('/v1/database/collections/:collectionId') ->param('read', [], 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', [], 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 */ + ->action(function ($collectionId, $name, $read, $write, $rules, $response, $projectDB, $audits) { + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ - /** @var Appwrite\Event\Event $webhooks */ /** @var Appwrite\Event\Event $audits */ $collection = $projectDB->getDocument($collectionId, false); @@ -277,33 +201,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', 'projectDB', 'webhooks', 'audits']); + $response->dynamic($collection, Response::MODEL_COLLECTION); + }, ['response', 'projectDB', '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', '', 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 +236,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 +252,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]) @@ -349,10 +265,9 @@ App::post('/v1/database/collections/:collectionId/documents') ->param('parentDocument', '', new UID(), 'Parent document unique ID. Use when you want your new document to be a child of a parent document.', true) ->param('parentProperty', '', 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, 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 */ + ->action(function ($collectionId, $data, $read, $write, $parentDocument, $parentProperty, $parentPropertyType, $response, $projectDB, $audits) { + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ - /** @var Appwrite\Event\Event $webhooks */ /** @var Appwrite\Event\Event $audits */ $data = (\is_string($data)) ? \json_decode($data, true) : $data; // Cast to JSON array @@ -387,11 +302,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,26 +351,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) + ->dynamic($data, Response::MODEL_ANY) ; - }, ['response', 'projectDB', 'webhooks', 'audits']); + }, ['response', 'projectDB', 'audits']); App::get('/v1/database/collections/:collectionId/documents') ->desc('List Documents') @@ -474,7 +380,7 @@ App::get('/v1/database/collections/:collectionId/documents') ->param('orderCast', 'string', 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', '', 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); @@ -495,27 +401,24 @@ App::get('/v1/database/collections/:collectionId/documents') ]), ]); - if (App::isDevelopment()) { - $collection - ->setAttribute('debug', $projectDB->getDebug()) - ->setAttribute('limit', $limit) - ->setAttribute('offset', $offset) - ->setAttribute('orderField', $orderField) - ->setAttribute('orderType', $orderType) - ->setAttribute('orderCast', $orderCast) - ->setAttribute('filters', $filters) - ; - } + // if (App::isDevelopment()) { + // $collection + // ->setAttribute('debug', $projectDB->getDebug()) + // ->setAttribute('limit', $limit) + // ->setAttribute('offset', $offset) + // ->setAttribute('orderField', $orderField) + // ->setAttribute('orderType', $orderType) + // ->setAttribute('orderCast', $orderCast) + // ->setAttribute('filters', $filters) + // ; + // } $collection ->setAttribute('sum', $projectDB->getSum()) ->setAttribute('documents', $list) ; - /* - * View - */ - $response->json($collection->getArrayCopy(/*['$id', '$collection', 'name', 'documents']*/[], ['rules'])); + $response->dynamic($collection, Response::MODEL_DOCUMENT_LIST); }, ['response', 'projectDB']); App::get('/v1/database/collections/:collectionId/documents/:documentId') @@ -529,8 +432,8 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId') ->param('collectionId', null, 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, 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 +443,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]) @@ -580,10 +460,9 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId') ->param('data', [], new JSON(), 'Document data as JSON object.') ->param('read', [], 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', [], 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 */ + ->action(function ($collectionId, $documentId, $data, $read, $write, $response, $projectDB, $audits) { + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ - /** @var Appwrite\Event\Event $webhooks */ /** @var Appwrite\Event\Event $audits */ $collection = $projectDB->getDocument($collectionId, false); @@ -592,7 +471,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 +500,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 +511,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', 'projectDB', 'webhooks', 'audits']); + $response->dynamic($data, Response::MODEL_ANY); + }, ['response', 'projectDB', '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 +532,7 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId') ->param('collectionId', null, 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, 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 +558,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 039eeb3f5..071cebfce 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' => '', 'vars' => $vars, 'events' => $events, 'schedule' => $schedule, - 'previous' => null, - 'next' => null, + 'schedulePrevious' => null, + 'scheduleNext' => null, 'timeout' => $timeout, ]); @@ -61,7 +62,7 @@ App::post('/v1/functions') $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($function->getArrayCopy()) + ->dynamic($function, Response::MODEL_FUNCTION) ; }, ['response', 'projectDB']); @@ -90,7 +91,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 +110,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', '', new UID(), 'Function unique ID.') + ->param('range', '30d', 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') @@ -142,16 +259,29 @@ App::put('/v1/functions/:functionId') 'vars' => $vars, 'events' => $events, 'schedule' => $schedule, - 'previous' => null, - 'next' => $next, + 'schedulePrevious' => null, + 'scheduleNext' => $next, 'timeout' => $timeout, ])); + if ($next) { + ResqueScheduler::enqueueAt($next, 'v1-functions', 'FunctionsV1', [ + + ]); + + // ->setParam('projectId', $project->getId()) + // ->setParam('event', $route->getLabel('event', '')) + // ->setParam('payload', []) + // ->setParam('functionId', null) + // ->setParam('executionId', null) + // ->setParam('trigger', 'event') + } + if (false === $function) { 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') @@ -182,14 +312,14 @@ App::patch('/v1/functions/:functionId/tag') $function = $projectDB->updateDocument(array_merge($function->getArrayCopy(), [ 'tag' => $tag->getId(), - 'next' => $next, + 'scheduleNext' => $next, ])); if (false === $function) { 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 +331,11 @@ App::delete('/v1/functions/:functionId') ->label('sdk.method', 'delete') ->label('sdk.description', '/docs/references/functions/delete-function.md') ->param('functionId', '', new UID(), 'Function unique ID.') - ->action(function ($functionId, $response, $projectDB) { + ->action(function ($functionId, $response, $projectDB, $deletes) { + /** @var Appwrite\Utopia\Response $response */ + /** @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 +346,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', 'projectDB', 'deletes']); App::post('/v1/functions/:functionId/tags') ->groups(['api', 'functions']) @@ -276,11 +414,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,12 +426,12 @@ 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()) + ->dynamic($tag, Response::MODEL_TAG) ; }, ['request', 'response', 'projectDB', 'usage']); @@ -330,7 +468,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') @@ -352,7 +493,7 @@ App::get('/v1/functions/:functionId/tags/:tagId') $tag = $projectDB->getDocument($tagId); - if($tag->getAttribute('functionId') !== $function->getId()) { + if ($tag->getAttribute('functionId') !== $function->getId()) { throw new Exception('Tag not found', 404); } @@ -360,7 +501,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') @@ -382,7 +523,7 @@ App::delete('/v1/functions/:functionId/tags/:tagId') $tag = $projectDB->getDocument($tagId); - if($tag->getAttribute('functionId') !== $function->getId()) { + if ($tag->getAttribute('functionId') !== $function->getId()) { throw new Exception('Tag not found', 404); } @@ -392,14 +533,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 +565,9 @@ App::post('/v1/functions/:functionId/executions') ->label('sdk.method', 'createExecution') ->label('sdk.description', '/docs/references/functions/create-execution.md') ->param('functionId', '', new UID(), 'Function unique ID.') - ->param('async', 1, 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, 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 */ @@ -428,7 +579,7 @@ App::post('/v1/functions/:functionId/executions') $tag = $projectDB->getDocument($function->getAttribute('tag')); - if($tag->getAttribute('functionId') !== $function->getId()) { + if ($tag->getAttribute('functionId') !== $function->getId()) { throw new Exception('Tag not found. Deploy tag before trying to execute a function', 404); } @@ -444,6 +595,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,21 +606,18 @@ 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()) + ->dynamic($execution, Response::MODEL_EXECUTION) ; }, ['response', 'project', 'projectDB']); @@ -505,7 +654,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') @@ -527,7 +679,7 @@ App::get('/v1/functions/:functionId/executions/:executionId') $execution = $projectDB->getDocument($executionId); - if($execution->getAttribute('functionId') !== $function->getId()) { + if ($execution->getAttribute('functionId') !== $function->getId()) { throw new Exception('Execution not found', 404); } @@ -535,5 +687,5 @@ App::get('/v1/functions/:functionId/executions/:executionId') throw new Exception('Execution not found', 404); } - $response->json($execution->getArrayCopy()); - }, ['response', 'projectDB']); \ No newline at end of file + $response->dynamic($execution, Response::MODEL_EXECUTION); + }, ['response', 'projectDB']); diff --git a/app/controllers/api/health.php b/app/controllers/api/health.php index 5ef4f2b9c..8b146167d 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 aa6fa9278..f65aeb88f 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 MaxMind\Db\Reader $geodb */ @@ -24,7 +26,7 @@ App::get('/v1/locale') $time = (60 * 60 * 24 * 45); // 45 days cache $countries = $locale->getText('countries'); $continents = $locale->getText('continents'); - + $output['ip'] = $ip; $currency = null; @@ -57,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') @@ -69,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') @@ -88,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') @@ -115,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') @@ -142,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') @@ -161,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']); @@ -178,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 3f62a66c6..fbcc56387 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; @@ -18,6 +17,7 @@ use Appwrite\Database\Document; use Appwrite\Database\Validator\UID; use Appwrite\Network\Validator\CNAME; use Appwrite\Network\Validator\Domain as DomainValidator; +use Appwrite\Utopia\Response; use Cron\CronExpression; App::post('/v1/projects') @@ -38,7 +38,7 @@ App::post('/v1/projects') ->param('legalAddress', '', new Text(256), 'Project legal Address. Max length: 256 chars.', true) ->param('legalTaxId', '', 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 */ @@ -70,6 +70,7 @@ App::post('/v1/projects') 'webhooks' => [], 'keys' => [], 'tasks' => [], + 'domains' => [], ] ); @@ -81,7 +82,7 @@ App::post('/v1/projects') $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($project->getArrayCopy()) + ->dynamic($project, Response::MODEL_PROJECT) ; }, ['response', 'consoleDB', 'projectDB']); @@ -96,7 +97,7 @@ App::get('/v1/projects') ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', 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([ @@ -111,7 +112,10 @@ App::get('/v1/projects') ], ]); - $response->json(['sum' => $consoleDB->getSum(), 'projects' => $results]); + $response->dynamic(new Document([ + 'sum' => $consoleDB->getSum(), + 'projects' => $results + ]), Response::MODEL_PROJECT_LIST); }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId') @@ -122,7 +126,7 @@ App::get('/v1/projects/:projectId') ->label('sdk.method', 'get') ->param('projectId', '', 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); @@ -131,7 +135,7 @@ App::get('/v1/projects/:projectId') throw new Exception('Project not found', 404); } - $response->json($project->getArrayCopy()); + $response->dynamic($project, Response::MODEL_PROJECT); }, ['response', 'consoleDB']); App::get('/v1/projects/:projectId/usage') @@ -141,9 +145,9 @@ App::get('/v1/projects/:projectId/usage') ->label('sdk.namespace', 'projects') ->label('sdk.method', 'getUsage') ->param('projectId', '', new UID(), 'Project unique ID.') - ->param('range', 'last30', new WhiteList(['daily', 'monthly', 'last30', 'last90'], true), 'Date range.', true) + ->param('range', '30d', new WhiteList(['24h', '7d', '30d', '90d'], true), '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 */ @@ -155,31 +159,26 @@ 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'); @@ -257,6 +256,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) { @@ -298,7 +298,7 @@ App::get('/v1/projects/:projectId/usage') ) + $projectDB->getCount( [ - 'attribute' => 'codeSize', + 'attribute' => 'size', 'filters' => [ '$collection='.Database::SYSTEM_COLLECTION_TAGS, ], @@ -326,7 +326,7 @@ App::patch('/v1/projects/:projectId') ->param('legalAddress', '', new Text(256), 'Project legal address. Max length: 256 chars.', true) ->param('legalTaxId', '', 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); @@ -352,7 +352,7 @@ App::patch('/v1/projects/:projectId') throw new Exception('Failed saving project to DB', 500); } - $response->json($project->getArrayCopy()); + $response->dynamic($project, Response::MODEL_PROJECT); }, ['response', 'consoleDB']); App::patch('/v1/projects/:projectId/oauth2') @@ -366,7 +366,7 @@ App::patch('/v1/projects/:projectId/oauth2') ->param('appId', '', new Text(256), 'Provider app ID. Max length: 256 chars.', true) ->param('secret', '', 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); @@ -384,7 +384,7 @@ App::patch('/v1/projects/:projectId/oauth2') throw new Exception('Failed saving project to DB', 500); } - $response->json($project->getArrayCopy()); + $response->dynamic($project, Response::MODEL_PROJECT); }, ['response', 'consoleDB']); App::delete('/v1/projects/:projectId') @@ -396,7 +396,7 @@ App::delete('/v1/projects/:projectId') ->param('projectId', '', new UID(), 'Project unique ID.') ->param('password', '', 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 */ @@ -450,7 +450,7 @@ App::post('/v1/projects/:projectId/webhooks') ->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true) ->param('httpPass', '', 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); @@ -489,7 +489,7 @@ App::post('/v1/projects/:projectId/webhooks') $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($webhook->getArrayCopy()) + ->dynamic($webhook, Response::MODEL_WEBHOOK) ; }, ['response', 'consoleDB']); @@ -501,7 +501,7 @@ App::get('/v1/projects/:projectId/webhooks') ->label('sdk.method', 'listWebhooks') ->param('projectId', '', 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); @@ -512,7 +512,10 @@ App::get('/v1/projects/:projectId/webhooks') $webhooks = $project->getAttribute('webhooks', []); - $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') @@ -524,7 +527,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('webhookId', null, 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); @@ -539,7 +542,7 @@ App::get('/v1/projects/:projectId/webhooks/:webhookId') throw new Exception('Webhook not found', 404); } - $response->json($webhook->getArrayCopy()); + $response->dynamic($webhook, Response::MODEL_WEBHOOK); }, ['response', 'consoleDB']); App::put('/v1/projects/:projectId/webhooks/:webhookId') @@ -557,7 +560,7 @@ App::put('/v1/projects/:projectId/webhooks/:webhookId') ->param('httpUser', '', new Text(256), 'Webhook HTTP user. Max length: 256 chars.', true) ->param('httpPass', '', 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); @@ -587,7 +590,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') @@ -599,7 +602,7 @@ App::delete('/v1/projects/:projectId/webhooks/:webhookId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('webhookId', null, 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); @@ -633,7 +636,7 @@ App::post('/v1/projects/:projectId/keys') ->param('name', null, new Text(128), 'Key name. Max length: 128 chars.') ->param('scopes', null, 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); @@ -667,7 +670,7 @@ App::post('/v1/projects/:projectId/keys') $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($key->getArrayCopy()) + ->dynamic($key, Response::MODEL_KEY) ; }, ['response', 'consoleDB']); @@ -679,7 +682,7 @@ App::get('/v1/projects/:projectId/keys') ->label('sdk.method', 'listKeys') ->param('projectId', null, 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); @@ -688,7 +691,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') @@ -712,7 +720,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') @@ -726,7 +734,7 @@ App::put('/v1/projects/:projectId/keys/:keyId') ->param('name', null, new Text(128), 'Key name. Max length: 128 chars.') ->param('scopes', null, 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); @@ -750,7 +758,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') @@ -762,7 +770,7 @@ App::delete('/v1/projects/:projectId/keys/:keyId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('keyId', null, 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); @@ -803,7 +811,7 @@ App::post('/v1/projects/:projectId/tasks') ->param('httpUser', '', new Text(256), 'Task HTTP user. Max length: 256 chars.', true) ->param('httpPass', '', 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); @@ -857,7 +865,7 @@ App::post('/v1/projects/:projectId/tasks') $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($task->getArrayCopy()) + ->dynamic($task, Response::MODEL_TASK) ; }, ['response', 'consoleDB']); @@ -869,7 +877,7 @@ App::get('/v1/projects/:projectId/tasks') ->label('sdk.method', 'listTasks') ->param('projectId', '', 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); @@ -880,7 +888,11 @@ App::get('/v1/projects/:projectId/tasks') $tasks = $project->getAttribute('tasks', []); - $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') @@ -892,7 +904,7 @@ App::get('/v1/projects/:projectId/tasks/:taskId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('taskId', null, 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); @@ -907,7 +919,7 @@ App::get('/v1/projects/:projectId/tasks/:taskId') throw new Exception('Task not found', 404); } - $response->json($task->getArrayCopy()); + $response->dynamic($task, Response::MODEL_TASK); }, ['response', 'consoleDB']); App::put('/v1/projects/:projectId/tasks/:taskId') @@ -928,7 +940,7 @@ App::put('/v1/projects/:projectId/tasks/:taskId') ->param('httpUser', '', new Text(256), 'Task HTTP user. Max length: 256 chars.', true) ->param('httpPass', '', 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); @@ -970,7 +982,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') @@ -982,7 +994,7 @@ App::delete('/v1/projects/:projectId/tasks/:taskId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('taskId', null, 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); @@ -1019,7 +1031,7 @@ App::post('/v1/projects/:projectId/platforms') ->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true) ->param('hostname', '', 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); @@ -1057,7 +1069,7 @@ App::post('/v1/projects/:projectId/platforms') $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($platform->getArrayCopy()) + ->dynamic($platform, Response::MODEL_PLATFORM) ; }, ['response', 'consoleDB']); @@ -1069,7 +1081,7 @@ App::get('/v1/projects/:projectId/platforms') ->label('sdk.method', 'listPlatforms') ->param('projectId', '', 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); @@ -1080,7 +1092,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') @@ -1092,7 +1107,7 @@ App::get('/v1/projects/:projectId/platforms/:platformId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('platformId', null, 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); @@ -1107,7 +1122,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') @@ -1123,7 +1138,7 @@ App::put('/v1/projects/:projectId/platforms/:platformId') ->param('store', '', new Text(256), 'App store or Google Play store ID. Max length: 256 chars.', true) ->param('hostname', '', 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); @@ -1150,7 +1165,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') @@ -1162,7 +1177,7 @@ App::delete('/v1/projects/:projectId/platforms/:platformId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('platformId', null, 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); @@ -1195,7 +1210,7 @@ App::post('/v1/projects/:projectId/domains') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('domain', null, 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); @@ -1246,7 +1261,7 @@ App::post('/v1/projects/:projectId/domains') $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($domain->getArrayCopy()) + ->dynamic($domain, Response::MODEL_DOMAIN) ; }, ['response', 'consoleDB']); @@ -1258,7 +1273,7 @@ App::get('/v1/projects/:projectId/domains') ->label('sdk.method', 'listDomains') ->param('projectId', '', 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); @@ -1268,8 +1283,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') @@ -1281,7 +1299,7 @@ App::get('/v1/projects/:projectId/domains/:domainId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('domainId', null, 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); @@ -1296,7 +1314,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') @@ -1308,7 +1326,7 @@ App::patch('/v1/projects/:projectId/domains/:domainId/verification') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('domainId', null, 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); @@ -1330,7 +1348,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 @@ -1354,7 +1372,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') @@ -1366,7 +1384,7 @@ App::delete('/v1/projects/:projectId/domains/:domainId') ->param('projectId', null, new UID(), 'Project unique ID.') ->param('domainId', null, 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 18cfb58f4..bd20fb569 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') @@ -36,12 +37,11 @@ App::post('/v1/storage/files') ->param('file', [], new File(), 'Binary file.', false) ->param('read', [], 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', [], 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 */ + ->action(function ($file, $read, $write, $request, $response, $user, $projectDB, $audits, $usage) { + /** @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 */ /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $usage */ @@ -140,10 +140,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()) @@ -155,9 +151,9 @@ App::post('/v1/storage/files') $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($file->getArrayCopy()) + ->dynamic($file, Response::MODEL_FILE) ; - }, ['request', 'response', 'user', 'projectDB', 'webhooks', 'audits', 'usage']); + }, ['request', 'response', 'user', 'projectDB', 'audits', 'usage']); App::get('/v1/storage/files') ->desc('List Files') @@ -172,7 +168,7 @@ App::get('/v1/storage/files') ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', 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 +183,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 +199,7 @@ App::get('/v1/storage/files/:fileId') ->label('sdk.description', '/docs/references/storage/get-file.md') ->param('fileId', '', 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 +208,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 +228,8 @@ App::get('/v1/storage/files/:fileId/preview') ->param('background', '', 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', '', 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 +349,7 @@ App::get('/v1/storage/files/:fileId/download') ->label('sdk.methodType', 'location') ->param('fileId', '', 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 +405,7 @@ App::get('/v1/storage/files/:fileId/view') ->param('fileId', '', new UID(), 'File unique ID.') ->param('as', '', 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 +469,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') @@ -482,10 +477,9 @@ App::put('/v1/storage/files/:fileId') ->param('fileId', '', new UID(), 'File unique ID.') ->param('read', [], 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', [], 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 */ + ->action(function ($fileId, $read, $write, $response, $projectDB, $audits) { + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ - /** @var Appwrite\Event\Event $webhooks */ /** @var Appwrite\Event\Event $audits */ $file = $projectDB->getDocument($fileId); @@ -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', '', 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()) @@ -562,6 +548,10 @@ App::delete('/v1/storage/files/:fileId') ->setParam('storage', $file->getAttribute('size', 0) * -1) ; + $webhooks + ->setParam('payload', $response->output($file, Response::MODEL_FILE)) + ; + $response->noContent(); }, ['response', 'projectDB', 'webhooks', 'audits', 'usage']); @@ -613,6 +603,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 ae9e44015..050eda1f5 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, new Text(128), 'Team name. Max length: 128 chars.') ->param('roles', ['owner'], 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 */ @@ -82,7 +83,7 @@ App::post('/v1/teams') $response ->setStatusCode(Response::STATUS_CODE_CREATED) - ->json($team->getArrayCopy()) + ->dynamic($team, Response::MODEL_TEAM) ; }, ['response', 'user', 'projectDB', 'mode']); @@ -99,7 +100,7 @@ App::get('/v1/teams') ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', 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 +115,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 +131,7 @@ App::get('/v1/teams/:teamId') ->label('sdk.description', '/docs/references/teams/get-team.md') ->param('teamId', '', 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 +140,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 +154,7 @@ App::put('/v1/teams/:teamId') ->param('teamId', '', new UID(), 'Team unique ID.') ->param('name', null, 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 +170,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 +184,7 @@ App::delete('/v1/teams/:teamId') ->label('sdk.description', '/docs/references/teams/delete-team.md') ->param('teamId', '', 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 +229,7 @@ App::post('/v1/teams/:teamId/memberships') ->param('roles', [], 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 */ @@ -392,19 +396,11 @@ App::post('/v1/teams/:teamId/memberships') ; $response - ->setStatusCode(Response::STATUS_CODE_CREATED) // TODO change response of this endpoint - ->json(\array_merge($membership->getArrayCopy([ - '$id', - 'userId', - 'teamId', - 'roles', - 'invited', - 'joined', - 'confirm', - ]), [ + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic(new Document(\array_merge($membership->getArrayCopy(), [ 'email' => $email, 'name' => $name, - ])) + ])), Response::MODEL_MEMBERSHIP) ; }, ['response', 'project', 'user', 'projectDB', 'locale', 'audits', 'mails', 'mode']); @@ -422,7 +418,7 @@ App::get('/v1/teams/:teamId/memberships') ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', 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); @@ -453,18 +449,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 +467,12 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') ->param('inviteId', '', new UID(), 'Invite unique ID.') ->param('userId', '', new UID(), 'User unique ID.') ->param('secret', '', 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 MaxMind\Db\Reader $geodb */ /** @var Appwrite\Event\Event $audits */ $protocol = $request->getProtocol(); @@ -540,10 +529,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 +558,34 @@ 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(), + ]); + + $record = $geodb->get($request->getIP()); + + if($record) { + $session + ->setAttribute('countryCode', \strtolower($record['country']['iso_code'])) + ; + } else { + $session + ->setAttribute('countryCode', '--') + ; + } + + $user->setAttribute('tokens', $session, Document::SET_TYPE_APPEND); Authorization::setRole('user:'.$userId); @@ -594,8 +628,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 +641,7 @@ App::delete('/v1/teams/:teamId/memberships/:inviteId') ->param('teamId', '', new UID(), 'Team unique ID.') ->param('inviteId', '', 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 741d0c661..f519b79c8 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; @@ -21,6 +21,7 @@ use DeviceDetector\DeviceDetector; App::post('/v1/users') ->desc('Create User') ->groups(['api', 'users']) + ->label('event', 'users.create') ->label('scope', 'users.write') ->label('sdk.platform', [APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'users') @@ -30,7 +31,7 @@ App::post('/v1/users') ->param('password', '', new Password(), 'User password. Must be between 6 to 32 chars.') ->param('name', '', 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 +66,10 @@ 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' => []])); + ->dynamic($user, Response::MODEL_USER) + ; }, ['response', 'projectDB']); App::get('/v1/users') @@ -101,7 +85,7 @@ App::get('/v1/users') ->param('offset', 0, new Range(0, 2000), 'Results offset. The default value is 0. Use this param to manage pagination.', true) ->param('orderType', 'ASC', 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 +100,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 +116,7 @@ App::get('/v1/users/:userId') ->label('sdk.description', '/docs/references/users/get-user.md') ->param('userId', '', 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 +125,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 +138,7 @@ App::get('/v1/users/:userId/prefs') ->label('sdk.description', '/docs/references/users/get-user-prefs.md') ->param('userId', '', 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 +149,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 +161,10 @@ App::get('/v1/users/:userId/sessions') ->label('sdk.method', 'getSessions') ->label('sdk.description', '/docs/references/users/get-user-sessions.md') ->param('userId', '', 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 MaxMind\Db\Reader $geodb */ $user = $projectDB->getDocument($userId); @@ -241,7 +174,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,47 +181,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->get($token->getAttribute('ip', '')); - - if ($record) { - $sessions[$index]['geo']['isoCode'] = \strtolower($record['country']['iso_code']); - $sessions[$index]['geo']['country'] = (isset($countries[$record['country']['iso_code']])) ? $countries[$record['country']['iso_code']] : $locale->getText('locale.country.unknown'); - } else { - $sessions[$index]['geo']['isoCode'] = '--'; - $sessions[$index]['geo']['country'] = $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') @@ -301,7 +205,7 @@ App::get('/v1/users/:userId/logs') ->label('sdk.description', '/docs/references/users/get-user-logs.md') ->param('userId', '', 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 */ @@ -350,41 +254,56 @@ 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' => [], - ]; - try { - $record = $geodb->get($log['ip']); + '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(), + ]); - if($record){ - $output[$i]['geo']['isoCode'] = \strtolower($record['country']['iso_code']); - $output[$i]['geo']['country'] = (isset($countries[$record['country']['iso_code']])) ? $countries[$record['country']['iso_code']] : $locale->getText('locale.country.unknown'); - } else{ - $output[$i]['geo']['isoCode'] = '--'; - $output[$i]['geo']['country'] = $locale->getText('locale.country.unknown'); - } + $record = $geodb->get($log['ip']); - } catch (\Exception $e) { - $output[$i]['geo']['isoCode'] = '--'; - $output[$i]['geo']['country'] = $locale->getText('locale.country.unknown'); + if ($record) { + $output[$i]['countryCode'] = (isset($countries[$record['country']['iso_code']])) ? \strtolower($record['country']['iso_code']) : '--'; + $output[$i]['countryName'] = (isset($countries[$record['country']['iso_code']])) ? $countries[$record['country']['iso_code']] : $locale->getText('locale.country.unknown'); + } else { + $output[$i]['countryCode'] = '--'; + $output[$i]['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') ->desc('Update User Status') ->groups(['api', 'users']) + ->label('event', 'users.update.status') ->label('scope', 'users.write') ->label('sdk.platform', [APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'users') @@ -393,7 +312,7 @@ App::patch('/v1/users/:userId/status') ->param('userId', '', new UID(), 'User unique ID.') ->param('status', '', 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); @@ -409,27 +328,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') @@ -443,7 +343,7 @@ App::patch('/v1/users/:userId/prefs') ->param('userId', '', new UID(), 'User unique ID.') ->param('prefs', '', 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); @@ -452,32 +352,21 @@ 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']); App::delete('/v1/users/:userId/sessions/:sessionId') ->desc('Delete User Session') ->groups(['api', 'users']) + ->label('event', 'users.sessions.delete') ->label('scope', 'users.write') ->label('sdk.platform', [APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'users') @@ -486,9 +375,10 @@ App::delete('/v1/users/:userId/sessions/:sessionId') ->label('abuse-limit', 100) ->param('userId', '', new UID(), 'User unique ID.') ->param('sessionId', null, new UID(), 'User unique session ID.') - ->action(function ($userId, $sessionId, $response, $projectDB) { - /** @var Utopia\Response $response */ + ->action(function ($userId, $sessionId, $response, $projectDB, $webhooks) { + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $webhooks */ $user = $projectDB->getDocument($userId); @@ -503,15 +393,20 @@ App::delete('/v1/users/:userId/sessions/:sessionId') if (!$projectDB->deleteDocument($token->getId())) { throw new Exception('Failed to remove token from DB', 500); } + + $webhooks + ->setParam('payload', $response->output($user, Response::MODEL_USER)) + ; } } - $response->json(array('result' => 'success')); - }, ['response', 'projectDB']); + $response->noContent(); + }, ['response', 'projectDB', 'webhooks']); App::delete('/v1/users/:userId/sessions') ->desc('Delete User Sessions') ->groups(['api', 'users']) + ->label('event', 'users.sessions.delete') ->label('scope', 'users.write') ->label('sdk.platform', [APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'users') @@ -519,9 +414,10 @@ App::delete('/v1/users/:userId/sessions') ->label('sdk.description', '/docs/references/users/delete-user-sessions.md') ->label('abuse-limit', 100) ->param('userId', '', new UID(), 'User unique ID.') - ->action(function ($userId, $response, $projectDB) { - /** @var Utopia\Response $response */ + ->action(function ($userId, $response, $projectDB, $webhooks) { + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $webhooks */ $user = $projectDB->getDocument($userId); @@ -537,12 +433,17 @@ App::delete('/v1/users/:userId/sessions') } } - $response->json(array('result' => 'success')); - }, ['response', 'projectDB']); + $webhooks + ->setParam('payload', $response->output($user, Response::MODEL_USER)) + ; + + $response->noContent(); + }, ['response', 'projectDB', 'webhooks']); App::delete('/v1/users/:userId') ->desc('Delete User') ->groups(['api', 'users']) + ->label('event', 'users.delete') ->label('scope', 'users.write') ->label('sdk.platform', [APP_PLATFORM_SERVER]) ->label('sdk.namespace', 'users') @@ -550,9 +451,10 @@ App::delete('/v1/users/:userId') ->label('sdk.description', '/docs/references/users/delete-user.md') ->label('abuse-limit', 100) ->param('userId', '', function () {return new UID();}, 'User unique ID.') - ->action(function ($userId, $response, $projectDB, $deletes) { - /** @var Utopia\Response $response */ + ->action(function ($userId, $response, $projectDB, $webhooks, $deletes) { + /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $webhooks */ /** @var Appwrite\Event\Event $deletes */ $user = $projectDB->getDocument($userId); @@ -580,7 +482,13 @@ App::delete('/v1/users/:userId') throw new Exception('Failed saving reserved id to DB', 500); } - $deletes->setParam('document', $user); + $deletes + ->setParam('document', $user) + ; + + $webhooks + ->setParam('payload', $response->output($user, Response::MODEL_USER)) + ; $response->noContent(); - }, ['response', 'projectDB', 'deletes']); + }, ['response', 'projectDB', 'webhooks', 'deletes']); diff --git a/app/controllers/general.php b/app/controllers/general.php index 5aae62ac3..c92181620 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; @@ -16,13 +16,14 @@ use Appwrite\Database\Validator\Authorization; use Appwrite\Network\Validator\Origin; use Appwrite\Storage\Device\Local; use Appwrite\Storage\Storage; +use Utopia\CLI\Console; 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, $clients) { + /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Document $console */ /** @var Appwrite\Database\Document $project */ @@ -31,6 +32,7 @@ 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 bool $mode */ /** @var array $clients */ @@ -44,7 +46,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo $route = $utopia->match($request); - if(!empty($route->getLabel('sdk.platform', [])) && empty($project->getId()) && ($route->getLabel('scope', '') !== 'public')) { + if (!empty($route->getLabel('sdk.platform', [])) && empty($project->getId()) && ($route->getLabel('scope', '') !== 'public')) { throw new Exception('Missing or unknown project ID', 400); } @@ -99,8 +101,8 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo * @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers */ if (App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS - if($request->getProtocol() !== 'https') { - return $response->redirect('https://'.$request->getHostname().$request->getURI()); + if ($request->getProtocol() !== 'https') { + return $response->redirect('https://'.$request->getHostname().$request->getURI()); } $response->addHeader('Strict-Transport-Security', 'max-age='.(60 * 60 * 24 * 126)); // 126 days @@ -126,11 +128,11 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo $origin = $request->getOrigin($request->getReferer('')); $originValidator = new Origin(\array_merge($project->getAttribute('platforms', []), $console->getAttribute('platforms', []))); - if(!$originValidator->isValid($origin) + if (!$originValidator->isValid($origin) && \in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH, Request::METHOD_DELETE]) && $route->getLabel('origin', false) !== '*' && empty($request->getHeader('x-appwrite-key', ''))) { - throw new Exception($originValidator->getDescription(), 403); + throw new Exception($originValidator->getDescription(), 403); } /* @@ -183,7 +185,10 @@ 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) { @@ -219,7 +224,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo */ $webhooks ->setParam('projectId', $project->getId()) - ->setParam('event', $route->getLabel('webhook', '')) + ->setParam('event', $route->getLabel('event', '')) ->setParam('payload', []) ; @@ -242,12 +247,16 @@ 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', 'clients']); App::shutdown(function ($utopia, $request, $response, $project, $webhooks, $audits, $usage, $deletes, $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 */ @@ -256,6 +265,10 @@ App::shutdown(function ($utopia, $request, $response, $project, $webhooks, $audi /** @var bool $mode */ if (!empty($webhooks->getParam('event'))) { + if(empty($webhooks->getParam('payload'))) { + $webhooks->setParam('payload', $response->getPayload()); + } + $webhooks->trigger(); } @@ -269,7 +282,7 @@ App::shutdown(function ($utopia, $request, $response, $project, $webhooks, $audi $route = $utopia->match($request); - if($project->getId() + if ($project->getId() && $mode !== APP_MODE_ADMIN && !empty($route->getLabel('sdk.namespace', null))) { // Don't calculate console usage and admin mode @@ -282,7 +295,7 @@ App::shutdown(function ($utopia, $request, $response, $project, $webhooks, $audi }, ['utopia', 'request', 'response', 'project', 'webhooks', 'audits', 'usage', 'deletes', '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,16 +313,21 @@ 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\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Utopia\View $layout */ /** @var Appwrite\Database\Document $project */ - if(php_sapi_name() === 'cli') { - var_dump(get_class($error)); - var_dump($error->getMessage()); - var_dump($error->getFile()); - var_dump($error->getLine()); + $route = $utopia->match($request); + $template = ($route) ? $route->getLabel('error', null) : null; + + if (php_sapi_name() === 'cli') { + Console::error('[Error] Method: '.$route->getMethod()); + Console::error('[Error] URL: '.$route->getURL()); + Console::error('[Error] Type: '.get_class($error)); + Console::error('[Error] Message: '.$error->getMessage()); + Console::error('[Error] File: '.$error->getFile()); + Console::error('[Error] Line: '.$error->getLine()); } $version = App::getEnv('_APP_VERSION', 'UNKNOWN'); @@ -352,10 +370,7 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project) { ->addHeader('Pragma', 'no-cache') ->setStatusCode($code) ; - - $route = $utopia->match($request); - $template = ($route) ? $route->getLabel('error', null) : null; - + if ($template) { $comp = new View($template); @@ -379,7 +394,6 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project) { $response->dynamic(new Document($output), $utopia->isDevelopment() ? Response::MODEL_ERROR_DEV : Response::MODEL_LOCALE); - }, ['error', 'utopia', 'request', 'response', 'layout', 'project']); App::get('/manifest.json') @@ -387,7 +401,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 +427,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 +436,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']); @@ -435,25 +449,25 @@ App::get('/.well-known/acme-challenge') $path = \str_replace('/.well-known/acme-challenge/', '', $request->getParam('q')); $absolute = \realpath($base.'/.well-known/acme-challenge/'.$path); - if(!$base) { + if (!$base) { throw new Exception('Storage error', 500); } - if(!$absolute) { + if (!$absolute) { throw new Exception('Unknown path', 404); } - if(!\substr($absolute, 0, \strlen($base)) === $base) { + if (!\substr($absolute, 0, \strlen($base)) === $base) { throw new Exception('Invalid path', 401); } - if(!\file_exists($absolute)) { + if (!\file_exists($absolute)) { throw new Exception('Unknown path', 404); } $content = @\file_get_contents($absolute); - if(!$content) { + if (!$content) { throw new Exception('Failed to get contents', 500); } @@ -463,6 +477,6 @@ App::get('/.well-known/acme-challenge') include_once __DIR__ . '/shared/api.php'; include_once __DIR__ . '/shared/web.php'; -foreach(Config::getParam('services', []) as $service) { +foreach (Config::getParam('services', []) as $service) { include_once $service['controller']; } \ No newline at end of file diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 3fdca7cf1..d64c46a2c 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 3452cd353..42d2a18c5 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 6c2f7ba44..a9b50b05b 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', '', 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 */ @@ -385,10 +385,9 @@ App::get('/console/version') try { $version = \json_decode(@\file_get_contents(App::getEnv('_APP_HOME', 'http://localhost').'/v1/health/version'), true); - if($version && isset($version['version'])) { + if ($version && isset($version['version'])) { return $response->json(['version' => $version['version']]); - } - else { + } else { throw new Exception('Failed to check for a newer version', 500); } } catch (\Throwable $th) { diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index a6c2491c0..7f7f41970 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, 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 a5f0dbd43..07e0fff45 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 /* 10mb */)); $http ->set([ @@ -49,7 +49,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 d24ca9d28..281bc743c 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'); }); @@ -361,8 +364,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 */ @@ -423,7 +426,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(); @@ -444,7 +447,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; @@ -460,11 +462,11 @@ 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) { + /** @var Utopia\Registry\Registry $register */ + return $register->get('geodb'); +}, ['register']); diff --git a/app/tasks/doctor.php b/app/tasks/doctor.php index 0ab19f290..27479bb30 100644 --- a/app/tasks/doctor.php +++ b/app/tasks/doctor.php @@ -213,7 +213,6 @@ $cli Console::error('🔴 ' . $message); } } - try { Console::log(''); @@ -232,4 +231,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 e44488ce0..abfd295d8 100644 --- a/app/tasks/migrate.php +++ b/app/tasks/migrate.php @@ -7,15 +7,18 @@ 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; $callbacks = [ '0.4.0' => function() { Console::log('I got nothing to do.'); }, - '0.5.0' => function($project) use ($register, $projectDB, $requset) { + + '0.5.0' => function($project) use ($register, $projectDB) { $db = $register->get('db'); - Console::log('Migrating project: '.$project->getId()); + Console::log('Migrating project: '.$project->getAttribute('name').' ('.$project->getId().')'); // Update all documents $uid -> $id @@ -46,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; } @@ -107,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)))) { @@ -165,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()); } } @@ -201,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/views/console/account/index.phtml b/app/views/console/account/index.phtml index 1606b41bd..84a18521d 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"> -