1
0
Fork 0
mirror of synced 2024-06-22 04:30:29 +12:00

First commit

This commit is contained in:
Eldad Fux 2020-10-30 21:53:27 +02:00
parent 64ee540086
commit e81fb88736
38 changed files with 17554 additions and 19862 deletions

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 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']);
}
}