Merge pull request #721 from appwrite/feat-response-models

Feat response models
This commit is contained in:
Eldad A. Fux 2020-10-31 07:43:48 +02:00 committed by GitHub
commit 10c4b093d2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 19150 additions and 22145 deletions

View file

@ -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',

View file

@ -28,23 +28,10 @@ use Utopia\Validator\ArrayList;
$oauthDefaultSuccess = App::getEnv('_APP_HOME').'/auth/oauth2/success';
$oauthDefaultFailure = App::getEnv('_APP_HOME').'/auth/oauth2/failure';
$oauth2Keys = [];
App::init(function() use (&$oauth2Keys) {
foreach (Config::getParam('providers') as $key => $provider) {
if (!$provider['enabled']) {
continue;
}
$oauth2Keys[] = 'oauth2'.\ucfirst($key);
$oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken';
}
}, [], 'account');
App::post('/v1/account')
->desc('Create Account')
->groups(['api', 'account'])
->label('webhook', 'account.create')
->label('event', 'account.create')
->label('scope', 'public')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.namespace', 'account')
@ -54,12 +41,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) {
->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()) {
@ -120,36 +106,24 @@ App::post('/v1/account')
throw new Exception('Failed saving user to DB', 500);
}
$webhooks
->setParam('payload', [
'name' => $name,
'email' => $email,
])
;
$audits
->setParam('userId', $user->getId())
->setParam('event', 'account.create')
->setParam('resource', 'users/'.$user->getId())
;
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json(\array_merge($user->getArrayCopy(\array_merge(
[
'$id',
'email',
'registration',
'name',
],
$oauth2Keys
)), ['roles' => Authorization::getRoles()]));
}, ['request', 'response', 'project', 'projectDB', 'webhooks', 'audits']);
$user
->setAttribute('roles', Authorization::getRoles())
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->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')
@ -159,11 +133,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) {
->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();
@ -185,6 +160,23 @@ App::post('/v1/account/sessions')
throw new Exception('Invalid credentials', 401); // Wrong password or username
}
$dd = new DeviceDetector($request->getUserAgent('UNKNOWN'));
$dd->parse();
$os = $dd->getOs();
$osCode = (isset($os['short_name'])) ? $os['short_name'] : '';
$osName = (isset($os['name'])) ? $os['name'] : '';
$osVersion = (isset($os['version'])) ? $os['version'] : '';
$client = $dd->getClient();
$clientType = (isset($client['type'])) ? $client['type'] : '';
$clientCode = (isset($client['short_name'])) ? $client['short_name'] : '';
$clientName = (isset($client['name'])) ? $client['name'] : '';
$clientVersion = (isset($client['version'])) ? $client['version'] : '';
$clientEngine = (isset($client['engine'])) ? $client['engine'] : '';
$clientEngineVersion = (isset($client['engine_version'])) ? $client['engine_version'] : '';
$expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$secret = Auth::tokenGenerator();
$session = new Document([
@ -195,8 +187,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());
@ -212,14 +229,7 @@ App::post('/v1/account/sessions')
if (false === $profile) {
throw new Exception('Failed saving user to DB', 500);
}
$webhooks
->setParam('payload', [
'name' => $profile->getAttribute('name', ''),
'email' => $profile->getAttribute('email', ''),
])
;
$audits
->setParam('userId', $profile->getId())
->setParam('event', 'account.sessions.create')
@ -237,10 +247,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')
@ -348,7 +362,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
->desc('OAuth2 Redirect')
->groups(['api', 'account'])
->label('error', __DIR__.'/../../views/general/error.phtml')
->label('webhook', 'account.sessions.create')
->label('event', 'account.sessions.create')
->label('scope', 'public')
->label('abuse-limit', 50)
->label('abuse-key', 'ip:{ip}')
@ -356,12 +370,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) {
->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();
@ -482,6 +497,24 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
// Create session token, verify user account and update OAuth2 ID and Access Token
$dd = new DeviceDetector($request->getUserAgent('UNKNOWN'));
$dd->parse();
$os = $dd->getOs();
$osCode = (isset($os['short_name'])) ? $os['short_name'] : '';
$osName = (isset($os['name'])) ? $os['name'] : '';
$osVersion = (isset($os['version'])) ? $os['version'] : '';
$client = $dd->getClient();
$clientType = (isset($client['type'])) ? $client['type'] : '';
$clientCode = (isset($client['short_name'])) ? $client['short_name'] : '';
$clientName = (isset($client['name'])) ? $client['name'] : '';
$clientVersion = (isset($client['version'])) ? $client['version'] : '';
$clientEngine = (isset($client['engine'])) ? $client['engine'] : '';
$clientEngineVersion = (isset($client['engine_version'])) ? $client['engine_version'] : '';
$secret = Auth::tokenGenerator();
$expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$session = new Document([
@ -492,8 +525,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)
@ -523,7 +581,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
}
// Add keys for non-web platforms - TODO - add verification phase to aviod session sniffing
if (parse_url($state['success'], PHP_URL_PATH) === $oauthDefaultSuccess) {
if (parse_url($state['success'], PHP_URL_PATH) === parse_url($oauthDefaultSuccess, PHP_URL_PATH)) {
$state['success'] = URLParser::parse($state['success']);
$query = URLParser::parseQuery($state['success']['query']);
$query['project'] = $project->getId();
@ -541,7 +599,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite'))
->redirect($state['success'])
;
}, ['request', 'response', 'project', 'user', 'projectDB', 'audits']);
}, ['request', 'response', 'project', 'user', 'projectDB', 'geodb', 'audits']);
App::get('/v1/account')
->desc('Get Account')
@ -552,20 +610,15 @@ App::get('/v1/account')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/account/get.md')
->label('sdk.response', ['200' => 'user'])
->action(function ($response, $user) use ($oauth2Keys) {
->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')
@ -580,14 +633,7 @@ App::get('/v1/account/prefs')
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Document $user */
$prefs = $user->getAttribute('prefs', '{}');
try {
$prefs = \json_decode($prefs, true);
$prefs = ($prefs) ? $prefs : [];
} catch (\Exception $error) {
throw new Exception('Failed to parse prefs', 500);
}
$prefs = $user->getAttribute('prefs', new \stdClass);
$response->json($prefs);
}, ['response', 'user']);
@ -600,64 +646,34 @@ App::get('/v1/account/sessions')
->label('sdk.namespace', 'account')
->label('sdk.method', 'getSessions')
->label('sdk.description', '/docs/references/account/get-sessions.md')
->action(function ($response, $user, $locale, $geodb) {
->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')
@ -709,48 +725,64 @@ 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');
}
} catch (\Exception $e) {
$output[$i]['geo']['isoCode'] = '--';
$output[$i]['geo']['country'] = $locale->getText('locale.country.unknown');
$record = $geodb->get($log['ip']);
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) {
->action(function ($name, $response, $user, $projectDB, $audits) {
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Document $user */
/** @var Appwrite\Database\Database $projectDB */
@ -764,27 +796,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')
@ -792,7 +818,7 @@ 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) {
->action(function ($password, $oldPassword, $response, $user, $projectDB, $audits) {
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Document $user */
/** @var Appwrite\Database\Database $projectDB */
@ -810,27 +836,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')
@ -838,7 +858,7 @@ 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) {
->action(function ($email, $password, $response, $user, $projectDB, $audits) {
/** @var Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Document $user */
/** @var Appwrite\Database\Database $projectDB */
@ -871,44 +891,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 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) {
@ -920,14 +931,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']);
@ -935,7 +939,7 @@ 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')
@ -974,10 +978,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')) {
@ -997,7 +998,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')
@ -1032,10 +1033,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')) {
@ -1062,7 +1060,7 @@ 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')
@ -1089,12 +1087,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')) {

View file

@ -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) {
->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,15 @@ App::post('/v1/database/collections')
throw new Exception('Failed saving collection to DB', 500);
}
$data = $data->getArrayCopy();
$webhooks
->setParam('payload', $data)
;
$audits
->setParam('event', 'database.collections.create')
->setParam('resource', 'database/collection/'.$data['$id'])
->setParam('data', $data)
->setParam('resource', 'database/collection/'.$data->getId())
->setParam('data', $data->getArrayCopy())
;
/*
* View
*/
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($data)
;
}, ['response', 'projectDB', 'webhooks', 'audits']);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($data, Response::MODEL_COLLECTION);
}, ['response', 'projectDB', 'audits']);
App::get('/v1/database/collections')
->desc('List Collections')
@ -126,7 +111,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')
@ -148,79 +136,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 +153,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) {
->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,26 +199,20 @@ 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')
@ -318,16 +234,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 +250,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 +263,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) {
->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 +300,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 +349,18 @@ App::post('/v1/database/collections/:collectionId/documents')
throw new Exception('Failed saving document to DB'.$exception->getMessage(), 500);
}
$data = $data->getArrayCopy();
$webhooks
->setParam('payload', $data)
;
$audits
->setParam('event', 'database.documents.create')
->setParam('resource', 'database/document/'.$data['$id'])
->setParam('data', $data)
->setParam('data', $data->getArrayCopy())
;
/*
* View
*/
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($data)
;
}, ['response', 'projectDB', 'webhooks', 'audits']);
$response->dynamic($data, Response::MODEL_ANY);
}, ['response', 'projectDB', 'audits']);
App::get('/v1/database/collections/:collectionId/documents')
->desc('List Documents')
@ -495,27 +400,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')
@ -540,36 +442,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 +459,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) {
->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 +470,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 +499,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 +510,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')
@ -687,16 +557,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();

View file

@ -1,15 +1,16 @@
<?php
use Appwrite\Database\Database;
use Appwrite\Database\Document;
use Appwrite\Database\Validator\UID;
use Appwrite\Storage\Storage;
use Appwrite\Storage\Validator\File;
use Appwrite\Storage\Validator\FileSize;
use Appwrite\Storage\Validator\FileType;
use Appwrite\Storage\Validator\Upload;
use Appwrite\Utopia\Response;
use Appwrite\Task\Validator\Cron;
use Utopia\App;
use Utopia\Response;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Assoc;
use Utopia\Validator\Text;
@ -43,15 +44,15 @@ App::post('/v1/functions')
],
'dateCreated' => 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,
]);
@ -59,10 +60,8 @@ App::post('/v1/functions')
throw new Exception('Failed saving function to DB', 500);
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($function->getArrayCopy())
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($function, Response::MODEL_FUNCTION);
}, ['response', 'projectDB']);
App::get('/v1/functions')
@ -90,7 +89,10 @@ App::get('/v1/functions')
],
]);
$response->json(['sum' => $projectDB->getSum(), 'functions' => $results]);
$response->dynamic(new Document([
'sum' => $projectDB->getSum(),
'functions' => $results
]), Response::MODEL_FUNCTION_LIST);
}, ['response', 'projectDB']);
App::get('/v1/functions/:functionId')
@ -106,12 +108,125 @@ App::get('/v1/functions/:functionId')
$function = $projectDB->getDocument($functionId);
if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) {
throw new Exception('function not found', 404);
throw new Exception('Function not found', 404);
}
$response->json($function->getArrayCopy());
$response->dynamic($function, Response::MODEL_FUNCTION);
}, ['response', 'projectDB']);
App::get('/v1/functions/:functionId/usage')
->desc('Get Function Usage')
->groups(['api', 'functions'])
->label('scope', 'functions.read')
->label('sdk.platform', [APP_PLATFORM_CONSOLE])
->label('sdk.namespace', 'functions')
->label('sdk.method', 'getUsage')
->param('functionId', '', 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 +257,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 +310,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 +329,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 +344,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 +412,11 @@ App::post('/v1/functions/:functionId/tags')
'read' => [],
'write' => [],
],
'dateCreated' => time(),
'functionId' => $function->getId(),
'dateCreated' => time(),
'command' => $command,
'codePath' => $path,
'codeSize' => $size,
'path' => $path,
'size' => $size,
]);
if (false === $tag) {
@ -288,13 +424,11 @@ App::post('/v1/functions/:functionId/tags')
}
$usage
->setParam('storage', $tag->getAttribute('codeSize', 0))
->setParam('storage', $tag->getAttribute('size', 0))
;
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($tag->getArrayCopy())
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($tag, Response::MODEL_TAG);
}, ['request', 'response', 'projectDB', 'usage']);
App::get('/v1/functions/:functionId/tags')
@ -330,7 +464,10 @@ App::get('/v1/functions/:functionId/tags')
],
]);
$response->json(['sum' => $projectDB->getSum(), 'tags' => $results]);
$response->dynamic(new Document([
'sum' => $projectDB->getSum(),
'tags' => $results
]), Response::MODEL_TAG_LIST);
}, ['response', 'projectDB']);
App::get('/v1/functions/:functionId/tags/:tagId')
@ -360,7 +497,7 @@ App::get('/v1/functions/:functionId/tags/:tagId')
throw new Exception('Tag not found', 404);
}
$response->json($tag->getArrayCopy());
$response->dynamic($tag, Response::MODEL_TAG);
}, ['response', 'projectDB']);
App::delete('/v1/functions/:functionId/tags/:tagId')
@ -392,14 +529,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,8 +561,8 @@ 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) {
// ->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 */
@ -444,6 +591,7 @@ App::post('/v1/functions/:functionId/executions')
],
'dateCreated' => time(),
'functionId' => $function->getId(),
'trigger' => 'http', // http / schedule / event
'status' => 'waiting', // waiting / processing / completed / failed
'exitCode' => 0,
'stdout' => '',
@ -454,22 +602,17 @@ App::post('/v1/functions/:functionId/executions')
if (false === $execution) {
throw new Exception('Failed saving execution to DB', 500);
}
if ((bool)$async) {
// Issue a TLS certificate when domain is verified
Resque::enqueue('v1-functions', 'FunctionsV1', [
'projectId' => $project->getId(),
'functionId' => $function->getId(),
'executionId' => $execution->getId(),
'functionTag' => $tag->getId(),
'functionTrigger' => 'API',
]);
}
// Issue a TLS certificate when domain is verified
Resque::enqueue('v1-functions', 'FunctionsV1', [
'projectId' => $project->getId(),
'functionId' => $function->getId(),
'executionId' => $execution->getId(),
'trigger' => 'http',
]);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($execution->getArrayCopy())
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($execution, Response::MODEL_EXECUTION);
}, ['response', 'project', 'projectDB']);
App::get('/v1/functions/:functionId/executions')
@ -505,7 +648,10 @@ App::get('/v1/functions/:functionId/executions')
],
]);
$response->json(['sum' => $projectDB->getSum(), 'executions' => $results]);
$response->dynamic(new Document([
'sum' => $projectDB->getSum(),
'executions' => $results
]), Response::MODEL_EXECUTION_LIST);
}, ['response', 'projectDB']);
App::get('/v1/functions/:functionId/executions/:executionId')
@ -535,5 +681,5 @@ App::get('/v1/functions/:functionId/executions/:executionId')
throw new Exception('Execution not found', 404);
}
$response->json($execution->getArrayCopy());
}, ['response', 'projectDB']);
$response->dynamic($execution, Response::MODEL_EXECUTION);
}, ['response', 'projectDB']);

View file

@ -1,5 +1,7 @@
<?php
use Appwrite\Database\Document;
use Appwrite\Utopia\Response;
use Utopia\App;
use Utopia\Config\Config;
@ -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')
@ -73,10 +76,18 @@ App::get('/v1/locale/countries')
/** @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')
@ -91,19 +102,22 @@ App::get('/v1/locale/countries/eu')
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Locale\Locale $locale */
$countries = $locale->getText('countries'); /* @var $countries array */
$list = $locale->getText('countries'); /* @var $countries array */
$eu = Config::getParam('locale-eu');
$list = [];
foreach ($eu as $code) {
if (\array_key_exists($code, $countries)) {
$list[$code] = $countries[$code];
}
}
$output = [];
\asort($list);
$response->json($list);
foreach ($eu as $code) {
if (\array_key_exists($code, $list)) {
$output[] = new Document([
'name' => $list[$code],
'code' => $code,
]);
}
}
$response->dynamic(new Document(['countries' => $output, 'sum' => \count($output)]), Response::MODEL_COUNTRY_LIST);
}, ['response', 'locale']);
App::get('/v1/locale/countries/phones')
@ -119,18 +133,22 @@ App::get('/v1/locale/countries/phones')
/** @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')
@ -148,8 +166,15 @@ App::get('/v1/locale/continents')
$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')
@ -163,9 +188,13 @@ App::get('/v1/locale/currencies')
->action(function ($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']);
@ -180,7 +209,11 @@ App::get('/v1/locale/languages')
->action(function ($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']);

View file

@ -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')
@ -70,6 +70,7 @@ App::post('/v1/projects')
'webhooks' => [],
'keys' => [],
'tasks' => [],
'domains' => [],
]
);
@ -79,10 +80,8 @@ App::post('/v1/projects')
$consoleDB->createNamespace($project->getId());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($project->getArrayCopy())
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($project, Response::MODEL_PROJECT);
}, ['response', 'consoleDB', 'projectDB']);
App::get('/v1/projects')
@ -111,7 +110,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')
@ -131,7 +133,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,7 +143,7 @@ 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 Appwrite\Utopia\Response $response */
/** @var Appwrite\Database\Database $consoleDB */
@ -155,31 +157,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 +254,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 +296,7 @@ App::get('/v1/projects/:projectId/usage')
) +
$projectDB->getCount(
[
'attribute' => 'codeSize',
'attribute' => 'size',
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_TAGS,
],
@ -352,7 +350,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')
@ -384,7 +382,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')
@ -487,10 +485,8 @@ App::post('/v1/projects/:projectId/webhooks')
throw new Exception('Failed saving project to DB', 500);
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($webhook->getArrayCopy())
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($webhook, Response::MODEL_WEBHOOK);
}, ['response', 'consoleDB']);
App::get('/v1/projects/:projectId/webhooks')
@ -512,7 +508,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')
@ -539,7 +538,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')
@ -587,7 +586,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')
@ -665,10 +664,8 @@ App::post('/v1/projects/:projectId/keys')
throw new Exception('Failed saving project to DB', 500);
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($key->getArrayCopy())
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($key, Response::MODEL_KEY);
}, ['response', 'consoleDB']);
App::get('/v1/projects/:projectId/keys')
@ -688,7 +685,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 +714,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')
@ -750,7 +752,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')
@ -855,10 +857,8 @@ App::post('/v1/projects/:projectId/tasks')
ResqueScheduler::enqueueAt($next, 'v1-tasks', 'TasksV1', $task->getArrayCopy());
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($task->getArrayCopy())
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($task, Response::MODEL_TASK);
}, ['response', 'consoleDB']);
App::get('/v1/projects/:projectId/tasks')
@ -880,7 +880,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')
@ -907,7 +911,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')
@ -970,7 +974,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')
@ -1055,10 +1059,8 @@ App::post('/v1/projects/:projectId/platforms')
throw new Exception('Failed saving project to DB', 500);
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($platform->getArrayCopy())
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($platform, Response::MODEL_PLATFORM);
}, ['response', 'consoleDB']);
App::get('/v1/projects/:projectId/platforms')
@ -1080,7 +1082,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')
@ -1107,7 +1112,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')
@ -1150,7 +1155,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')
@ -1244,10 +1249,8 @@ App::post('/v1/projects/:projectId/domains')
throw new Exception('Failed saving project to DB', 500);
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($domain->getArrayCopy())
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($domain, Response::MODEL_DOMAIN);
}, ['response', 'consoleDB']);
App::get('/v1/projects/:projectId/domains')
@ -1268,8 +1271,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')
@ -1296,7 +1302,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')
@ -1330,7 +1336,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 +1360,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')

View file

@ -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) {
->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())
@ -153,11 +149,9 @@ App::post('/v1/storage/files')
->setParam('storage', $sizeActual)
;
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($file->getArrayCopy())
;
}, ['request', 'response', 'user', 'projectDB', 'webhooks', 'audits', 'usage']);
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($file, Response::MODEL_FILE);
}, ['request', 'response', 'user', 'projectDB', 'audits', 'usage']);
App::get('/v1/storage/files')
->desc('List Files')
@ -187,11 +181,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')
@ -213,7 +206,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')
@ -474,7 +467,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 +475,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) {
->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,23 +498,19 @@ 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')
@ -548,11 +536,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 +546,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 +601,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()));
// }
// );

View file

@ -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')
@ -80,10 +81,8 @@ App::post('/v1/teams')
}
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($team->getArrayCopy())
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($team, Response::MODEL_TEAM);
}, ['response', 'user', 'projectDB', 'mode']);
App::get('/v1/teams')
@ -114,7 +113,10 @@ App::get('/v1/teams')
],
]);
$response->json(['sum' => $projectDB->getSum(), 'teams' => $results]);
$response->dynamic(new Document([
'sum' => $projectDB->getSum(),
'teams' => $results
]), Response::MODEL_TEAM_LIST);
}, ['response', 'projectDB']);
App::get('/v1/teams/:teamId')
@ -136,7 +138,7 @@ App::get('/v1/teams/:teamId')
throw new Exception('Team not found', 404);
}
$response->json($team->getArrayCopy([]));
$response->dynamic($team, Response::MODEL_TEAM);
}, ['response', 'projectDB']);
App::put('/v1/teams/:teamId')
@ -166,8 +168,8 @@ App::put('/v1/teams/:teamId')
if (false === $team) {
throw new Exception('Failed saving team to DB', 500);
}
$response->json($team->getArrayCopy());
$response->dynamic($team, Response::MODEL_TEAM);
}, ['response', 'projectDB']);
App::delete('/v1/teams/:teamId')
@ -391,21 +393,12 @@ App::post('/v1/teams/:teamId/memberships')
->setParam('resource', 'teams/'.$teamId)
;
$response
->setStatusCode(Response::STATUS_CODE_CREATED) // TODO change response of this endpoint
->json(\array_merge($membership->getArrayCopy([
'$id',
'userId',
'teamId',
'roles',
'invited',
'joined',
'confirm',
]), [
'email' => $email,
'name' => $name,
]))
;
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic(new Document(\array_merge($membership->getArrayCopy(), [
'email' => $email,
'name' => $name,
])), Response::MODEL_MEMBERSHIP);
}, ['response', 'project', 'user', 'projectDB', 'locale', 'audits', 'mails', 'mode']);
App::get('/v1/teams/:teamId/memberships')
@ -453,18 +446,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 +464,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) {
->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 +526,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 +555,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,7 +625,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')

View file

@ -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')
@ -65,27 +66,8 @@ App::post('/v1/users')
throw new Exception('Account already exists', 409);
}
$oauth2Keys = [];
foreach (Config::getParam('providers') as $key => $provider) {
if (!$provider['enabled']) {
continue;
}
$oauth2Keys[] = 'oauth2'.\ucfirst($key);
$oauth2Keys[] = 'oauth2'.\ucfirst($key).'AccessToken';
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json(\array_merge($user->getArrayCopy(\array_merge([
'$id',
'status',
'email',
'registration',
'emailVerification',
'name',
], $oauth2Keys)), ['roles' => []]));
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($user, Response::MODEL_USER);
}, ['response', 'projectDB']);
App::get('/v1/users')
@ -116,32 +98,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')
@ -163,28 +123,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')
@ -208,13 +147,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 +159,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) {
->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 +172,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,46 +179,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')
@ -349,40 +252,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');
}
} catch (\Exception $e) {
$output[$i]['geo']['isoCode'] = '--';
$output[$i]['geo']['country'] = $locale->getText('locale.country.unknown');
$record = $geodb->get($log['ip']);
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')
@ -407,27 +326,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')
@ -450,32 +350,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')
@ -484,9 +373,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) {
->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);
@ -501,15 +391,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')
@ -517,9 +412,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) {
->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);
@ -535,12 +431,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')
@ -548,9 +449,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) {
->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);
@ -578,7 +480,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']);

View file

@ -22,7 +22,7 @@ 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) {
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 */
@ -32,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 */
@ -187,7 +188,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo
if ($user->getId()) {
Authorization::setRole('user:'.$user->getId());
}
Authorization::setRole('role:'.$role);
\array_map(function ($node) {
@ -223,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', [])
;
@ -246,7 +247,11 @@ 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 */
@ -260,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();
}

View file

@ -192,7 +192,7 @@
data-name="sessions"
data-event="load,account.deleteRemoteSession">
<ul data-ls-loop="sessions" data-ls-as="session" class="list">
<ul data-ls-loop="sessions.sessions" data-ls-as="session" class="list">
<li class="clear">
<span data-ls-if="true != {{session.current}}">
<!-- From remote session (-logout event) -->
@ -236,17 +236,18 @@
</form>
</span>
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{session.client.short_name|lowercase}}?width=120&height=120&project={{env.PROJECT}},title={{session.client.name}},alt={{session.client.name}}" class="avatar trans pull-start margin-end" />
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{session.clientCode|lowercase}}?width=120&height=120&project={{env.PROJECT}},title={{session.clientName}},alt={{session.clientName}}" class="avatar trans pull-start margin-end" />
<span data-ls-if="({{session.client.name}})" data-ls-bind="{{session.client.name}}"></span> <span data-ls-if="(!{{session.client.name}})">Unknown</span> <span data-ls-bind="{{session.client.version}}"></span> on <span data-ls-bind="{{session.model}}"></span> <span data-ls-if="(!{{session.OS.name}})">Unknown</span> <span data-ls-if="({{session.OS.name}})" data-ls-bind="{{session.OS.name}}"></span> <span data-ls-bind="{{session.OS.version}}"></span>
<span data-ls-if="(!{{log.clientName}})">Unknown</span>
<span data-ls-if="({{log.clientName}})" data-ls-bind="{{session.clientName}}"></span> <span data-ls-bind="{{session.clientVersion}}"></span> on <span data-ls-bind="{{session.deviceModel}}"></span> <span data-ls-bind="{{session.osName}}"></span> <span data-ls-bind="{{session.osVersion}}"></span>
&nbsp;
<span data-ls-if="true == {{session.current}}">
<span class="tag green">Current Session</span>
</span>
<div class="margin-top-small">
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-attrs="src={{env.API}}/avatars/flags/{{session.geo.isoCode}}?width=80&height=80&project={{env.PROJECT}}" class="avatar xxs margin-end-small inline" />
<small data-ls-bind="{{session.ip}}"></small> / <small data-ls-bind="{{session.geo.country}}"></small>
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-if="{{session.countryCode}} !== '--'" data-ls-attrs="src={{env.API}}/avatars/flags/{{session.countryCode}}?width=80&height=80&project={{env.PROJECT}}" class="avatar xxs margin-end-small inline" />
<small data-ls-bind="{{session.ip}}"></small> / <small data-ls-bind="{{session.countryName}}"></small>
</div>
</li>
</ul>
@ -289,17 +290,18 @@
<th width="90">IP</th>
</tr>
</thead>
<tbody data-ls-loop="securityLogs" data-ls-as="log">
<tbody data-ls-loop="securityLogs.logs" data-ls-as="log">
<tr>
<td data-title="Date: "><span data-ls-bind="{{log.time|dateTime}}"></span></td>
<td data-title="Event: "><span data-ls-bind="{{log.event}}"></span></td>
<td data-title="Client: ">
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{log.client.short_name|lowercase}}?width=80&height=80&project={{env.PROJECT}},title={{log.client.name}},alt={{log.client.name}}" class="avatar xxs inline margin-end-small" />
<span data-ls-bind="{{log.client.name}} {{log.client.version}} on {{log.model}} {{log.OS.name}} {{log.OS.version}}"></span>
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{log.clientCode|lowercase}}?width=80&height=80&project={{env.PROJECT}},title={{log.clientName}},alt={{log.clientName}}" class="avatar xxs inline margin-end-small" />
<span data-ls-if="(!{{log.clientName}})">Unknown</span>
<span data-ls-if="({{log.clientName}})" data-ls-bind="{{log.clientName}} {{log.clientVersion}} on {{log.model}} {{log.osName}} {{log.osVersion}}"></span>
</td>
<td data-title="Location: ">
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-attrs="src={{env.API}}/avatars/flags/{{log.geo.isoCode}}?width=80&height=80&project={{env.PROJECT}}" class="avatar xxs inline margin-end-small" />
<span data-ls-bind="{{log.geo.country}}"></span>
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-attrs="src={{env.API}}/avatars/flags/{{log.countryCode}}?width=80&height=80&project={{env.PROJECT}}" class="avatar xxs inline margin-end-small" />
<span data-ls-bind="{{log.geo.countryName}}"></span>
</td>
<td data-title="IP: "><span data-ls-bind="{{log.ip}}"></span></td>
</tr>

View file

@ -3,12 +3,11 @@ $fileLimit = $this->getParam('fileLimit', 0);
$fileLimitHuman = $this->getParam('fileLimitHuman', 0);
$events = array_keys($this->getParam('events', []));
$timeout = $this->getParam('timeout', 900);
?>
<div
data-service="functions.get"
data-name="project-function"
data-event="load,functions.update,functions.createTag,functions.updateTag"
data-event="load,functions.update,functions.createTag,functions.updateTag,functions.deleteTag"
data-param-function-id="{{router.params.id}}"
data-success="trigger"
data-success-param-trigger-events="functions.get">
@ -64,7 +63,7 @@ $timeout = $this->getParam('timeout', 900);
data-failure="alert"
data-failure-param-alert-text="Failed to execute function"
data-failure-param-alert-classname="error">
<button style="vertical-align: top;">Execute Now</button> &nbsp; <a data-ls-attrs="href=/console/functions/function/usage?id={{router.params.id}}&project={{router.params.project}}" class="button reverse" style="vertical-align: top;">View Logs</a>
<button style="vertical-align: top;">Execute Now</button> &nbsp; <a data-ls-attrs="href=/console/functions/function/logs?id={{router.params.id}}&project={{router.params.project}}" class="button reverse" style="vertical-align: top;">View Logs</a>
</form>
</div>
</div>
@ -113,7 +112,7 @@ $timeout = $this->getParam('timeout', 900);
<b data-ls-bind="{{tag.$id}}"></b> &nbsp;
<span class="text-fade" data-ls-bind="{{tag.command}}"></span>
<div class="text-size-small margin-top-small clear">
<span class="pull-start" data-ls-bind="Created {{tag.dateCreated|timeSince}} &nbsp; | &nbsp; {{tag.codeSize|humanFileSize}}"></span>
<span class="pull-start" data-ls-bind="Created {{tag.dateCreated|timeSince}} &nbsp; | &nbsp; {{tag.size|humanFileSize}}"></span>
<form data-ls-if="{{tag.$id}} !== {{project-function.tag}}" name="functions.deleteTag" class="pull-start"
data-analytics-event="submit"
@ -241,23 +240,89 @@ $timeout = $this->getParam('timeout', 900);
</div>
</div>
</li>
<li data-state="/console/functions/function/usage?id={{router.params.id}}&project={{router.params.project}}">
<h2>Usage</h2>
<li data-state="/console/functions/function/monitors?id={{router.params.id}}&project={{router.params.project}}">
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '90d'"
data-service="functions.getUsage"
data-event="submit"
data-name="usage"
data-param-function-id="{{router.params.id}}"
data-param-range="90d">
<button class="tick">90d</button>
</form>
<div class="box margin-bottom-small">
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
<div class="chart margin-bottom-no">
<div class="content" data-forms-chart="Requests=usage.requests.data,Network=usage.network.data" data-height="140"></div>
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '90d'" disabled>90d</button>
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '30d'"
data-service="functions.getUsage"
data-event="submit"
data-name="usage"
data-param-function-id="{{router.params.id}}">
<button class="tick">30d</button>
</form>
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '30d'" disabled>30d</button>
<form class="pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} !== '24h'"
data-service="functions.getUsage"
data-event="submit"
data-name="usage"
data-param-function-id="{{router.params.id}}"
data-param-range="24h">
<button class="tick">24h</button>
</form>
<button class="tick pull-end margin-start-small margin-top-small" data-ls-if="{{usage.range}} === '24h'" disabled>24h</button>
<h2>Monitors</h2>
<div
data-service="functions.getUsage"
data-event="load"
data-name="usage"
data-param-function-id="{{router.params.id}}">
<div class="box margin-bottom-small">
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
<div class="chart margin-bottom-no">
<input type="hidden" data-ls-bind="{{usage}}" data-forms-chart="Executions=usage.executions.data" data-height="140" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large">
<li>Executions <span data-ls-bind="({{usage.executions.total}})"></span></li>
</ul>
<div class="box margin-bottom-small">
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
<div class="chart margin-bottom-no">
<input type="hidden" data-ls-bind="{{usage}}" data-forms-chart="CPU Time (seconds)=usage.compute.data" data-colors="orange" data-height="140" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large">
<li class="orange">CPU Time <span data-ls-bind="({{usage.compute.total|seconds2hum}})"></span></li>
</ul>
<div class="box margin-bottom-small">
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
<div class="chart margin-bottom-no">
<input type="hidden" data-ls-bind="{{usage}}" data-forms-chart="Failures=usage.failures.data" data-colors="red" data-height="140" />
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large">
<li class="red">Errors <span data-ls-bind="({{usage.failures.total}})"></span></li>
</ul>
</div>
</li>
<li data-state="/console/functions/function/logs?id={{router.params.id}}&project={{router.params.project}}">
<ul class="chart-notes margin-bottom-large">
<li>Invocations</li>
<li>CPU Time</li>
</ul>
<div class="text-fade text-size-small pull-end margin-top" data-ls-bind="{{project-function-executions.sum}} executions found"></div>
<h3>Logs &nbsp; <span class="text-fade text-size-small pull-end margin-top-small" data-ls-bind="{{project-function-executions.sum}} executions found"></span></h3>
<h2>Logs</h2>
<div
data-service="functions.listExecutions"
@ -277,9 +342,10 @@ $timeout = $this->getParam('timeout', 900);
<thead>
<tr>
<th width="30"></th>
<th width="160">Created</th>
<th width="150">Status</th>
<th width="170">Date</th>
<th width="100">Runtime</th>
<th width="120">Trigger</th>
<th width="80">Runtime</th>
<th></th>
</tr>
</thead>
@ -291,35 +357,42 @@ $timeout = $this->getParam('timeout', 900);
<i class="dot info" data-ls-if="{{execution.status}} === 'processing'"></i>
<i class="dot success" data-ls-if="{{execution.status}} === 'completed'"></i>
</td>
<td data-title="Date: ">
<span data-ls-bind="{{execution.dateCreated|dateTime}}"></span>
</td>
<td data-title="Status: ">
<span data-ls-bind="{{execution.status}}"></span>
<span class="text-fade text-size-small" data-ls-if="{{execution.exitCode}} !== 0" data-ls-bind=" exit code: {{execution.exitCode}}"></span>
</td>
<td data-title="Date: ">
<span data-ls-bind="{{execution.dateCreated|dateTime}}"></span>
<td data-title="Trigger: ">
<span data-ls-bind="{{execution.trigger}}"></span>
</td>
<td data-title="Runtime: ">
<span data-ls-if="{{execution.status}} === 'completed' || {{execution.status}} === 'failed'" data-ls-bind="{{execution.time|seconds2hum}}"></span>
<span data-ls-if="{{execution.status}} === 'waiting' || {{execution.status}} === 'processing'">-</span>
</td>
<td>
<td data-title="">
<div data-ls-if="{{execution.status}} === 'completed' || {{execution.status}} === 'failed'" data-title="">
<button class="desktops-only pull-end link margin-start text-danger" data-ls-ui-trigger="execution-stderr-{{execution.$id}}">Errors</button>
<button class="desktops-only pull-end link margin-start" data-ls-ui-trigger="execution-stdout-{{execution.$id}}">Output</button>
<button class="phones-only tablets-only link margin-start text-danger" data-ls-ui-trigger="execution-stderr-{{execution.$id}}">Errors</button>
<button class="phones-only tablets-only link margin-start" data-ls-ui-trigger="execution-stdout-{{execution.$id}}">Output</button>
<button class="phones-only-inline tablets-only-inline link margin-end-small" data-ls-ui-trigger="execution-stdout-{{execution.$id}}">Output</button>
<button class="phones-only-inline tablets-only-inline link text-danger" data-ls-ui-trigger="execution-stderr-{{execution.$id}}">Errors</button>
<div data-ui-modal class="modal width-large box close" data-button-alias="none" data-open-event="execution-stdout-{{execution.$id}}">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>STDOUT</h1>
<div class="margin-bottom">
<input type="hidden" data-ls-bind="{{execution.stdout}}" data-forms-code />
<div class="margin-bottom ide" data-ls-if="({{execution.stdout.length}})">
<pre data-ls-bind="{{execution.stdout}}"></pre>
<!-- <input type="hidden" data-ls-bind="{{execution.stdout}}" data-forms-code="bash" /> -->
</div>
<div class="margin-bottom" data-ls-if="(!{{execution.stdout.length}})">
<p>No output was logged.</p>
</div>
</div>
<div data-ui-modal class="modal width-large box close" data-button-alias="none" data-open-event="execution-stderr-{{execution.$id}}">
@ -327,8 +400,13 @@ $timeout = $this->getParam('timeout', 900);
<h1>STDERR</h1>
<div class="margin-bottom">
<input type="hidden" data-ls-bind="{{execution.stderr}}" data-forms-code />
<div class="margin-bottom ide" data-ls-if="({{execution.stderr.length}})">
<pre data-ls-bind="{{execution.stderr}}"></pre>
<!-- <input type="hidden" data-ls-bind="{{execution.stderr}}" data-forms-code="bash" /> -->
</div>
<div class="margin-bottom" data-ls-if="(!{{execution.stderr.length}})">
<p>No errors were logged.</p>
</div>
</div>
</div>
@ -412,11 +490,11 @@ $timeout = $this->getParam('timeout', 900);
<div class="text-size-small text-fade margin-bottom margin-top-negative-small">Max value is <?php echo $this->escape(number_format($timeout)); ?> seconds (<?php echo $this->escape((int) ($timeout / 60)); ?> minutes)</div>
</section>
<section class="margin-bottom">
<section class="margin-bottom" data-forms-select-all>
<label for="events" class="margin-bottom">Events <span class="tooltip small" data-tooltip="Choose which events should trigger this function."><i class="icon-info-circled"></i></span></label>
<div class="row responsive thin margin-top-small">
<?php foreach ($events as $i => $event) : ?>
<div class="col span-6 text-one-liner margin-bottom text-height-large" title="<?php echo $event; ?>">
<div class="col span-6 text-one-liner margin-bottom text-height-large text-size-small" title="<?php echo $event; ?>">
<input type="checkbox" name="events" data-ls-bind="{{project-function.events}}" value="<?php echo $event; ?>" /> &nbsp; <?php echo $event; ?>
</div>
<?php if (($i + 1) % 2 === 0) : ?>

View file

@ -47,8 +47,7 @@ $environments = $this->getParam('environments', []);
<span data-ls-bind="{{function.name}}"></span>
<div class="text-fade" data-ls-if="({{function.events.length}})"><span data-ls-bind="{{function.events.length}}"></span> events assigned</div>
<div class="text-fade" data-ls-if="(!{{function.events.length}})">&nbsp;</div>
<p class="text-fade margin-bottom-no" data-ls-bind="{{function.env|envName}} {{function.env|envVersion}}"></p>
</li>
</ul>
</div>

View file

@ -13,69 +13,105 @@ $graph = $this->getParam('graph', false);
<span class="title" data-ls-bind="{{console-project.name}}">&nbsp;</span>&nbsp;&nbsp;
</h1>
<ul class="margin-top-negative-small margin-bottom clear">
<ul class="desktops-only margin-top-negative-small margin-bottom clear">
<li class="pull-start margin-end margin-bottom-small"><a data-ls-attrs="href=/console/settings?project={{router.params.project}}" class="link-animation-enabled"><i class="icon-cog"></i> &nbsp;Settings</a> &nbsp;&nbsp;</li>
<li class="pull-start margin-end margin-bottom-small"><a data-ls-attrs="href=/console/keys?project={{router.params.project}}" class="link-animation-enabled"><i class="icon-key-inv"></i> &nbsp;API Keys</a> &nbsp;&nbsp;</li>
<li class="pull-start margin-end margin-bottom-small"><a data-ls-attrs="href=/console/webhooks?project={{router.params.project}}" class="link-animation-enabled"><i class="icon-link"></i> &nbsp;Webhooks</a> &nbsp;&nbsp;</li>
<li class="pull-start margin-end margin-bottom-small"><a data-ls-attrs="href=/console/tasks?project={{router.params.project}}" class="link-animation-enabled"><i class="icon-clock"></i> &nbsp;Tasks</a> &nbsp;&nbsp;</li>
<!-- <li class="pull-end margin-start margin-bottom-small text-size-small margin-top-tiny"><a data-ls-attrs="href=/console/tasks?project={{router.params.project}}">May 2020 &nbsp; <i class="icon-down-dir"></i></a></li> -->
</ul>
<div class="margin-bottom phones-only">&nbsp;</div>
</div>
</div>
<div class="zone xl margin-top-negative-xxl">
<div class="box margin-bottom dashboard">
<div
data-service="projects.getUsage"
data-event="load"
data-name="usage"
data-param-project-id="{{router.params.project}}"
data-param-range="last30">
<div class="zone xxl margin-top-negative-xxxl">
<div class="clear margin-bottom-small margin-top-negative">
<div class="pull-end">
<?php if (!$graph) : ?>
<form class="margin-start-small inline" data-ls-if="{{usage.range}} !== '24h'"
data-service="projects.getUsage"
data-event="submit"
data-name="usage"
data-param-project-id="{{router.params.project}}"
data-param-range="24h">
<button class="tick">24h</button>
</form>
<button class="tick margin-start-small" data-ls-if="{{usage.range}} === '24h'" disabled>24h</button>
<form class="margin-start-small inline" data-ls-if="{{usage.range}} !== '30d'"
data-service="projects.getUsage"
data-event="submit"
data-name="usage"
data-param-project-id="{{router.params.project}}">
<button class="tick">30d</button>
</form>
<button class="tick margin-start-small" data-ls-if="{{usage.range}} === '30d'" disabled>30d</button>
<form class="margin-start-small inline" data-ls-if="{{usage.range}} !== '90d'"
data-service="projects.getUsage"
data-event="submit"
data-name="usage"
data-param-project-id="{{router.params.project}}"
data-param-range="90d">
<button class="tick">90d</button>
</form>
<button class="tick margin-start-small" data-ls-if="{{usage.range}} === '90d'" disabled>90d</button>
</div>
</div>
<div
data-service="projects.getUsage"
data-event="load"
data-name="usage"
data-param-project-id="{{router.params.project}}"
data-param-range="30d">
<?php if (!$graph) : ?>
<div class="box dashboard">
<div class="row responsive">
<div class="col span-9">
<div class="chart pull-end">
<div class="content" data-forms-chart="Requests=usage.requests.data"></div>
<input type="hidden" data-ls-bind="{{usage}}" data-forms-chart="Requests=usage.requests.data" />
</div>
<div class="chart-metric">
<div class="value margin-bottom-small"><span class="sum" data-ls-bind="{{usage.requests.total|statsTotal}}">N/A</span></div>
<div class="metric margin-bottom-small">Requests <span class="tooltip" data-tooltip="Total number of API requests last 30 days"><i class="icon-info-circled"></i></span></div>
<div class="range">Last 30 days</div>
</div>
</div>
<div class="col span-3">
<div class="value margin-bottom-small"><span class="sum" data-ls-bind="{{usage.network.total|humanFileSize}}" data-default="0">0</span></div>
<div class="metric margin-bottom-small">Bandwidth</div>
<div class="range">Last 30 days</div>
<!-- <div class="margin-top dev-feature">
<a href="">Full Usage Report <i class="icon-right-open"></i></a>
<!-- <div class="margin-top-large value small">
<b class="text-size-small sum small" data-ls-bind="{{usage.functions.total|statsTotal}}" data-default="0"></b>
<br />
<b>Func. Executions</b>
</div> -->
</div>
</div>
</div>
<?php endif; ?>
<hr />
<?php endif; ?>
<div>
<div class="row responsive">
<div class="col span-3">
<div class="value"><span class="sum" data-ls-bind="{{usage.documents.total|statsTotal}}" data-default="0">0</span></div>
<div class="margin-top-small"><b class="text-size-small unit">Documents</b></div>
</div>
<div class="col span-3">
<div class="value"><span class="sum" data-ls-bind="{{usage.storage.total|humanFileSize}}" data-default="0">0</span></div>
<div class="margin-top-small"><b class="text-size-small unit">Storage</b></div>
</div>
<div class="col span-3">
<div class="value"><span class="sum" data-ls-bind="{{usage.users.total}}" data-default="0">0</span></div>
<div class="margin-top-small"><b class="text-size-small unit">Users</b></div>
</div>
<div class="col span-3">
<div class="value"><span class="sum" data-ls-bind="{{usage.tasks.total}}" data-default="0">0</span></div>
<div class="margin-top-small"><b class="text-size-small unit">Tasks</b></div>
</div>
<div class="box dashboard">
<div class="row responsive">
<div class="col span-3">
<div class="value"><span class="sum" data-ls-bind="{{usage.documents.total|statsTotal}}" data-default="0">0</span></div>
<div class="margin-top-small"><b class="text-size-small unit">Documents</b></div>
</div>
<div class="col span-3">
<div class="value"><span class="sum" data-ls-bind="{{usage.storage.total|humanFileSize}}" data-default="0">0</span></div>
<div class="margin-top-small"><b class="text-size-small unit">Storage</b></div>
</div>
<div class="col span-3">
<div class="value"><span class="sum" data-ls-bind="{{usage.users.total}}" data-default="0">0</span></div>
<div class="margin-top-small"><b class="text-size-small unit">Users</b></div>
</div>
<div class="col span-3">
<div class="value"><span class="sum" data-ls-bind="{{usage.tasks.total}}" data-default="0">0</span></div>
<div class="margin-top-small"><b class="text-size-small unit">Tasks</b></div>
</div>
</div>
</div>
@ -122,22 +158,22 @@ $graph = $this->getParam('graph', false);
<div>
<div class="pull-start margin-end avatar-container">
<img src="" data-ls-attrs="src=/images/clients/{{platform.type}}.png?v=<?php echo APP_CACHE_BUSTER; ?>" class="avatar" loading="lazy" width="60" height="60" />
<img src="" data-ls-attrs="src=/images/clients/{{platform.type}}.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="Platform Logo" class="avatar" loading="lazy" width="60" height="60" />
<div data-ls-if="{{platform.type}} === 'flutter-ios'" class="corner">
<img src="" data-ls-attrs="src=/images/clients/ios.png?v=<?php echo APP_CACHE_BUSTER; ?>" class="avatar xs" loading="lazy" width="30" height="30" />
<img src="" data-ls-attrs="src=/images/clients/ios.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="iOS Logo" class="avatar xs" loading="lazy" width="30" height="30" />
</div>
<div data-ls-if="{{platform.type}} === 'flutter-android'" class="corner">
<img src="" data-ls-attrs="src=/images/clients/android.png?v=<?php echo APP_CACHE_BUSTER; ?>" class="avatar xs" loading="lazy" width="30" height="30" />
<img src="" data-ls-attrs="src=/images/clients/android.png?v=<?php echo APP_CACHE_BUSTER; ?>" alt="Android Logo" class="avatar xs" loading="lazy" width="30" height="30" />
</div>
</div>
<span data-ls-bind="{{platform.name}}"></span>
<span class="text-one-liner" data-ls-bind="{{platform.name}}"></span>
</div>
<p class="margin-bottom-no"><small data-ls-bind="{{platform.hostname}}{{platform.key}}"></small></p>
<div class="phones-only-inline tablets-only-inline margin-top-tiny">
<div class="phones-only-inline tablets-only-inline margin-top-small">
<button class="link" data-ls-ui-trigger="platform-update-{{platform.$id}}">Update</button>
<button class="link danger" data-ls-ui-trigger="platform-delete-{{platform.$id}}">Delete</button>
</div>

View file

@ -18,14 +18,14 @@ $scopes = $this->getParam('scopes', []);
data-success="trigger"
data-success-param-trigger-events="projects.listKeys">
<div data-ls-if="0 == {{console-keys.length}} || undefined == {{console-keys.length}}" class="box margin-top margin-bottom">
<div data-ls-if="0 == {{console-keys.sum}} || undefined == {{console-keys.sum}}" class="box margin-top margin-bottom">
<h3 class="margin-bottom-small text-bold">No API Keys Found</h3>
<p class="margin-bottom-no">You haven't created any API keys for your project yet.</p>
</div>
<div class="box margin-bottom" data-ls-if="0 != {{console-keys.length}}">
<ul data-ls-loop="console-keys" data-ls-as="key" class="list">
<div class="box margin-bottom" data-ls-if="0 != {{console-keys.sum}}">
<ul data-ls-loop="console-keys.keys" data-ls-as="key" class="list">
<li class="clear">
<div data-ui-modal class="modal box close" data-button-alias="none" data-open-event="key-update-{{key.$id}}">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
@ -52,19 +52,21 @@ $scopes = $this->getParam('scopes', []);
<label data-ls-attrs="for=name-{{key.$id}}">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different API keys."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" data-ls-attrs="id=name-{{key.$id}}" name="name" required autocomplete="off" data-ls-bind="{{key.name}}" maxlength="128" />
<label data-ls-attrs="for=scopes-{{key.$id}}">Scopes (<a data-ls-attrs="href={{env.HOME}}/docs/keys" target="_blank" rel="noopener">Learn more</a>)</label>
<div class="row responsive thin">
<?php foreach ($scopes as $i => $scope) : ?>
<div class="col span-6 text-one-liner margin-bottom text-height-large" title="<?php echo $scope; ?>">
<input type="checkbox" name="scopes" data-ls-bind="{{key.scopes}}" value="<?php echo $scope; ?>" /> &nbsp; <?php echo $scope; ?>
</div>
<?php if (($i + 1) % 2 === 0) : ?>
</div>
<div class="row responsive thin">
<?php endif; ?>
<?php endforeach; ?>
</div>
<section data-forms-select-all>
<label data-ls-attrs="for=scopes-{{key.$id}}">Scopes (<a data-ls-attrs="href={{env.HOME}}/docs/keys" target="_blank" rel="noopener">Learn more</a>)</label>
<div class="row responsive thin">
<?php foreach ($scopes as $i => $scope) : ?>
<div class="col span-6 text-one-liner margin-bottom text-height-large text-size-small" title="<?php echo $scope; ?>">
<input type="checkbox" name="scopes" data-ls-bind="{{key.scopes}}" value="<?php echo $scope; ?>" /> &nbsp; <?php echo $scope; ?>
</div>
<?php if (($i + 1) % 2 === 0) : ?>
</div>
<div class="row responsive thin">
<?php endif; ?>
<?php endforeach; ?>
</div>
</section>
<hr class="margin-top-no" />
@ -147,19 +149,21 @@ $scopes = $this->getParam('scopes', []);
<label for="name">Name <span class="tooltip large" data-tooltip="Choose any name that will help you distinguish between your different API keys."><i class="icon-question"></i></span></label>
<input type="text" class="full-width" id="name" name="name" required autocomplete="off" maxlength="128" />
<label for="scopes">Scopes (<a data-ls-attrs="href={{env.HOME}}/docs/keys" target="_blank" rel="noopener">Learn more</a>)</label>
<div class="row responsive thin">
<?php foreach ($scopes as $i => $scope) : ?>
<div class="col span-6 text-one-liner margin-bottom text-height-large" title="<?php echo $scope; ?>">
<input type="checkbox" name="scopes" value="<?php echo $scope; ?>" /> &nbsp; <?php echo $scope; ?>
</div>
<?php if (($i + 1) % 2 === 0) : ?>
</div>
<div class="row responsive thin">
<?php endif; ?>
<section data-forms-select-all>
<label for="scopes">Scopes (<a data-ls-attrs="href={{env.HOME}}/docs/keys" target="_blank" rel="noopener">Learn more</a>)</label>
<div class="row responsive thin">
<?php foreach ($scopes as $i => $scope) : ?>
<div class="col span-6 text-one-liner margin-bottom text-height-large text-size-small" title="<?php echo $scope; ?>">
<input type="checkbox" name="scopes" value="<?php echo $scope; ?>" /> &nbsp; <?php echo $scope; ?>
</div>
<?php if (($i + 1) % 2 === 0) : ?>
</div>
<div class="row responsive thin">
<?php endif; ?>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</div>
</section>
<hr class="margin-top-no" />

View file

@ -218,13 +218,13 @@ $customDomainsTarget = $this->getParam('customDomainsTarget', false);
data-success="trigger"
data-success-param-trigger-events="projects.listDomains">
<div data-ls-if="0 == {{console-domains.length}} || undefined == {{console-domains.length}}" class="box margin-top margin-bottom">
<div data-ls-if="0 == {{console-domains.sum}} || undefined == {{console-domains.sum}}" class="box margin-top margin-bottom">
<h3 class="margin-bottom-small text-bold">No Custom Domains Added</h3>
<p class="margin-bottom-no">You haven't created any custom domains for your project yet.</p>
</div>
<div class="box margin-bottom" data-ls-if="0 != {{console-domains.length}}">
<div class="box margin-bottom" data-ls-if="0 != {{console-domains.sum}}">
<table class="vertical">
<thead>
<tr>
@ -235,7 +235,7 @@ $customDomainsTarget = $this->getParam('customDomainsTarget', false);
<th width="40"></th>
</tr>
</thead>
<tbody data-ls-loop="console-domains" data-ls-as="domain">
<tbody data-ls-loop="console-domains.domains" data-ls-as="domain">
<tr>
<td data-title="Status">
<span class="text-size-small text-danger" data-ls-if="true !== {{domain.verification}}"><i class="icon-cancel-circled"></i> Unverified&nbsp;</span>

View file

@ -15,13 +15,13 @@
data-success="trigger"
data-success-param-trigger-events="projects.listTasks">
<div data-ls-if="0 === {{console-tasks.length}} || undefined === {{console-tasks.length}}" class="box margin-top margin-bottom">
<div data-ls-if="0 === {{console-tasks.sum}} || undefined === {{console-tasks.sum}}" class="box margin-top margin-bottom">
<h3 class="margin-bottom-small text-bold">No Tasks Found</h3>
<p class="margin-bottom-no">You haven't created any tasks for your project yet.</p>
</div>
<div class="box y-scroll margin-bottom" data-ls-if="0 != {{console-tasks.length}}">
<div class="box y-scroll margin-bottom" data-ls-if="0 != {{console-tasks.sum}}">
<table class="full vertical">
<thead>
<tr>
@ -31,7 +31,7 @@
<th width="140"></th>
</tr>
</thead>
<tbody data-ls-loop="console-tasks" data-ls-as="task" class="list">
<tbody data-ls-loop="console-tasks.tasks" data-ls-as="task" class="list">
<tr>
<td>
<div class="margin-bottom-tiny text-one-liner">

View file

@ -183,13 +183,13 @@
data-param-user-id="{{router.params.id}}"
data-event="load,users.update">
<div data-ls-if="{{sessions.length}} === 0" style="display: none" class="margin-top-xxl margin-bottom-xxl text-align-center">
<div data-ls-if="{{sessions.sessions.length}} === 0" style="display: none" class="margin-top-xxl margin-bottom-xxl text-align-center">
No sessions available.
</div>
<div data-ls-if="{{sessions.length}} !== 0" style="display: none">
<div data-ls-if="{{sessions.sessions.length}} !== 0" style="display: none">
<div class="box margin-bottom">
<ul data-ls-loop="sessions" data-ls-as="session" class="list">
<ul data-ls-loop="sessions.sessions" data-ls-as="session" class="list">
<li class="clear">
<form class="pull-end"
data-analytics-event="submit"
@ -209,13 +209,13 @@
<button class="danger">Logout</button>
</form>
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{session.client.short_name|lowercase}}?width=120&height=120&project={{env.PROJECT}},title={{session.client.name}},alt={{session.client.name}}" class="avatar trans pull-start margin-end" />
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-attrs="src={{env.API}}/avatars/browsers/{{session.clientCode|lowercase}}?width=120&height=120&project={{env.PROJECT}},title={{session.clientName}},alt={{session.clientName}}" class="avatar trans pull-start margin-end" />
<span data-ls-if="({{session.client.name}})" data-ls-bind="{{session.client.name}}"></span> <span data-ls-if="(!{{session.client.name}})">Unknown</span> <span data-ls-bind="{{session.client.version}}"></span> on <span data-ls-bind="{{session.model}}"></span> <span data-ls-if="(!{{session.OS.name}})">Unknown</span> <span data-ls-if="({{session.OS.name}})" data-ls-bind="{{session.OS.name}}"></span> <span data-ls-bind="{{session.OS.version}}"></span>
<span data-ls-bind="{{session.clientName}}"></span> <span data-ls-bind="{{session.clientVersion}}"></span> on <span data-ls-bind="{{session.deviceModel}}"></span> <span data-ls-bind="{{session.osName}}"></span> <span data-ls-bind="{{session.osVersion}}"></span>
<div class="margin-top-small">
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-attrs="src={{env.API}}/avatars/flags/{{session.geo.isoCode}}?width=80&height=80&project={{env.PROJECT}}" class="avatar xxs margin-end-small inline" />
<small data-ls-bind="{{session.ip}}"></small> / <small data-ls-bind="{{session.geo.country}}"></small>
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-if="{{session.countryCode}} !== '--'" data-ls-attrs="src={{env.API}}/avatars/flags/{{session.countryCode}}?width=80&height=80&project={{env.PROJECT}}" class="avatar xxs margin-end-small inline" />
<small data-ls-bind="{{session.ip}}"></small> / <small data-ls-bind="{{session.countryName}}"></small>
</div>
</li>
</ul>
@ -264,18 +264,18 @@
<th width="90">IP</th>
</tr>
</thead>
<tbody data-ls-loop="logs" data-ls-as="log">
<tbody data-ls-loop="logs.logs" data-ls-as="log">
<tr>
<td data-title="Date: "><span data-ls-bind="{{log.time|dateTime}}"></span></td>
<td data-title="Event: "><span data-ls-bind="{{log.event}}"></span></td>
<td data-title="Client: ">
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-if="({{log.client.short_name}})" data-ls-attrs="src={{env.API}}/avatars/browsers/{{log.client.short_name|lowercase}}?width=80&height=80&project={{env.PROJECT}},title={{log.client.name}},alt={{log.client.name}}" class="avatar xxs inline margin-end-small" />
<span data-ls-if="({{log.client.name}})" data-ls-bind="{{log.client.name}} {{log.client.version}} on {{log.model}} {{log.OS.name}} {{log.OS.version}}"></span>
<div data-ls-if="(!{{log.client.name}})" class="text-align-center text-fade">Unknown</div>
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-if="({{log.clientCode}})" data-ls-attrs="src={{env.API}}/avatars/browsers/{{log.clientCode|lowercase}}?width=80&height=80&project={{env.PROJECT}},title={{log.clientName}},alt={{log.clientName}}" class="avatar xxs inline margin-end-small" />
<span data-ls-if="({{log.clientName}})" data-ls-bind="{{log.clientName}} {{log.clientVersion}} on {{log.model}} {{log.osName}} {{log.osVersion}}"></span>
<div data-ls-if="(!{{log.clientName}})" class="text-align-center text-fade">Unknown</div>
</td>
<td data-title="Location: ">
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-attrs="src={{env.API}}/avatars/flags/{{log.geo.isoCode}}?width=80&height=80&project={{env.PROJECT}}" class="avatar xxs inline margin-end-small" />
<span data-ls-bind="{{log.geo.country}}"></span>
<img onerror="this.onerror=null;this.src='/images/unknown.svg'" data-ls-if="{{log.countryCode}} !== '--'" data-ls-attrs="src={{env.API}}/avatars/flags/{{log.countryCode}}?width=80&height=80&project={{env.PROJECT}}" class="avatar xxs inline margin-end-small" />
<span data-ls-bind="{{log.countryName}}"></span>
</td>
<td data-title="IP: "><span data-ls-bind="{{log.ip}}"></span></td>
</tr>

View file

@ -21,14 +21,14 @@ $events = array_keys($this->getParam('events', []));
data-success="trigger"
data-success-param-trigger-events="projects.listWebhooks">
<div data-ls-if="0 == {{console-webhooks.length}} || undefined == {{console-webhooks.length}}" class="box margin-top margin-bottom">
<div data-ls-if="0 == {{console-webhooks.sum}} || undefined == {{console-webhooks.sum}}" class="box margin-top margin-bottom">
<h3 class="margin-bottom-small text-bold">No Webhooks Found</h3>
<p class="margin-bottom-no">You haven't created any webhooks for your project yet.</p>
</div>
<div class="box margin-bottom" data-ls-if="0 != {{console-webhooks.length}}">
<ul data-ls-loop="console-webhooks" data-ls-as="webhook" class="list">
<div class="box margin-bottom" data-ls-if="0 != {{console-webhooks.sum}}">
<ul data-ls-loop="console-webhooks.webhooks" data-ls-as="webhook" class="list">
<li class="clear">
<div data-ui-modal class="modal close sticky-footer" data-button-text="Update" data-button-class="pull-end">
@ -56,19 +56,21 @@ $events = array_keys($this->getParam('events', []));
<label data-ls-attrs="for=name-{{webhook.$id}}">Name</label>
<input type="text" class="full-width" data-ls-attrs="id=name-{{webhook.$id}}" name="name" required autocomplete="off" data-ls-bind="{{webhook.name}}" maxlength="128" />
<label data-ls-attrs="for=events-{{webhook.$id}}">Events</label>
<div class="row responsive thin">
<?php foreach ($events as $i => $event) : ?>
<div class="col span-6 text-one-liner margin-bottom text-height-large" title="<?php echo $event; ?>">
<input type="checkbox" name="events" data-ls-bind="{{webhook.events}}" value="<?php echo $event; ?>" /> &nbsp; <?php echo $event; ?>
</div>
<?php if (($i + 1) % 2 === 0) : ?>
</div>
<div class="row responsive thin">
<?php endif; ?>
<?php endforeach; ?>
</div>
<section data-forms-select-all>
<label data-ls-attrs="for=events-{{webhook.$id}}">Events</label>
<div class="row responsive thin">
<?php foreach ($events as $i => $event) : ?>
<div class="col span-6 text-one-liner margin-bottom text-height-large text-size-small" title="<?php echo $event; ?>">
<input type="checkbox" name="events" data-ls-bind="{{webhook.events}}" value="<?php echo $event; ?>" /> &nbsp; <?php echo $event; ?>
</div>
<?php if (($i + 1) % 2 === 0) : ?>
</div>
<div class="row responsive thin">
<?php endif; ?>
<?php endforeach; ?>
</div>
</section>
<label data-ls-attrs="for=url-{{webhook.$id}}">POST URL</label>
<input type="url" class="full-width" data-ls-attrs="id=url-{{webhook.$id}}" name="url" required autocomplete="off" placeholder="https://example.com/callback" data-ls-bind="{{webhook.url}}" />
@ -170,19 +172,21 @@ $events = array_keys($this->getParam('events', []));
<label for="name">Name</label>
<input type="text" class="full-width" id="name" name="name" required autocomplete="off" maxlength="128" />
<label for="events">Events</label>
<div class="row responsive thin">
<?php foreach ($events as $i => $event) : ?>
<div class="col span-6 text-one-liner margin-bottom text-height-large" title="<?php echo $event; ?>">
<input type="checkbox" name="events" value="<?php echo $event; ?>" /> &nbsp; <?php echo $event; ?>
</div>
<?php if (($i + 1) % 2 === 0) : ?>
</div>
<div class="row responsive thin">
<?php endif; ?>
<section data-forms-select-all>
<label for="events">Events</label>
<div class="row responsive thin">
<?php foreach ($events as $i => $event) : ?>
<div class="col span-6 text-one-liner margin-bottom text-height-large text-size-small" title="<?php echo $event; ?>">
<input type="checkbox" name="events" value="<?php echo $event; ?>" /> &nbsp; <?php echo $event; ?>
</div>
<?php if (($i + 1) % 2 === 0) : ?>
</div>
<div class="row responsive thin">
<?php endif; ?>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</div>
</section>
<label for="url">POST URL</label>
<input type="url" class="full-width" id="url" name="url" required autocomplete="off" placeholder="https://example.com/callback" />

View file

@ -1,6 +1,6 @@
<div class="zone large padding margin-top" id="message" style="display: none">
<h1 class="margin-bottom">Missing Redirect URL</h1>
<p>Your OAuth login flow is missing a redirect URL. Please check the
<p>Your OAuth login flow is missing a proper redirect URL. Please check the
<a href="https://<?php echo APP_DOMAIN; ?>/docs/client/account?sdk=web#createOAuth2Session">OAuth docs</a>
and send request for new session with a valid callback URL.</p>
</div>

View file

@ -55,6 +55,7 @@ const configApp = {
'public/scripts/views/forms/pell.js',
'public/scripts/views/forms/remove.js',
'public/scripts/views/forms/run.js',
'public/scripts/views/forms/select-all.js',
'public/scripts/views/forms/switch.js',
'public/scripts/views/forms/tags.js',
'public/scripts/views/forms/text-count.js',

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -23,6 +23,14 @@ window.ls.router
template: "/auth/join?version=" + APP_ENV.CACHEBUSTER,
scope: "home"
})
.add("/auth/oauth2/success", {
template: "/auth/oauth2/success?version=" + APP_ENV.CACHEBUSTER,
scope: "home"
})
.add("/auth/oauth2/failure", {
template: "/auth/oauth2/failure?version=" + APP_ENV.CACHEBUSTER,
scope: "home"
})
.add("/console", {
template: "/console?version=" + APP_ENV.CACHEBUSTER,
scope: "console"

View file

@ -4,87 +4,110 @@
window.ls.container.get("view").add({
selector: "data-forms-chart",
controller: function(element, container, date, document) {
let wrapper = document.createElement("div");
let child = document.createElement("canvas");
let sources = element.getAttribute('data-forms-chart');
let width = element.getAttribute('data-width') || 500;
let height = element.getAttribute('data-height') || 175;
let colors = ['#29b5d9' /* blue */, '#4eb55b' /* green */, '#fba233', /* orange */,];
let colors = (element.getAttribute('data-colors') || 'blue,green,orange,red').split(',');
let themes = {'blue': '#29b5d9', 'green': '#4eb55b', 'orange': '#fba233', 'red': '#dc3232',};
let range = {'24h': 'H:i', '7d': 'd F Y', '30d': 'd F Y', '90d': 'd F Y'}
element.parentNode.insertBefore(wrapper, element.nextSibling);
wrapper.classList.add('content');
child.width = width;
child.height = height;
let config = {
type: "line",
data: {
labels: [],
datasets: []
},
options: {
responsive: true,
title: {
display: false,
text: "Stats"
},
legend: {
display: false
},
tooltips: {
mode: "index",
intersect: false,
caretPadding: 0
},
hover: {
mode: "nearest",
intersect: true
},
scales: {
xAxes: [
{
display: false
}
],
yAxes: [
{
display: false
}
]
}
}
};
sources = sources.split(',');
for (let i = 0; i < sources.length; i++) {
let label = sources[i].substring(0, sources[i].indexOf('='));
let path = sources[i].substring(sources[i].indexOf('=') + 1);
let data = container.path(path);
wrapper.appendChild(child);
config.data.labels[i] = label;
config.data.datasets[i] = {};
config.data.datasets[i].label = label;
config.data.datasets[i].borderColor = colors[i];
config.data.datasets[i].backgroundColor = colors[i] + '36';
config.data.datasets[i].borderWidth = 2;
config.data.datasets[i].data = [0, 0, 0, 0, 0, 0, 0];
config.data.datasets[i].fill = true;
let chart = null;
if(!data) {
return;
let check = function() {
let config = {
type: "line",
data: {
labels: [],
datasets: []
},
options: {
responsive: true,
title: {
display: false,
text: "Stats"
},
legend: {
display: false
},
tooltips: {
mode: "index",
intersect: false,
caretPadding: 0
},
hover: {
mode: "nearest",
intersect: true
},
scales: {
xAxes: [
{
display: false
}
],
yAxes: [
{
display: false
}
]
}
}
};
for (let i = 0; i < sources.length; i++) {
let label = sources[i].substring(0, sources[i].indexOf('='));
let path = sources[i].substring(sources[i].indexOf('=') + 1);
let data = container.path(path);
let value = JSON.parse(element.value);
config.data.labels[i] = label;
config.data.datasets[i] = {};
config.data.datasets[i].label = label;
config.data.datasets[i].borderColor = themes[colors[i]];
config.data.datasets[i].backgroundColor = themes[colors[i]] + '36';
config.data.datasets[i].borderWidth = 2;
config.data.datasets[i].data = [0, 0, 0, 0, 0, 0, 0];
config.data.datasets[i].fill = true;
if(!data) {
return;
}
let dateFormat = (value.range && range[value.range]) ? range[value.range] : 'd F Y';
for (let x = 0; x < data.length; x++) {
config.data.datasets[i].data[x] = data[x].value;
config.data.labels[x] = date.format(dateFormat, data[x].date);
}
}
for (let x = 0; x < data.length; x++) {
config.data.datasets[i].data[x] = data[x].value;
config.data.labels[x] = date.format("d F Y", data[x].date);
if(chart) {
chart.destroy();
}
else {
}
chart = new Chart(child.getContext("2d"), config);
wrapper.dataset["canvas"] = true;
}
element.innerHTML = "";
check();
element.appendChild(child);
container.set("chart", new Chart(child.getContext("2d"), config), true);
element.dataset["canvas"] = true;
element.addEventListener('change', check);
}
});
})(window);

View file

@ -0,0 +1,51 @@
(function(window) {
"use strict";
window.ls.container.get("view").add({
selector: "data-forms-select-all",
controller: function(element) {
let select = document.createElement("button");
let unselect = document.createElement("button");
select.textContent = 'Select All';
unselect.textContent = 'Unselect All';
select.classList.add('link');
select.classList.add('margin-top-tiny');
select.classList.add('margin-start-small');
select.classList.add('text-size-small');
select.classList.add('pull-end');
unselect.classList.add('link');
unselect.classList.add('margin-top-tiny');
unselect.classList.add('margin-start-small');
unselect.classList.add('text-size-small');
unselect.classList.add('pull-end');
// select.disabled = true;
// unselect.disabled = true;
select.type = 'button';
unselect.type = 'button';
element.parentNode.insertBefore(select, element);
element.parentNode.insertBefore(unselect, element);
select.addEventListener('click', function () {
let checkboxes = document.querySelectorAll("input[type='checkbox']");
for(var i = 0; i < checkboxes.length; i++) {
checkboxes[i].checked = true;
}
})
unselect.addEventListener('click', function () {
let checkboxes = document.querySelectorAll("input[type='checkbox']");
for(var i = 0; i < checkboxes.length; i++) {
checkboxes[i].checked = false;
}
})
}
});
})(window);

View file

@ -4,10 +4,10 @@
visibility: hidden;
position: fixed;
padding: 0;
right: 0;
left: 0;
.func-end(0);
.func-start(0);
color: var(--config-color-normal);
z-index: 1002;
z-index: 4;
margin: 0 auto;
bottom: 15px;
max-width: 560px;
@ -34,42 +34,66 @@
font-weight: 600;
}
a {
border-bottom: dotted 1px var(--config-color-normal);
}
i {
cursor: pointer;
position: absolute;
font-size: 14px;
line-height: 22px;
top: 8px;
.func-start(8px);
line-height: 20px;
top: 9px;
.func-start(9px);
color: var(--config-color-background-dark);
background: var(--config-color-normal);
width: 22px;
height: 22px;
border-radius: 50%;
}
&.error {
color: #ffffff;
background: var(--config-color-danger);
color: #ffffff!important;
background: var(--config-color-danger)!important;
a {
color: #ffffff;
border-bottom: dotted 1px #ffffff;
color: #ffffff!important;
border-bottom: dotted 1px #ffffff!important;
}
i {
color: var(--config-color-danger);
background: #ffffff;
}
}
&.success {
color: #ffffff;
background: var(--config-color-success);
color: #ffffff!important;
background: var(--config-color-success)!important;
a {
color: #ffffff;
border-bottom: dotted 1px #ffffff;
}
i {
color: var(--config-color-success);
background: #ffffff;
}
}
&.warning {
color: #ffffff;
background: var(--config-color-success);
color: var(--config-color-normal)!important;
background: var(--config-color-warning)!important;
a {
color: var(--config-color-normal)!important;
border-bottom: dotted 1px var(--config-color-normal)!important;
}
i {
color: #ffffff;
border-bottom: dotted 1px #ffffff;
background: var(--config-color-normal)!important;
}
}
@ -91,7 +115,12 @@
a {
color: var(--config-color-focus);
font-weight: 400;
border-bottom: dotted 1px var(--config-color-focus);
border-bottom: dotted 1px var(--config-color-focus)!important;
}
i {
color: var(--config-color-focus-fade)!important;
background: var(--config-color-focus)!important;
}
}
@ -100,6 +129,7 @@
top: auto;
bottom: 0;
max-width: 100%;
.func-start(0);
li {
margin: 5px 0 0 0;
@ -110,4 +140,18 @@
}
}
}
}
.show-nav {
.alerts {
ul {
.func-start(220px);
}
@media @phones, @tablets {
ul {
.func-start(0);
}
}
}
}

View file

@ -11,8 +11,8 @@
top: 0;
bottom: 0;
background: #0c0c0c;
opacity: 0.5;
z-index: 4;
opacity: 0.75;
z-index: 5;
}
}

View file

@ -13,6 +13,12 @@ input:-moz-placeholder {
text-align: @config-start;
}
form {
&.inline {
display: inline-block;
}
}
input, textarea {
background: var(--config-color-background-input);
}
@ -98,6 +104,22 @@ button,
font-size: 13px;
}
&.tick {
background: var(--config-color-fade-light);
color: var(--config-color-dark);
border-radius: 20px;
padding: 0 10px;
line-height: 30px;
height: 30px;
font-size: 12px;
display: inline-block;
&.selected {
background: var(--config-color-dark);
color: var(--config-color-fade);
}
}
&.round {
width: 52px;
padding: 0;

View file

@ -143,6 +143,10 @@
margin-top: -100px!important;
}
.margin-top-negative-xxxl {
margin-top: -150px!important;
}
.margin-bottom-xxl {
margin-bottom: 140px!important;
}

View file

@ -408,10 +408,10 @@
.dashboard {
padding: 20px;
min-height: 95px;
overflow: hidden;
position: relative;
z-index: 1;
margin-bottom: 2px;
.chart {
width: 80%;
@ -422,7 +422,7 @@
}
hr {
margin: 20px -20px;
margin: 20px -25px;
height: 2px;
background: var(--config-console-background);
@ -452,7 +452,6 @@
display: block;
width: 2px;
background: var(--config-console-background);
height: ~"calc(100% + 110px)";
position: absolute;
top: -20px;
bottom: -20px;
@ -473,11 +472,20 @@
vertical-align: bottom;
line-height: 45px;
&.small {
line-height: 35px;
}
.sum {
font-size: 45px;
line-height: 45px;
font-weight: 700;
vertical-align: bottom;
&.small {
font-size: 25px;
line-height: 25px;
}
}
}
@ -578,7 +586,7 @@
vertical-align: middle;
}
&:nth-child(1) {
&:nth-child(1), &.blue {
color: #29b5d9;
&::before {
@ -586,7 +594,7 @@
}
}
&:nth-child(2) {
&:nth-child(2), &.green {
color: #4eb55b;
&::before {
@ -594,13 +602,21 @@
}
}
&:nth-child(3) {
&:nth-child(3), &.orange {
color: #ec9323;
&::before {
background: #ec9323;
}
}
&:nth-child(4), &.red {
color: #dc3232;
&::before {
background: #dc3232;
}
}
}
}

View file

@ -34,6 +34,7 @@ abstract class Scope extends TestCase
protected function getLastEmail():array
{
sleep(10);
$emails = json_decode(file_get_contents('http://maildev/email'), true);
if ($emails && is_array($emails)) {
@ -43,6 +44,16 @@ abstract class Scope extends TestCase
return [];
}
protected function getLastRequest():array
{
sleep(10);
$resquest = json_decode(file_get_contents('http://request-catcher:5000/__last_request__'), true);
$resquest['data'] = json_decode($resquest['data'], true);
return $resquest;
}
/**
* @return array
*/

View file

@ -235,31 +235,28 @@ trait AccountBase
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertIsArray($response['body']);
$this->assertNotEmpty($response['body']);
$this->assertCount(1, $response['body']);
$this->assertEquals($sessionId, $response['body'][0]['$id']);
$this->assertCount(2, $response['body']);
$this->assertEquals(1, $response['body']['sum']);
$this->assertEquals($sessionId, $response['body']['sessions'][0]['$id']);
$this->assertIsArray($response['body'][0]['OS']);
$this->assertEquals('Windows', $response['body'][0]['OS']['name']);
$this->assertEquals('WIN', $response['body'][0]['OS']['short_name']);
$this->assertEquals('10', $response['body'][0]['OS']['version']);
$this->assertEquals('x64', $response['body'][0]['OS']['platform']);
$this->assertEquals('Windows', $response['body']['sessions'][0]['osName']);
$this->assertEquals('WIN', $response['body']['sessions'][0]['osCode']);
$this->assertEquals('10', $response['body']['sessions'][0]['osVersion']);
$this->assertIsArray($response['body'][0]['client']);
$this->assertEquals('browser', $response['body'][0]['client']['type']);
$this->assertEquals('Chrome', $response['body'][0]['client']['name']);
$this->assertEquals('CH', $response['body'][0]['client']['short_name']); // FIXME (v1) key name should be camelcase
$this->assertEquals('70.0', $response['body'][0]['client']['version']);
$this->assertEquals('Blink', $response['body'][0]['client']['engine']);
$this->assertEquals(0, $response['body'][0]['device']);
$this->assertEquals('', $response['body'][0]['brand']);
$this->assertEquals('', $response['body'][0]['model']);
$this->assertEquals($response['body'][0]['ip'], filter_var($response['body'][0]['ip'], FILTER_VALIDATE_IP));
$this->assertEquals('browser', $response['body']['sessions'][0]['clientType']);
$this->assertEquals('Chrome', $response['body']['sessions'][0]['clientName']);
$this->assertEquals('CH', $response['body']['sessions'][0]['clientCode']);
$this->assertEquals('70.0', $response['body']['sessions'][0]['clientVersion']);
$this->assertEquals('Blink', $response['body']['sessions'][0]['clientEngine']);
$this->assertEquals('desktop', $response['body']['sessions'][0]['deviceName']);
$this->assertEquals('', $response['body']['sessions'][0]['deviceBrand']);
$this->assertEquals('', $response['body']['sessions'][0]['deviceModel']);
$this->assertEquals($response['body']['sessions'][0]['ip'], filter_var($response['body']['sessions'][0]['ip'], FILTER_VALIDATE_IP));
$this->assertIsArray($response['body'][0]['geo']);
$this->assertEquals('--', $response['body'][0]['geo']['isoCode']);
$this->assertEquals('Unknown', $response['body'][0]['geo']['country']);
$this->assertEquals('--', $response['body']['sessions'][0]['countryCode']);
$this->assertEquals('Unknown', $response['body']['sessions'][0]['countryName']);
$this->assertEquals(true, $response['body'][0]['current']);
$this->assertEquals(true, $response['body']['sessions'][0]['current']);
/**
* Test for FAILURE
@ -294,59 +291,53 @@ trait AccountBase
]));
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertIsArray($response['body']);
$this->assertNotEmpty($response['body']);
$this->assertCount(2, $response['body']);
$this->assertIsArray($response['body']['logs']);
$this->assertNotEmpty($response['body']['logs']);
$this->assertCount(2, $response['body']['logs']);
$this->assertEquals('account.sessions.create', $response['body'][0]['event']);
$this->assertEquals($response['body'][0]['ip'], filter_var($response['body'][0]['ip'], FILTER_VALIDATE_IP));
$this->assertIsNumeric($response['body'][0]['time']);
$this->assertEquals('account.sessions.create', $response['body']['logs'][0]['event']);
$this->assertEquals($response['body']['logs'][0]['ip'], filter_var($response['body']['logs'][0]['ip'], FILTER_VALIDATE_IP));
$this->assertIsNumeric($response['body']['logs'][0]['time']);
$this->assertIsArray($response['body'][0]['OS']);
$this->assertEquals('Windows', $response['body'][0]['OS']['name']);
$this->assertEquals('WIN', $response['body'][0]['OS']['short_name']);
$this->assertEquals('10', $response['body'][0]['OS']['version']);
$this->assertEquals('x64', $response['body'][0]['OS']['platform']);
$this->assertEquals('Windows', $response['body']['logs'][0]['osName']);
$this->assertEquals('WIN', $response['body']['logs'][0]['osCode']);
$this->assertEquals('10', $response['body']['logs'][0]['osVersion']);
$this->assertIsArray($response['body'][0]['client']);
$this->assertEquals('browser', $response['body'][0]['client']['type']);
$this->assertEquals('Chrome', $response['body'][0]['client']['name']);
$this->assertEquals('CH', $response['body'][0]['client']['short_name']); // FIXME (v1) key name should be camelcase
$this->assertEquals('70.0', $response['body'][0]['client']['version']);
$this->assertEquals('Blink', $response['body'][0]['client']['engine']);
$this->assertEquals(0, $response['body'][0]['device']);
$this->assertEquals('', $response['body'][0]['brand']);
$this->assertEquals('', $response['body'][0]['model']);
$this->assertEquals($response['body'][0]['ip'], filter_var($response['body'][0]['ip'], FILTER_VALIDATE_IP));
$this->assertEquals('browser', $response['body']['logs'][0]['clientType']);
$this->assertEquals('Chrome', $response['body']['logs'][0]['clientName']);
$this->assertEquals('CH', $response['body']['logs'][0]['clientCode']); // FIXME (v1) key name should be camelcase
$this->assertEquals('70.0', $response['body']['logs'][0]['clientVersion']);
$this->assertEquals('Blink', $response['body']['logs'][0]['clientEngine']);
$this->assertEquals('desktop', $response['body']['logs'][0]['deviceName']);
$this->assertEquals('', $response['body']['logs'][0]['deviceBrand']);
$this->assertEquals('', $response['body']['logs'][0]['deviceModel']);
$this->assertEquals($response['body']['logs'][0]['ip'], filter_var($response['body']['logs'][0]['ip'], FILTER_VALIDATE_IP));
$this->assertIsArray($response['body'][0]['geo']);
$this->assertEquals('--', $response['body'][0]['geo']['isoCode']);
$this->assertEquals('Unknown', $response['body'][0]['geo']['country']);
$this->assertEquals('--', $response['body']['logs'][0]['countryCode']);
$this->assertEquals('Unknown', $response['body']['logs'][0]['countryName']);
$this->assertEquals('account.create', $response['body'][1]['event']);
$this->assertEquals($response['body'][1]['ip'], filter_var($response['body'][0]['ip'], FILTER_VALIDATE_IP));
$this->assertIsNumeric($response['body'][1]['time']);
$this->assertEquals('account.create', $response['body']['logs'][1]['event']);
$this->assertEquals($response['body']['logs'][1]['ip'], filter_var($response['body']['logs'][1]['ip'], FILTER_VALIDATE_IP));
$this->assertIsNumeric($response['body']['logs'][1]['time']);
$this->assertIsArray($response['body'][1]['OS']);
$this->assertEquals('Windows', $response['body'][1]['OS']['name']);
$this->assertEquals('WIN', $response['body'][1]['OS']['short_name']);
$this->assertEquals('10', $response['body'][1]['OS']['version']);
$this->assertEquals('x64', $response['body'][1]['OS']['platform']);
$this->assertEquals('Windows', $response['body']['logs'][1]['osName']);
$this->assertEquals('WIN', $response['body']['logs'][1]['osCode']);
$this->assertEquals('10', $response['body']['logs'][1]['osVersion']);
$this->assertIsArray($response['body'][1]['client']);
$this->assertEquals('browser', $response['body'][1]['client']['type']);
$this->assertEquals('Chrome', $response['body'][1]['client']['name']);
$this->assertEquals('CH', $response['body'][1]['client']['short_name']); // FIXME (v1) key name should be camelcase
$this->assertEquals('70.0', $response['body'][1]['client']['version']);
$this->assertEquals('Blink', $response['body'][1]['client']['engine']);
$this->assertEquals(0, $response['body'][1]['device']);
$this->assertEquals('', $response['body'][1]['brand']);
$this->assertEquals('', $response['body'][1]['model']);
$this->assertEquals($response['body'][1]['ip'], filter_var($response['body'][0]['ip'], FILTER_VALIDATE_IP));
$this->assertEquals('browser', $response['body']['logs'][1]['clientType']);
$this->assertEquals('Chrome', $response['body']['logs'][1]['clientName']);
$this->assertEquals('CH', $response['body']['logs'][1]['clientCode']); // FIXME (v1) key name should be camelcase
$this->assertEquals('70.0', $response['body']['logs'][1]['clientVersion']);
$this->assertEquals('Blink', $response['body']['logs'][1]['clientEngine']);
$this->assertEquals('desktop', $response['body']['logs'][1]['deviceName']);
$this->assertEquals('', $response['body']['logs'][1]['deviceBrand']);
$this->assertEquals('', $response['body']['logs'][1]['deviceModel']);
$this->assertEquals($response['body']['logs'][1]['ip'], filter_var($response['body']['logs'][1]['ip'], FILTER_VALIDATE_IP));
$this->assertIsArray($response['body'][1]['geo']);
$this->assertEquals('--', $response['body'][1]['geo']['isoCode']);
$this->assertEquals('Unknown', $response['body'][1]['geo']['country']);
$this->assertEquals('--', $response['body']['logs'][1]['countryCode']);
$this->assertEquals('Unknown', $response['body']['logs'][1]['countryName']);
/**
* Test for FAILURE
@ -573,8 +564,8 @@ trait AccountBase
'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session,
]), [
'prefs' => [
'key1' => 'value1',
'key2' => 'value2',
'prefKey1' => 'prefValue1',
'prefKey2' => 'prefValue2',
]
]);
@ -582,8 +573,8 @@ trait AccountBase
$this->assertIsArray($response['body']);
$this->assertNotEmpty($response['body']);
$this->assertNotEmpty($response['body']);
$this->assertEquals('value1', $response['body']['key1']);
$this->assertEquals('value2', $response['body']['key2']);
$this->assertEquals('prefValue1', $response['body']['prefKey1']);
$this->assertEquals('prefValue2', $response['body']['prefKey2']);
/**
* Test for FAILURE

View file

@ -24,9 +24,9 @@ class FunctionsConsoleClientTest extends Scope
], $this->getHeaders()), [
'name' => 'Test',
'vars' => [
'key1' => 'value1',
'key2' => 'value2',
'key3' => 'value3',
'funcKey1' => 'funcValue1',
'funcKey2' => 'funcValue2',
'funcKey3' => 'funcValue3',
],
'events' => [
'account.create',

View file

@ -24,11 +24,11 @@ class FunctionsConsoleServerTest extends Scope
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'name' => 'Test',
'env' => 'node-14',
'env' => 'php-7.4',
'vars' => [
'key1' => 'value1',
'key2' => 'value2',
'key3' => 'value3',
'funcKey1' => 'funcValue1',
'funcKey2' => 'funcValue2',
'funcKey3' => 'funcValue3',
],
'events' => [
'account.create',
@ -43,14 +43,14 @@ class FunctionsConsoleServerTest extends Scope
$this->assertEquals(201, $response1['headers']['status-code']);
$this->assertNotEmpty($response1['body']['$id']);
$this->assertEquals('Test', $response1['body']['name']);
$this->assertEquals('node-14', $response1['body']['env']);
$this->assertEquals('php-7.4', $response1['body']['env']);
$this->assertIsInt($response1['body']['dateCreated']);
$this->assertIsInt($response1['body']['dateUpdated']);
$this->assertEquals('', $response1['body']['tag']);
$this->assertEquals([
'key1' => 'value1',
'key2' => 'value2',
'key3' => 'value3',
'funcKey1' => 'funcValue1',
'funcKey2' => 'funcValue2',
'funcKey3' => 'funcValue3',
], $response1['body']['vars']);
$this->assertEquals([
'account.create',
@ -192,8 +192,7 @@ class FunctionsConsoleServerTest extends Scope
$this->assertNotEmpty($tag['body']['$id']);
$this->assertIsInt($tag['body']['dateCreated']);
$this->assertEquals('php function.php', $tag['body']['command']);
$this->assertStringStartsWith('/storage/functions/app-', $tag['body']['codePath']);
$this->assertEquals(751, $tag['body']['codeSize']);
$this->assertEquals(751, $tag['body']['size']);
/**
* Test for FAILURE
@ -265,7 +264,7 @@ class FunctionsConsoleServerTest extends Scope
], $this->getHeaders()));
$this->assertEquals(200, $function['headers']['status-code']);
$this->assertEquals(751, $function['body']['codeSize']);
$this->assertEquals(751, $function['body']['size']);
/**
* Test for FAILURE
@ -308,7 +307,32 @@ class FunctionsConsoleServerTest extends Scope
$this->assertEquals('', $execution['body']['stdout']);
$this->assertEquals('', $execution['body']['stderr']);
$this->assertEquals(0, $execution['body']['time']);
// sleep(75);
// $execution = $this->client->call(Client::METHOD_GET, '/functions/'.$data['functionId'].'/executions/'.$executionId, array_merge([
// 'content-type' => 'application/json',
// 'x-appwrite-project' => $this->getProject()['$id'],
// ], $this->getHeaders()));
// $this->assertNotEmpty($execution['body']['$id']);
// $this->assertNotEmpty($execution['body']['functionId']);
// $this->assertIsInt($execution['body']['dateCreated']);
// $this->assertEquals($data['functionId'], $execution['body']['functionId']);
// $this->assertEquals('completed', $execution['body']['status']);
// $this->assertEquals(0, $execution['body']['exitCode']);
// $this->assertStringContainsString('APPWRITE_FUNCTION_ID', $execution['body']['stdout']);
// $this->assertStringContainsString('APPWRITE_FUNCTION_NAME', $execution['body']['stdout']);
// $this->assertStringContainsString('APPWRITE_FUNCTION_TAG', $execution['body']['stdout']);
// $this->assertStringContainsString('APPWRITE_FUNCTION_TRIGGER', $execution['body']['stdout']);
// $this->assertStringContainsString('APPWRITE_FUNCTION_ENV_NAME', $execution['body']['stdout']);
// $this->assertStringContainsString('APPWRITE_FUNCTION_ENV_VERSION', $execution['body']['stdout']);
// $this->assertStringContainsString('Hello World', $execution['body']['stdout']);
// $this->assertStringContainsString($execution['body']['functionId'], $execution['body']['stdout']);
// $this->assertEquals('', $execution['body']['stderr']);
// $this->assertGreaterThan(0.100, $execution['body']['time']);
// $this->assertLessThan(0.500, $execution['body']['time']);
/**
* Test for FAILURE
*/

View file

@ -2,6 +2,7 @@
namespace Tests\E2E\Services\Locale;
use Exception;
use Tests\E2E\Client;
trait LocaleBase
@ -44,8 +45,9 @@ trait LocaleBase
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertIsArray($response['body']);
$this->assertCount(194, $response['body']);
$this->assertEquals($response['body']['US'], 'United States');
$this->assertEquals(194, $response['body']['sum']);
$this->assertEquals($response['body']['countries'][0]['name'], 'Afghanistan');
$this->assertEquals($response['body']['countries'][0]['code'], 'AF');
// Test locale code change to ES
@ -57,8 +59,9 @@ trait LocaleBase
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertIsArray($response['body']);
$this->assertCount(194, $response['body']);
$this->assertEquals($response['body']['US'], 'Estados Unidos');
$this->assertEquals(194, $response['body']['sum']);
$this->assertEquals($response['body']['countries'][0]['name'], 'Afganistán');
$this->assertEquals($response['body']['countries'][0]['code'], 'AF');
/**
* Test for FAILURE
@ -78,9 +81,10 @@ trait LocaleBase
], $this->getHeaders()));
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertIsArray($response['body']);
$this->assertCount(27, $response['body']);
$this->assertEquals($response['body']['DE'], 'Germany');
$this->assertEquals(27, $response['body']['sum']);
$this->assertIsArray($response['body']['countries']);
$this->assertEquals($response['body']['countries'][0]['name'], 'Austria');
$this->assertEquals($response['body']['countries'][0]['code'], 'AT');
// Test locale code change to ES
@ -91,9 +95,11 @@ trait LocaleBase
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertIsArray($response['body']);
$this->assertCount(27, $response['body']);
$this->assertEquals($response['body']['DE'], 'Alemania');
$this->assertEquals(27, $response['body']['sum']);
$this->assertIsArray($response['body']['countries']);
$this->assertEquals($response['body']['countries'][0]['name'], 'Austria');
$this->assertEquals($response['body']['countries'][0]['code'], 'AT');
/**
* Test for FAILURE
@ -114,9 +120,11 @@ trait LocaleBase
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertIsArray($response['body']);
$this->assertCount(194, $response['body']);
$this->assertEquals($response['body']['US'], '+1');
$this->assertEquals($response['body']['IL'], '+972');
$this->assertEquals(194, $response['body']['sum']);
$this->assertIsArray($response['body']['phones']);
$this->assertEquals($response['body']['phones'][0]['code'], '+1');
$this->assertEquals($response['body']['phones'][0]['countryName'], 'United States');
$this->assertEquals($response['body']['phones'][0]['countryCode'], 'US');
/**
* Test for FAILURE
@ -136,9 +144,10 @@ trait LocaleBase
], $this->getHeaders()));
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertIsArray($response['body']);
$this->assertCount(7, $response['body']);
$this->assertEquals($response['body']['NA'], 'North America');
$this->assertEquals(7, $response['body']['sum']);
$this->assertIsArray($response['body']['continents']);
$this->assertEquals($response['body']['continents'][0]['code'], 'AF');
$this->assertEquals($response['body']['continents'][0]['name'], 'Africa');
// Test locale code change to ES
$response = $this->client->call(Client::METHOD_GET, '/locale/continents', [
@ -148,9 +157,10 @@ trait LocaleBase
]);
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertIsArray($response['body']);
$this->assertCount(7, $response['body']);
$this->assertEquals($response['body']['NA'], 'América del Norte');
$this->assertEquals(7, $response['body']['sum']);
$this->assertIsArray($response['body']['continents']);
$this->assertEquals($response['body']['continents'][0]['code'], 'NA');
$this->assertEquals($response['body']['continents'][0]['name'], 'América del Norte');
/**
@ -172,9 +182,9 @@ trait LocaleBase
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertIsArray($response['body']);
$this->assertCount(117, $response['body']);
$this->assertEquals($response['body'][0]['symbol'], '$');
$this->assertEquals($response['body'][0]['name'], 'US Dollar');
$this->assertEquals(117, $response['body']['sum']);
$this->assertEquals($response['body']['currencies'][0]['symbol'], '$');
$this->assertEquals($response['body']['currencies'][0]['name'], 'US Dollar');
/**
* Test for FAILURE
@ -195,15 +205,15 @@ trait LocaleBase
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertIsArray($response['body']);
$this->assertCount(185, $response['body']);
$this->assertEquals(185, $response['body']['sum']);
$this->assertEquals($response['body'][0]['code'], 'aa');
$this->assertEquals($response['body'][0]['name'], 'Afar');
$this->assertEquals($response['body'][0]['nativeName'], 'Afar');
$this->assertEquals($response['body']['languages'][0]['code'], 'aa');
$this->assertEquals($response['body']['languages'][0]['name'], 'Afar');
$this->assertEquals($response['body']['languages'][0]['nativeName'], 'Afar');
$this->assertEquals($response['body'][184]['code'], 'zu');
$this->assertEquals($response['body'][184]['name'], 'Zulu');
$this->assertEquals($response['body'][184]['nativeName'], 'isiZulu');
$this->assertEquals($response['body']['languages'][184]['code'], 'zu');
$this->assertEquals($response['body']['languages'][184]['name'], 'Zulu');
$this->assertEquals($response['body']['languages'][184]['nativeName'], 'isiZulu');
/**
* Test for FAILURE
@ -228,16 +238,20 @@ trait LocaleBase
'x-appwrite-locale' => $lang,
]);
foreach ($response['body'] as $i => $code) {
$this->assertArrayHasKey($i, $defaultCountries, $i . ' country should be removed from ' . $lang);
if(!\is_array($response['body']['countries'])) {
throw new Exception('Failed to itterate locale: '.$lang);
}
foreach (array_keys($defaultCountries) as $i => $code) {
$this->assertArrayHasKey($code, $response['body'], $code . ' country is missing from ' . $lang . ' (total: ' . count($response['body']) . ')');
foreach ($response['body']['countries'] as $i => $code) {
$this->assertArrayHasKey($code['code'], $defaultCountries, $code['code'] . ' country should be removed from ' . $lang);
}
// foreach (array_keys($defaultCountries) as $i => $code) {
// $this->assertArrayHasKey($code, $response['body']['countries'], $code . ' country is missing from ' . $lang . ' (total: ' . count($response['body']['countries']) . ')');
// }
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertCount(194, $response['body']);
$this->assertEquals(194, $response['body']['sum']);
$response = $this->client->call(Client::METHOD_GET, '/locale/continents', [
'content-type' => 'application/json',
@ -245,16 +259,16 @@ trait LocaleBase
'x-appwrite-locale' => $lang,
]);
foreach ($response['body'] as $i => $code) {
$this->assertArrayHasKey($i, $defaultContinents, $i . ' continent should be removed from ' . $lang);
foreach ($response['body']['continents'] as $i => $code) {
$this->assertArrayHasKey($code['code'], $defaultContinents, $code['code'] . ' continent should be removed from ' . $lang);
}
foreach (array_keys($defaultContinents) as $i => $code) {
$this->assertArrayHasKey($code, $response['body'], $code . ' continent is missing from ' . $lang . ' (total: ' . count($response['body']) . ')');
}
// foreach (array_keys($defaultContinents) as $i => $code) {
// $this->assertArrayHasKey($code, $response['body']['continents'], $code . ' continent is missing from ' . $lang . ' (total: ' . count($response['body']['continents']) . ')');
// }
$this->assertEquals($response['headers']['status-code'], 200);
$this->assertCount(7, $response['body']);
$this->assertEquals(7, $response['body']['sum']);
}
/**

View file

@ -366,7 +366,7 @@ class ProjectsConsoleClientTest extends Scope
], $this->getHeaders()), []);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(1, $response['body']);
$this->assertEquals(1, $response['body']['sum']);
/**
* Test for FAILURE
@ -589,7 +589,7 @@ class ProjectsConsoleClientTest extends Scope
], $this->getHeaders()), []);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(1, $response['body']);
$this->assertEquals(1, $response['body']['sum']);
/**
* Test for FAILURE
@ -867,7 +867,7 @@ class ProjectsConsoleClientTest extends Scope
], $this->getHeaders()), []);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(1, $response['body']);
$this->assertEquals(1, $response['body']['sum']);
/**
* Test for FAILURE
@ -1235,7 +1235,7 @@ class ProjectsConsoleClientTest extends Scope
], $this->getHeaders()), []);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(3, $response['body']);
$this->assertEquals(3, $response['body']['sum']);
/**
* Test for FAILURE
@ -1478,7 +1478,7 @@ class ProjectsConsoleClientTest extends Scope
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertIsInt($response['body']['updated']);
// $this->assertIsInt($response['body']['updated']);
$this->assertEquals('sub.example.com', $response['body']['domain']);
$this->assertEquals('com', $response['body']['tld']);
$this->assertEquals('example.com', $response['body']['registerable']);
@ -1514,7 +1514,7 @@ class ProjectsConsoleClientTest extends Scope
], $this->getHeaders()), []);
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertCount(1, $response['body']);
$this->assertEquals(1, $response['body']['sum']);
/**
* Test for FAILURE
@ -1539,7 +1539,7 @@ class ProjectsConsoleClientTest extends Scope
$this->assertEquals(200, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertEquals($domainId, $response['body']['$id']);
$this->assertIsInt($response['body']['updated']);
// $this->assertIsInt($response['body']['updated']);
$this->assertEquals('sub.example.com', $response['body']['domain']);
$this->assertEquals('com', $response['body']['tld']);
$this->assertEquals('example.com', $response['body']['registerable']);

View file

@ -24,17 +24,10 @@ trait StorageBase
$this->assertEquals($file['headers']['status-code'], 201);
$this->assertNotEmpty($file['body']['$id']);
$this->assertEquals('files', $file['body']['$collection']);
$this->assertIsInt($file['body']['dateCreated']);
$this->assertEquals('logo.png', $file['body']['name']);
$this->assertEquals('image/png', $file['body']['mimeType']);
$this->assertEquals(47218, $file['body']['sizeOriginal']);
$this->assertEquals(54944, $file['body']['sizeActual']);
$this->assertEquals('gzip', $file['body']['algorithm']);
$this->assertEquals('1', $file['body']['fileOpenSSLVersion']);
$this->assertEquals('aes-128-gcm', $file['body']['fileOpenSSLCipher']);
$this->assertNotEmpty($file['body']['fileOpenSSLTag']);
$this->assertNotEmpty($file['body']['fileOpenSSLIV']);
/**
* Test for FAILURE

View file

@ -137,14 +137,14 @@ trait UsersBase
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'prefs' => [
'key1' => 'value1',
'key2' => 'value2',
'funcKey1' => 'funcValue1',
'funcKey2' => 'funcValue2',
],
]);
$this->assertEquals($user['headers']['status-code'], 200);
$this->assertEquals($user['body']['key1'], 'value1');
$this->assertEquals($user['body']['key2'], 'value2');
$this->assertEquals($user['body']['funcKey1'], 'funcValue1');
$this->assertEquals($user['body']['funcKey2'], 'funcValue2');
/**
* Test for FAILURE

View file

@ -0,0 +1,152 @@
<?php
namespace Tests\E2E\Services\Workers;
use Tests\E2E\Client;
use Tests\E2E\Scopes\ProjectConsole;
use Tests\E2E\Scopes\ProjectCustom;
use Tests\E2E\Scopes\Scope;
use Tests\E2E\Scopes\SideClient;
use Tests\E2E\Scopes\SideServer;
class WebhooksTest extends Scope
{
use ProjectConsole;
use SideClient;
public function testCreateProject(): array
{
/**
* Test for SUCCESS
*/
$team = $this->client->call(Client::METHOD_POST, '/teams', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'name' => 'Project Test',
]);
$this->assertEquals(201, $team['headers']['status-code']);
$this->assertEquals('Project Test', $team['body']['name']);
$this->assertNotEmpty($team['body']['$id']);
$response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'name' => 'Project Test',
'teamId' => $team['body']['$id'],
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertEquals('Project Test', $response['body']['name']);
$this->assertEquals($team['body']['$id'], $response['body']['teamId']);
$this->assertArrayHasKey('platforms', $response['body']);
$this->assertArrayHasKey('webhooks', $response['body']);
$this->assertArrayHasKey('keys', $response['body']);
$this->assertArrayHasKey('tasks', $response['body']);
$projectId = $response['body']['$id'];
/**
* Test for FAILURE
*/
$response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'name' => '',
'teamId' => $team['body']['$id'],
]);
$this->assertEquals(400, $response['headers']['status-code']);
$response = $this->client->call(Client::METHOD_POST, '/projects', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'name' => 'Project Test',
]);
$this->assertEquals(400, $response['headers']['status-code']);
return ['projectId' => $projectId];
}
/**
* @depends testCreateProject
*/
public function testCreateWebhook($data): array
{
$id = (isset($data['projectId'])) ? $data['projectId'] : '';
$response = $this->client->call(Client::METHOD_POST, '/projects/'.$id.'/webhooks', array_merge([
'content-type' => 'application/json',
'x-appwrite-project' => $this->getProject()['$id'],
], $this->getHeaders()), [
'name' => 'Webhook Worker Test',
'events' => ['account.create', 'account.update.email'],
'url' => 'http://request-catcher:5000/webhook',
'security' => true,
'httpUser' => 'username',
'httpPass' => 'password',
]);
$this->assertEquals(201, $response['headers']['status-code']);
$this->assertNotEmpty($response['body']['$id']);
$this->assertContains('account.create', $response['body']['events']);
$this->assertContains('account.update.email', $response['body']['events']);
$this->assertCount(2, $response['body']['events']);
$this->assertEquals('http://request-catcher:5000/webhook', $response['body']['url']);
$this->assertIsBool($response['body']['security']);
$this->assertEquals(true, $response['body']['security']);
$this->assertEquals('username', $response['body']['httpUser']);
$data = array_merge($data, ['webhookId' => $response['body']['$id']]);
/**
* Test for FAILURE
*/
return $data;
}
/**
* @depends testCreateWebhook
*/
public function testCreateAccount($data)
{
$projectId = (isset($data['projectId'])) ? $data['projectId'] : '';
$email = uniqid().'webhook.user@localhost.test';
$password = 'password';
$name = 'User Name';
/**
* Test for SUCCESS
*/
$response = $this->client->call(Client::METHOD_POST, '/account', array_merge([
'origin' => 'http://localhost',
'content-type' => 'application/json',
'x-appwrite-project' => $projectId,
]), [
'email' => $email,
'password' => $password,
'name' => $name,
]);
$this->assertEquals($response['headers']['status-code'], 201);
$webhook = $this->getLastRequest();
$this->assertNotEmpty($webhook['data']);
$this->assertNotEmpty($webhook['data']['$id']);
$this->assertIsNumeric($webhook['data']['status']);
$this->assertIsNumeric($webhook['data']['registration']);
$this->assertEquals($webhook['data']['email'], $email);
$this->assertEquals($webhook['data']['name'], $name);
$this->assertIsBool($webhook['data']['emailVerification']);
$this->assertIsArray($webhook['data']['prefs']);
$this->assertIsArray($webhook['data']['roles']);
}
}