1
0
Fork 0
mirror of synced 2024-05-20 20:52:36 +12:00

Resorted routes for docs

This commit is contained in:
Eldad Fux 2020-02-01 00:34:07 +02:00
parent aad00aa895
commit a510bfb3c1
6 changed files with 870 additions and 871 deletions

View file

@ -41,182 +41,6 @@ $utopia->init(function() use ($providers, &$oauthKeys) {
});
$utopia->get('/v1/account')
->desc('Get Account')
->label('scope', 'account')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/account/get.md')
->label('sdk.response', ['200' => 'user'])
->action(
function () use ($response, &$user, $oauthKeys) {
$response->json(array_merge($user->getArrayCopy(array_merge(
[
'$uid',
'email',
'registration',
'name',
],
$oauthKeys
)), ['roles' => Authorization::getRoles()]));
}
);
$utopia->get('/v1/account/prefs')
->desc('Get Account Preferences')
->label('scope', 'account')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'getPrefs')
->label('sdk.description', '/docs/references/account/get-prefs.md')
->action(
function () use ($response, $user) {
$prefs = $user->getAttribute('prefs', '{}');
try {
$prefs = json_decode($prefs, true);
$prefs = ($prefs) ? $prefs : [];
} catch (\Exception $error) {
throw new Exception('Failed to parse prefs', 500);
}
$response->json($prefs);
}
);
$utopia->get('/v1/account/sessions')
->desc('Get Account Sessions')
->label('scope', 'account')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'getSessions')
->label('sdk.description', '/docs/references/account/get-sessions.md')
->action(
function () use ($response, $user) {
$tokens = $user->getAttribute('tokens', []);
$reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb');
$sessions = [];
$current = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_LOGIN, Auth::$secret);
$index = 0;
$countries = Locale::getText('countries');
foreach ($tokens as $token) { /* @var $token Document */
if (Auth::TOKEN_TYPE_LOGIN != $token->getAttribute('type')) {
continue;
}
$userAgent = (!empty($token->getAttribute('userAgent'))) ? $token->getAttribute('userAgent') : 'UNKNOWN';
$dd = new DeviceDetector($userAgent);
// OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
// $dd->skipBotDetection();
$dd->parse();
$sessions[$index] = [
'$uid' => $token->getUid(),
'OS' => $dd->getOs(),
'client' => $dd->getClient(),
'device' => $dd->getDevice(),
'brand' => $dd->getBrand(),
'model' => $dd->getModel(),
'ip' => $token->getAttribute('ip', ''),
'geo' => [],
'current' => ($current == $token->getUid()) ? true : false,
];
try {
$record = $reader->country($token->getAttribute('ip', ''));
$sessions[$index]['geo']['isoCode'] = strtolower($record->country->isoCode);
$sessions[$index]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
} catch (\Exception $e) {
$sessions[$index]['geo']['isoCode'] = '--';
$sessions[$index]['geo']['country'] = Locale::getText('locale.country.unknown');
}
++$index;
}
$response->json($sessions);
}
);
$utopia->get('/v1/account/logs')
->desc('Get Account Logs')
->label('scope', 'account')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'getLogs')
->label('sdk.description', '/docs/references/account/get-logs.md')
->action(
function () use ($response, $register, $project, $user) {
$adapter = new AuditAdapter($register->get('db'));
$adapter->setNamespace('app_'.$project->getUid());
$audit = new Audit($adapter);
$countries = Locale::getText('countries');
$logs = $audit->getLogsByUserAndActions($user->getUid(), [
'auth.register', // TODO Deprectate this
'auth.login', // TODO Deprectate this
'auth.logout', // TODO Deprectate this
'auth.recovery', // TODO Deprectate this
'auth.recovery.reset', // TODO Deprectate this
'auth.oauth.login', // TODO Deprectate this
'auth.invite', // TODO Deprectate this
'auth.join', // TODO Deprectate this
'auth.leave', // TODO Deprectate this
'account.create',
'account.delete',
'account.update.name',
'account.update.email',
'account.update.password',
'account.update.prefs',
'account.sessions.create',
'account.sessions.delete',
]);
$reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb');
$output = [];
foreach ($logs as $i => &$log) {
$log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
$dd = new DeviceDetector($log['userAgent']);
$dd->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
$dd->parse();
$output[$i] = [
'event' => $log['event'],
'ip' => $log['ip'],
'time' => strtotime($log['time']),
'OS' => $dd->getOs(),
'client' => $dd->getClient(),
'device' => $dd->getDevice(),
'brand' => $dd->getBrand(),
'model' => $dd->getModel(),
'geo' => [],
];
try {
$record = $reader->country($log['ip']);
$output[$i]['geo']['isoCode'] = strtolower($record->country->isoCode);
$output[$i]['geo']['country'] = $record->country->name;
$output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
} catch (\Exception $e) {
$output[$i]['geo']['isoCode'] = '--';
$output[$i]['geo']['country'] = Locale::getText('locale.country.unknown');
}
}
$response->json($output);
}
);
$utopia->post('/v1/account')
->desc('Create Account')
->label('webhook', 'account.create')
@ -625,6 +449,184 @@ $utopia->get('/v1/account/sessions/oauth/:provider/redirect')
}
);
$utopia->get('/v1/account')
->desc('Get Account')
->label('scope', 'account')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/account/get.md')
->label('sdk.response', ['200' => 'user'])
->action(
function () use ($response, &$user, $oauthKeys) {
$response->json(array_merge($user->getArrayCopy(array_merge(
[
'$uid',
'email',
'registration',
'name',
],
$oauthKeys
)), ['roles' => Authorization::getRoles()]));
}
);
$utopia->get('/v1/account/prefs')
->desc('Get Account Preferences')
->label('scope', 'account')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'getPrefs')
->label('sdk.description', '/docs/references/account/get-prefs.md')
->action(
function () use ($response, $user) {
$prefs = $user->getAttribute('prefs', '{}');
try {
$prefs = json_decode($prefs, true);
$prefs = ($prefs) ? $prefs : [];
} catch (\Exception $error) {
throw new Exception('Failed to parse prefs', 500);
}
$response->json($prefs);
}
);
$utopia->get('/v1/account/sessions')
->desc('Get Account Sessions')
->label('scope', 'account')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'getSessions')
->label('sdk.description', '/docs/references/account/get-sessions.md')
->action(
function () use ($response, $user) {
$tokens = $user->getAttribute('tokens', []);
$reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb');
$sessions = [];
$current = Auth::tokenVerify($tokens, Auth::TOKEN_TYPE_LOGIN, Auth::$secret);
$index = 0;
$countries = Locale::getText('countries');
foreach ($tokens as $token) { /* @var $token Document */
if (Auth::TOKEN_TYPE_LOGIN != $token->getAttribute('type')) {
continue;
}
$userAgent = (!empty($token->getAttribute('userAgent'))) ? $token->getAttribute('userAgent') : 'UNKNOWN';
$dd = new DeviceDetector($userAgent);
// OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
// $dd->skipBotDetection();
$dd->parse();
$sessions[$index] = [
'$uid' => $token->getUid(),
'OS' => $dd->getOs(),
'client' => $dd->getClient(),
'device' => $dd->getDevice(),
'brand' => $dd->getBrand(),
'model' => $dd->getModel(),
'ip' => $token->getAttribute('ip', ''),
'geo' => [],
'current' => ($current == $token->getUid()) ? true : false,
];
try {
$record = $reader->country($token->getAttribute('ip', ''));
$sessions[$index]['geo']['isoCode'] = strtolower($record->country->isoCode);
$sessions[$index]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
} catch (\Exception $e) {
$sessions[$index]['geo']['isoCode'] = '--';
$sessions[$index]['geo']['country'] = Locale::getText('locale.country.unknown');
}
++$index;
}
$response->json($sessions);
}
);
$utopia->get('/v1/account/logs')
->desc('Get Account Logs')
->label('scope', 'account')
->label('sdk.platform', [APP_PLATFORM_CLIENT])
->label('sdk.namespace', 'account')
->label('sdk.method', 'getLogs')
->label('sdk.description', '/docs/references/account/get-logs.md')
->action(
function () use ($response, $register, $project, $user) {
$adapter = new AuditAdapter($register->get('db'));
$adapter->setNamespace('app_'.$project->getUid());
$audit = new Audit($adapter);
$countries = Locale::getText('countries');
$logs = $audit->getLogsByUserAndActions($user->getUid(), [
'auth.register', // TODO Deprectate this
'auth.login', // TODO Deprectate this
'auth.logout', // TODO Deprectate this
'auth.recovery', // TODO Deprectate this
'auth.recovery.reset', // TODO Deprectate this
'auth.oauth.login', // TODO Deprectate this
'auth.invite', // TODO Deprectate this
'auth.join', // TODO Deprectate this
'auth.leave', // TODO Deprectate this
'account.create',
'account.delete',
'account.update.name',
'account.update.email',
'account.update.password',
'account.update.prefs',
'account.sessions.create',
'account.sessions.delete',
]);
$reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb');
$output = [];
foreach ($logs as $i => &$log) {
$log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
$dd = new DeviceDetector($log['userAgent']);
$dd->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
$dd->parse();
$output[$i] = [
'event' => $log['event'],
'ip' => $log['ip'],
'time' => strtotime($log['time']),
'OS' => $dd->getOs(),
'client' => $dd->getClient(),
'device' => $dd->getDevice(),
'brand' => $dd->getBrand(),
'model' => $dd->getModel(),
'geo' => [],
];
try {
$record = $reader->country($log['ip']);
$output[$i]['geo']['isoCode'] = strtolower($record->country->isoCode);
$output[$i]['geo']['country'] = $record->country->name;
$output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
} catch (\Exception $e) {
$output[$i]['geo']['isoCode'] = '--';
$output[$i]['geo']['country'] = Locale::getText('locale.country.unknown');
}
}
$response->json($output);
}
);
$utopia->patch('/v1/account/name')
->desc('Update Account Name')
->label('webhook', 'account.update.name')

View file

@ -23,74 +23,6 @@ include_once __DIR__ . '/../shared/api.php';
$isDev = (App::ENV_TYPE_PRODUCTION !== $utopia->getEnv());
$utopia->get('/v1/database/collections')
->desc('List Collections')
->label('scope', 'collections.read')
->label('sdk.namespace', 'database')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.method', 'listCollections')
->label('sdk.description', '/docs/references/database/list-collections.md')
->param('search', '', function () { return new Text(256); }, 'Search term to filter your list results.', true)
->param('limit', 25, function () { return new Range(0, 100); }, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, function () { return new Range(0, 40000); }, 'Results offset. The default value is 0. Use this param to manage pagination.', true)
->param('orderType', 'ASC', function () { return new WhiteList(['ASC', 'DESC']); }, 'Order result by ASC or DESC order.', true)
->action(
function ($search, $limit, $offset, $orderType) use ($response, $projectDB) {
/*$vl = new Structure($projectDB);
var_dump($vl->isValid(new Document([
'$collection' => Database::SYSTEM_COLLECTION_RULES,
'$permissions' => [
'read' => ['*'],
'write' => ['*'],
],
'label' => 'Platforms',
'key' => 'platforms',
'type' => 'document',
'default' => [],
'required' => false,
'array' => true,
'options' => [Database::SYSTEM_COLLECTION_PLATFORMS],
])));
var_dump($vl->getDescription());*/
$results = $projectDB->getCollection([
'limit' => $limit,
'offset' => $offset,
'orderField' => 'name',
'orderType' => $orderType,
'orderCast' => 'string',
'search' => $search,
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_COLLECTIONS,
],
]);
$response->json(['sum' => $projectDB->getSum(), 'collections' => $results]);
}
);
$utopia->get('/v1/database/collections/:collectionId')
->desc('Get Collection')
->label('scope', 'collections.read')
->label('sdk.namespace', 'database')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.method', 'getCollection')
->label('sdk.description', '/docs/references/database/get-collection.md')
->param('collectionId', '', function () { return new UID(); }, 'Collection unique ID.')
->action(
function ($collectionId) use ($response, $projectDB) {
$collection = $projectDB->getDocument($collectionId, false);
if (empty($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
throw new Exception('Collection not found', 404);
}
$response->json($collection->getArrayCopy());
}
);
$utopia->post('/v1/database/collections')
->desc('Create Collection')
->label('webhook', 'database.collections.create')
@ -160,6 +92,74 @@ $utopia->post('/v1/database/collections')
}
);
$utopia->get('/v1/database/collections')
->desc('List Collections')
->label('scope', 'collections.read')
->label('sdk.namespace', 'database')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.method', 'listCollections')
->label('sdk.description', '/docs/references/database/list-collections.md')
->param('search', '', function () { return new Text(256); }, 'Search term to filter your list results.', true)
->param('limit', 25, function () { return new Range(0, 100); }, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, function () { return new Range(0, 40000); }, 'Results offset. The default value is 0. Use this param to manage pagination.', true)
->param('orderType', 'ASC', function () { return new WhiteList(['ASC', 'DESC']); }, 'Order result by ASC or DESC order.', true)
->action(
function ($search, $limit, $offset, $orderType) use ($response, $projectDB) {
/*$vl = new Structure($projectDB);
var_dump($vl->isValid(new Document([
'$collection' => Database::SYSTEM_COLLECTION_RULES,
'$permissions' => [
'read' => ['*'],
'write' => ['*'],
],
'label' => 'Platforms',
'key' => 'platforms',
'type' => 'document',
'default' => [],
'required' => false,
'array' => true,
'options' => [Database::SYSTEM_COLLECTION_PLATFORMS],
])));
var_dump($vl->getDescription());*/
$results = $projectDB->getCollection([
'limit' => $limit,
'offset' => $offset,
'orderField' => 'name',
'orderType' => $orderType,
'orderCast' => 'string',
'search' => $search,
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_COLLECTIONS,
],
]);
$response->json(['sum' => $projectDB->getSum(), 'collections' => $results]);
}
);
$utopia->get('/v1/database/collections/:collectionId')
->desc('Get Collection')
->label('scope', 'collections.read')
->label('sdk.namespace', 'database')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.method', 'getCollection')
->label('sdk.description', '/docs/references/database/get-collection.md')
->param('collectionId', '', function () { return new UID(); }, 'Collection unique ID.')
->action(
function ($collectionId) use ($response, $projectDB) {
$collection = $projectDB->getDocument($collectionId, false);
if (empty($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
throw new Exception('Collection not found', 404);
}
$response->json($collection->getArrayCopy());
}
);
$utopia->put('/v1/database/collections/:collectionId')
->desc('Update Collection')
->label('scope', 'collections.write')
@ -261,6 +261,113 @@ $utopia->delete('/v1/database/collections/:collectionId')
}
);
$utopia->post('/v1/database/collections/:collectionId/documents')
->desc('Create Document')
->label('webhook', 'database.documents.create')
->label('scope', 'documents.write')
->label('sdk.namespace', 'database')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.method', 'createDocument')
->label('sdk.description', '/docs/references/database/create-document.md')
->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID.')
->param('data', [], function () { return new \Utopia\Validator\Mock(); }, 'Document data as JSON string.')
->param('read', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('parentDocument', '', function () { return new UID(); }, 'Parent document unique ID. Use when you want your new document to be a child of a parent document.', true)
->param('parentProperty', '', function () { return new Key(); }, 'Parent document property name. Use when you want your new document to be a child of a parent document.', true)
->param('parentPropertyType', Document::SET_TYPE_ASSIGN, function () { return new WhiteList([Document::SET_TYPE_ASSIGN, Document::SET_TYPE_APPEND, Document::SET_TYPE_PREPEND]); }, 'Parent document property connection type. You can set this value to **assign**, **append** or **prepend**, default value is assign. Use when you want your new document to be a child of a parent document.', true)
->action(
function ($collectionId, $data, $read, $write, $parentDocument, $parentProperty, $parentPropertyType) use ($response, $projectDB, $webhook, $audit) {
$data = (is_string($data) && $result = json_decode($data, true)) ? $result : $data; // Cast to JSON array
if (empty($data)) {
throw new Exception('Missing payload', 400);
}
if (isset($data['$uid'])) {
throw new Exception('$uid is not allowed for creating new documents, try update instead', 400);
}
$collection = $projectDB->getDocument($collectionId/*, $isDev*/);
if (is_null($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
throw new Exception('Collection not found', 404);
}
$data['$collection'] = $collectionId; // Adding this param to make API easier for developers
$data['$permissions'] = [
'read' => $read,
'write' => $write,
];
// Read parent document + validate not 404 + validate read / write permission like patch method
// Add payload to parent document property
if ((!empty($parentDocument)) && (!empty($parentProperty))) {
$parentDocument = $projectDB->getDocument($parentDocument);
if (empty($parentDocument->getArrayCopy())) { // Check empty
throw new Exception('No parent document found', 404);
}
/*
* 1. Check child has valid structure,
* 2. Check user have write permission for parent document
* 3. Assign parent data (including child) to $data
* 4. Validate the combined result has valid structure (inside $projectDB->createDocument method)
*/
$new = new Document($data);
$structure = new Structure($projectDB);
if (!$structure->isValid($new)) {
throw new Exception('Invalid data structure: '.$structure->getDescription(), 400);
}
$authorization = new Authorization($parentDocument, 'write');
if (!$authorization->isValid($new->getPermissions())) {
throw new Exception('Unauthorized action', 401);
}
$parentDocument
->setAttribute($parentProperty, $data, $parentPropertyType);
$data = $parentDocument->getArrayCopy();
}
try {
$data = $projectDB->createDocument($data);
} catch (AuthorizationException $exception) {
throw new Exception('Unauthorized action', 401);
} catch (StructureException $exception) {
throw new Exception('Bad structure. '.$exception->getMessage(), 400);
} catch (\Exception $exception) {
throw new Exception('Failed saving document to DB'.$exception->getMessage(), 500);
}
$data = $data->getArrayCopy();
$webhook
->setParam('payload', $data)
;
$audit
->setParam('event', 'database.documents.create')
->setParam('resource', 'database/document/'.$data['$uid'])
->setParam('data', $data)
;
/*
* View
*/
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($data)
;
}
);
$utopia->get('/v1/database/collections/:collectionId/documents')
->desc('List Documents')
->label('scope', 'documents.read')
@ -373,113 +480,6 @@ $utopia->get('/v1/database/collections/:collectionId/documents/:documentId')
}
);
$utopia->post('/v1/database/collections/:collectionId/documents')
->desc('Create Document')
->label('webhook', 'database.documents.create')
->label('scope', 'documents.write')
->label('sdk.namespace', 'database')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.method', 'createDocument')
->label('sdk.description', '/docs/references/database/create-document.md')
->param('collectionId', null, function () { return new UID(); }, 'Collection unique ID.')
->param('data', [], function () { return new \Utopia\Validator\Mock(); }, 'Document data as JSON string.')
->param('read', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('parentDocument', '', function () { return new UID(); }, 'Parent document unique ID. Use when you want your new document to be a child of a parent document.', true)
->param('parentProperty', '', function () { return new Key(); }, 'Parent document property name. Use when you want your new document to be a child of a parent document.', true)
->param('parentPropertyType', Document::SET_TYPE_ASSIGN, function () { return new WhiteList([Document::SET_TYPE_ASSIGN, Document::SET_TYPE_APPEND, Document::SET_TYPE_PREPEND]); }, 'Parent document property connection type. You can set this value to **assign**, **append** or **prepend**, default value is assign. Use when you want your new document to be a child of a parent document.', true)
->action(
function ($collectionId, $data, $read, $write, $parentDocument, $parentProperty, $parentPropertyType) use ($response, $projectDB, $webhook, $audit) {
$data = (is_string($data) && $result = json_decode($data, true)) ? $result : $data; // Cast to JSON array
if (empty($data)) {
throw new Exception('Missing payload', 400);
}
if (isset($data['$uid'])) {
throw new Exception('$uid is not allowed for creating new documents, try update instead', 400);
}
$collection = $projectDB->getDocument($collectionId/*, $isDev*/);
if (is_null($collection->getUid()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
throw new Exception('Collection not found', 404);
}
$data['$collection'] = $collectionId; // Adding this param to make API easier for developers
$data['$permissions'] = [
'read' => $read,
'write' => $write,
];
// Read parent document + validate not 404 + validate read / write permission like patch method
// Add payload to parent document property
if ((!empty($parentDocument)) && (!empty($parentProperty))) {
$parentDocument = $projectDB->getDocument($parentDocument);
if (empty($parentDocument->getArrayCopy())) { // Check empty
throw new Exception('No parent document found', 404);
}
/*
* 1. Check child has valid structure,
* 2. Check user have write permission for parent document
* 3. Assign parent data (including child) to $data
* 4. Validate the combined result has valid structure (inside $projectDB->createDocument method)
*/
$new = new Document($data);
$structure = new Structure($projectDB);
if (!$structure->isValid($new)) {
throw new Exception('Invalid data structure: '.$structure->getDescription(), 400);
}
$authorization = new Authorization($parentDocument, 'write');
if (!$authorization->isValid($new->getPermissions())) {
throw new Exception('Unauthorized action', 401);
}
$parentDocument
->setAttribute($parentProperty, $data, $parentPropertyType);
$data = $parentDocument->getArrayCopy();
}
try {
$data = $projectDB->createDocument($data);
} catch (AuthorizationException $exception) {
throw new Exception('Unauthorized action', 401);
} catch (StructureException $exception) {
throw new Exception('Bad structure. '.$exception->getMessage(), 400);
} catch (\Exception $exception) {
throw new Exception('Failed saving document to DB'.$exception->getMessage(), 500);
}
$data = $data->getArrayCopy();
$webhook
->setParam('payload', $data)
;
$audit
->setParam('event', 'database.documents.create')
->setParam('resource', 'database/document/'.$data['$uid'])
->setParam('data', $data)
;
/*
* View
*/
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($data)
;
}
);
$utopia->patch('/v1/database/collections/:collectionId/documents/:documentId')
->desc('Update Document')
->label('webhook', 'database.documents.update')

View file

@ -21,6 +21,66 @@ include_once __DIR__ . '/../shared/api.php';
$scopes = include __DIR__.'/../../../app/config/scopes.php';
$utopia->post('/v1/projects')
->desc('Create Project')
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'create')
->param('name', null, function () { return new Text(100); }, 'Project name')
->param('teamId', '', function () { return new UID(); }, 'Team unique ID.')
->param('description', '', function () { return new Text(255); }, 'Project description', true)
->param('logo', '', function () { return new Text(1024); }, 'Project logo', true)
->param('url', '', function () { return new URL(); }, 'Project URL', true)
->param('legalName', '', function () { return new Text(256); }, 'Project Legal Name', true)
->param('legalCountry', '', function () { return new Text(256); }, 'Project Legal Country', true)
->param('legalState', '', function () { return new Text(256); }, 'Project Legal State', true)
->param('legalCity', '', function () { return new Text(256); }, 'Project Legal City', true)
->param('legalAddress', '', function () { return new Text(256); }, 'Project Legal Address', true)
->param('legalTaxId', '', function () { return new Text(256); }, 'Project Legal Tax ID', true)
->action(
function ($name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId) use ($response, $user, $consoleDB, $projectDB) {
$team = $projectDB->getDocument($teamId);
if (empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
throw new Exception('Team not found', 404);
}
$project = $consoleDB->createDocument(
[
'$collection' => Database::SYSTEM_COLLECTION_PROJECTS,
'$permissions' => [
'read' => ['team:'.$teamId],
'write' => ['team:'.$teamId.'/owner', 'team:'.$teamId.'/developer'],
],
'name' => $name,
'description' => $description,
'logo' => $logo,
'url' => $url,
'legalName' => $legalName,
'legalCountry' => $legalCountry,
'legalState' => $legalState,
'legalCity' => $legalCity,
'legalAddress' => $legalAddress,
'legalTaxId' => $legalTaxId,
'teamId' => $team->getUid(),
'webhooks' => [],
'keys' => [],
]
);
if (false === $project) {
throw new Exception('Failed saving project to DB', 500);
}
$consoleDB->createNamespace($project->getUid());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($project->getArrayCopy())
;
}
);
$utopia->get('/v1/projects')
->desc('List Projects')
->label('scope', 'projects.read')
@ -215,66 +275,6 @@ $utopia->get('/v1/projects/:projectId/usage')
}
);
$utopia->post('/v1/projects')
->desc('Create Project')
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'create')
->param('name', null, function () { return new Text(100); }, 'Project name')
->param('teamId', '', function () { return new UID(); }, 'Team unique ID.')
->param('description', '', function () { return new Text(255); }, 'Project description', true)
->param('logo', '', function () { return new Text(1024); }, 'Project logo', true)
->param('url', '', function () { return new URL(); }, 'Project URL', true)
->param('legalName', '', function () { return new Text(256); }, 'Project Legal Name', true)
->param('legalCountry', '', function () { return new Text(256); }, 'Project Legal Country', true)
->param('legalState', '', function () { return new Text(256); }, 'Project Legal State', true)
->param('legalCity', '', function () { return new Text(256); }, 'Project Legal City', true)
->param('legalAddress', '', function () { return new Text(256); }, 'Project Legal Address', true)
->param('legalTaxId', '', function () { return new Text(256); }, 'Project Legal Tax ID', true)
->action(
function ($name, $teamId, $description, $logo, $url, $legalName, $legalCountry, $legalState, $legalCity, $legalAddress, $legalTaxId) use ($response, $user, $consoleDB, $projectDB) {
$team = $projectDB->getDocument($teamId);
if (empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
throw new Exception('Team not found', 404);
}
$project = $consoleDB->createDocument(
[
'$collection' => Database::SYSTEM_COLLECTION_PROJECTS,
'$permissions' => [
'read' => ['team:'.$teamId],
'write' => ['team:'.$teamId.'/owner', 'team:'.$teamId.'/developer'],
],
'name' => $name,
'description' => $description,
'logo' => $logo,
'url' => $url,
'legalName' => $legalName,
'legalCountry' => $legalCountry,
'legalState' => $legalState,
'legalCity' => $legalCity,
'legalAddress' => $legalAddress,
'legalTaxId' => $legalTaxId,
'teamId' => $team->getUid(),
'webhooks' => [],
'keys' => [],
]
);
if (false === $project) {
throw new Exception('Failed saving project to DB', 500);
}
$consoleDB->createNamespace($project->getUid());
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($project->getArrayCopy())
;
}
);
$utopia->patch('/v1/projects/:projectId')
->desc('Update Project')
->label('scope', 'projects.write')
@ -400,6 +400,70 @@ $utopia->delete('/v1/projects/:projectId')
// Webhooks
$utopia->post('/v1/projects/:projectId/webhooks')
->desc('Create Webhook')
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'createWebhook')
->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
->param('name', null, function () { return new Text(256); }, 'Webhook name')
->param('events', null, function () { return new ArrayList(new Text(256)); }, 'Webhook events list')
->param('url', null, function () { return new Text(2000); }, 'Webhook URL')
->param('security', null, function () { return new Range(0, 1); }, 'Certificate verification, 0 for disabled or 1 for enabled')
->param('httpUser', '', function () { return new Text(256); }, 'Webhook HTTP user', true)
->param('httpPass', '', function () { return new Text(256); }, 'Webhook HTTP password', true)
->action(
function ($projectId, $name, $events, $url, $security, $httpUser, $httpPass) use ($request, $response, $consoleDB) {
$project = $consoleDB->getDocument($projectId);
if (empty($project->getUid()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
throw new Exception('Project not found', 404);
}
$key = $request->getServer('_APP_OPENSSL_KEY_V1');
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
$tag = null;
$httpPass = json_encode([
'data' => OpenSSL::encrypt($httpPass, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
'method' => OpenSSL::CIPHER_AES_128_GCM,
'iv' => bin2hex($iv),
'tag' => bin2hex($tag),
'version' => '1',
]);
$webhook = $consoleDB->createDocument([
'$collection' => Database::SYSTEM_COLLECTION_WEBHOOKS,
'$permissions' => [
'read' => ['team:'.$project->getAttribute('teamId', null)],
'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'],
],
'name' => $name,
'events' => $events,
'url' => $url,
'security' => (int) $security,
'httpUser' => $httpUser,
'httpPass' => $httpPass,
]);
if (false === $webhook) {
throw new Exception('Failed saving webhook to DB', 500);
}
$project->setAttribute('webhooks', $webhook, Document::SET_TYPE_APPEND);
$project = $consoleDB->updateDocument($project->getArrayCopy());
if (false === $project) {
throw new Exception('Failed saving project to DB', 500);
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($webhook->getArrayCopy())
;
}
);
$utopia->get('/v1/projects/:projectId/webhooks')
->desc('List Webhooks')
->label('scope', 'projects.read')
@ -464,69 +528,6 @@ $utopia->get('/v1/projects/:projectId/webhooks/:webhookId')
}
);
$utopia->post('/v1/projects/:projectId/webhooks')
->desc('Create Webhook')
->label('scope', 'projects.write')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'createWebhook')
->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
->param('name', null, function () { return new Text(256); }, 'Webhook name')
->param('events', null, function () { return new ArrayList(new Text(256)); }, 'Webhook events list')
->param('url', null, function () { return new Text(2000); }, 'Webhook URL')
->param('security', null, function () { return new Range(0, 1); }, 'Certificate verification, 0 for disabled or 1 for enabled')
->param('httpUser', '', function () { return new Text(256); }, 'Webhook HTTP user', true)
->param('httpPass', '', function () { return new Text(256); }, 'Webhook HTTP password', true)
->action(
function ($projectId, $name, $events, $url, $security, $httpUser, $httpPass) use ($request, $response, $consoleDB) {
$project = $consoleDB->getDocument($projectId);
if (empty($project->getUid()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
throw new Exception('Project not found', 404);
}
$key = $request->getServer('_APP_OPENSSL_KEY_V1');
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
$tag = null;
$httpPass = json_encode([
'data' => OpenSSL::encrypt($httpPass, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag),
'method' => OpenSSL::CIPHER_AES_128_GCM,
'iv' => bin2hex($iv),
'tag' => bin2hex($tag),
'version' => '1',
]);
$webhook = $consoleDB->createDocument([
'$collection' => Database::SYSTEM_COLLECTION_WEBHOOKS,
'$permissions' => [
'read' => ['team:'.$project->getAttribute('teamId', null)],
'write' => ['team:'.$project->getAttribute('teamId', null).'/owner', 'team:'.$project->getAttribute('teamId', null).'/developer'],
],
'name' => $name,
'events' => $events,
'url' => $url,
'security' => (int) $security,
'httpUser' => $httpUser,
'httpPass' => $httpPass,
]);
if (false === $webhook) {
throw new Exception('Failed saving webhook to DB', 500);
}
$project->setAttribute('webhooks', $webhook, Document::SET_TYPE_APPEND);
$project = $consoleDB->updateDocument($project->getArrayCopy());
if (false === $project) {
throw new Exception('Failed saving project to DB', 500);
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($webhook->getArrayCopy())
;
}
);
$utopia->put('/v1/projects/:projectId/webhooks/:webhookId')
->desc('Update Webhook')
@ -614,49 +615,6 @@ $utopia->delete('/v1/projects/:projectId/webhooks/:webhookId')
// Keys
$utopia->get('/v1/projects/:projectId/keys')
->desc('List Keys')
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listKeys')
->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
->action(
function ($projectId) use ($response, $consoleDB) {
$project = $consoleDB->getDocument($projectId);
if (empty($project->getUid()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
throw new Exception('Project not found', 404);
}
$response->json($project->getAttribute('keys', [])); //FIXME make sure array objects return correctly
}
);
$utopia->get('/v1/projects/:projectId/keys/:keyId')
->desc('Get Key')
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getKey')
->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
->param('keyId', null, function () { return new UID(); }, 'Key unique ID.')
->action(
function ($projectId, $keyId) use ($response, $consoleDB) {
$project = $consoleDB->getDocument($projectId);
if (empty($project->getUid()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
throw new Exception('Project not found', 404);
}
$key = $project->search('$uid', $keyId, $project->getAttribute('keys', []));
if (empty($key) && $key instanceof Document) {
throw new Exception('Key not found', 404);
}
$response->json($key->getArrayCopy());
}
);
$utopia->post('/v1/projects/:projectId/keys')
->desc('Create Key')
->label('scope', 'projects.write')
@ -703,6 +661,49 @@ $utopia->post('/v1/projects/:projectId/keys')
}
);
$utopia->get('/v1/projects/:projectId/keys')
->desc('List Keys')
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listKeys')
->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
->action(
function ($projectId) use ($response, $consoleDB) {
$project = $consoleDB->getDocument($projectId);
if (empty($project->getUid()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
throw new Exception('Project not found', 404);
}
$response->json($project->getAttribute('keys', [])); //FIXME make sure array objects return correctly
}
);
$utopia->get('/v1/projects/:projectId/keys/:keyId')
->desc('Get Key')
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getKey')
->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
->param('keyId', null, function () { return new UID(); }, 'Key unique ID.')
->action(
function ($projectId, $keyId) use ($response, $consoleDB) {
$project = $consoleDB->getDocument($projectId);
if (empty($project->getUid()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
throw new Exception('Project not found', 404);
}
$key = $project->search('$uid', $keyId, $project->getAttribute('keys', []));
if (empty($key) && $key instanceof Document) {
throw new Exception('Key not found', 404);
}
$response->json($key->getArrayCopy());
}
);
$utopia->put('/v1/projects/:projectId/keys/:keyId')
->desc('Update Key')
->label('scope', 'projects.write')
@ -770,70 +771,6 @@ $utopia->delete('/v1/projects/:projectId/keys/:keyId')
// Tasks
$utopia->get('/v1/projects/:projectId/tasks')
->desc('List Tasks')
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listTasks')
->param('projectId', '', function () { return new UID(); }, 'Project unique ID.')
->action(
function ($projectId) use ($request, $response, $consoleDB) {
$project = $consoleDB->getDocument($projectId);
if (empty($project->getUid()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
throw new Exception('Project not found', 404);
}
$tasks = $project->getAttribute('tasks', []);
foreach ($tasks as $task) { /* @var $task Document */
$httpPass = json_decode($task->getAttribute('httpPass', '{}'), true);
if (empty($httpPass) || !isset($httpPass['version'])) {
continue;
}
$key = $request->getServer('_APP_OPENSSL_KEY_V'.$httpPass['version']);
$task->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, hex2bin($httpPass['iv']), hex2bin($httpPass['tag'])));
}
$response->json($tasks);
}
);
$utopia->get('/v1/projects/:projectId/tasks/:taskId')
->desc('Get Task')
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getTask')
->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
->param('taskId', null, function () { return new UID(); }, 'Task unique ID.')
->action(
function ($projectId, $taskId) use ($request, $response, $consoleDB) {
$project = $consoleDB->getDocument($projectId);
if (empty($project->getUid()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
throw new Exception('Project not found', 404);
}
$task = $project->search('$uid', $taskId, $project->getAttribute('tasks', []));
if (empty($task) && $task instanceof Document) {
throw new Exception('Task not found', 404);
}
$httpPass = json_decode($task->getAttribute('httpPass', '{}'), true);
if (!empty($httpPass) && isset($httpPass['version'])) {
$key = $request->getServer('_APP_OPENSSL_KEY_V'.$httpPass['version']);
$task->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, hex2bin($httpPass['iv']), hex2bin($httpPass['tag'])));
}
$response->json($task->getArrayCopy());
}
);
$utopia->post('/v1/projects/:projectId/tasks')
->desc('Create Task')
->label('scope', 'projects.write')
@ -916,6 +853,70 @@ $utopia->post('/v1/projects/:projectId/tasks')
}
);
$utopia->get('/v1/projects/:projectId/tasks')
->desc('List Tasks')
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listTasks')
->param('projectId', '', function () { return new UID(); }, 'Project unique ID.')
->action(
function ($projectId) use ($request, $response, $consoleDB) {
$project = $consoleDB->getDocument($projectId);
if (empty($project->getUid()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
throw new Exception('Project not found', 404);
}
$tasks = $project->getAttribute('tasks', []);
foreach ($tasks as $task) { /* @var $task Document */
$httpPass = json_decode($task->getAttribute('httpPass', '{}'), true);
if (empty($httpPass) || !isset($httpPass['version'])) {
continue;
}
$key = $request->getServer('_APP_OPENSSL_KEY_V'.$httpPass['version']);
$task->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, hex2bin($httpPass['iv']), hex2bin($httpPass['tag'])));
}
$response->json($tasks);
}
);
$utopia->get('/v1/projects/:projectId/tasks/:taskId')
->desc('Get Task')
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getTask')
->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
->param('taskId', null, function () { return new UID(); }, 'Task unique ID.')
->action(
function ($projectId, $taskId) use ($request, $response, $consoleDB) {
$project = $consoleDB->getDocument($projectId);
if (empty($project->getUid()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
throw new Exception('Project not found', 404);
}
$task = $project->search('$uid', $taskId, $project->getAttribute('tasks', []));
if (empty($task) && $task instanceof Document) {
throw new Exception('Task not found', 404);
}
$httpPass = json_decode($task->getAttribute('httpPass', '{}'), true);
if (!empty($httpPass) && isset($httpPass['version'])) {
$key = $request->getServer('_APP_OPENSSL_KEY_V'.$httpPass['version']);
$task->setAttribute('httpPass', OpenSSL::decrypt($httpPass['data'], $httpPass['method'], $key, 0, hex2bin($httpPass['iv']), hex2bin($httpPass['tag'])));
}
$response->json($task->getArrayCopy());
}
);
$utopia->put('/v1/projects/:projectId/tasks/:taskId')
->desc('Update Task')
->label('scope', 'projects.write')
@ -1017,51 +1018,6 @@ $utopia->delete('/v1/projects/:projectId/tasks/:taskId')
// Platforms
$utopia->get('/v1/projects/:projectId/platforms')
->desc('List Platforms')
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listPlatforms')
->param('projectId', '', function () { return new UID(); }, 'Project unique ID.')
->action(
function ($projectId) use ($request, $response, $consoleDB) {
$project = $consoleDB->getDocument($projectId);
if (empty($project->getUid()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
throw new Exception('Project not found', 404);
}
$platforms = $project->getAttribute('platforms', []);
$response->json($platforms);
}
);
$utopia->get('/v1/projects/:projectId/platforms/:platformId')
->desc('Get Platform')
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getPlatform')
->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
->param('platformId', null, function () { return new UID(); }, 'Platform unique ID.')
->action(
function ($projectId, $platformId) use ($request, $response, $consoleDB) {
$project = $consoleDB->getDocument($projectId);
if (empty($project->getUid()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
throw new Exception('Project not found', 404);
}
$platform = $project->search('$uid', $platformId, $project->getAttribute('platforms', []));
if (empty($platform) && $platform instanceof Document) {
throw new Exception('Platform not found', 404);
}
$response->json($platform->getArrayCopy());
}
);
$utopia->post('/v1/projects/:projectId/platforms')
->desc('Create Platform')
->label('scope', 'projects.write')
@ -1114,6 +1070,51 @@ $utopia->post('/v1/projects/:projectId/platforms')
;
}
);
$utopia->get('/v1/projects/:projectId/platforms')
->desc('List Platforms')
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'listPlatforms')
->param('projectId', '', function () { return new UID(); }, 'Project unique ID.')
->action(
function ($projectId) use ($request, $response, $consoleDB) {
$project = $consoleDB->getDocument($projectId);
if (empty($project->getUid()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
throw new Exception('Project not found', 404);
}
$platforms = $project->getAttribute('platforms', []);
$response->json($platforms);
}
);
$utopia->get('/v1/projects/:projectId/platforms/:platformId')
->desc('Get Platform')
->label('scope', 'projects.read')
->label('sdk.namespace', 'projects')
->label('sdk.method', 'getPlatform')
->param('projectId', null, function () { return new UID(); }, 'Project unique ID.')
->param('platformId', null, function () { return new UID(); }, 'Platform unique ID.')
->action(
function ($projectId, $platformId) use ($request, $response, $consoleDB) {
$project = $consoleDB->getDocument($projectId);
if (empty($project->getUid()) || Database::SYSTEM_COLLECTION_PROJECTS != $project->getCollection()) {
throw new Exception('Project not found', 404);
}
$platform = $project->search('$uid', $platformId, $project->getAttribute('platforms', []));
if (empty($platform) && $platform instanceof Document) {
throw new Exception('Platform not found', 404);
}
$response->json($platform->getArrayCopy());
}
);
$utopia->put('/v1/projects/:projectId/platforms/:platformId')
->desc('Update Platform')

View file

@ -116,6 +116,140 @@ $mimes = [
'application/pdf',
];
$utopia->post('/v1/storage/files')
->desc('Create File')
->label('scope', 'files.write')
->label('webhook', 'storage.files.create')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'createFile')
->label('sdk.description', '/docs/references/storage/create-file.md')
->label('sdk.consumes', 'multipart/form-data')
->param('file', [], function () { return new File(); }, 'Binary Files.', false)
->param('read', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
// ->param('folderId', '', function () { return new UID(); }, 'Folder to associate files with.', true)
->action(
function ($file, $read, $write, $folderId = '') use ($request, $response, $user, $projectDB, $webhook, $audit, $usage) {
$file = $request->getFiles('file');
$read = (empty($read)) ? ['user:'.$user->getUid()] : $read;
$write = (empty($write)) ? ['user:'.$user->getUid()] : $write;
/*
* Validators
*/
//$fileType = new FileType(array(FileType::FILE_TYPE_PNG, FileType::FILE_TYPE_GIF, FileType::FILE_TYPE_JPEG));
$fileSize = new FileSize(2097152 * 2); // 4MB
$upload = new Upload();
if (empty($file)) {
throw new Exception('No file sent', 400);
}
// Make sure we handle a single file and multiple files the same way
$file['name'] = (is_array($file['name']) && isset($file['name'][0])) ? $file['name'][0] : $file['name'];
$file['tmp_name'] = (is_array($file['tmp_name']) && isset($file['tmp_name'][0])) ? $file['tmp_name'][0] : $file['tmp_name'];
$file['size'] = (is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
// Check if file type is allowed (feature for project settings?)
//if (!$fileType->isValid($file['tmp_name'])) {
//throw new Exception('File type not allowed', 400);
//}
// Check if file size is exceeding allowed limit
if (!$fileSize->isValid($file['size'])) {
throw new Exception('File size not allowed', 400);
}
$antiVirus = new Network('clamav', 3310);
/*
* Models
*/
$list = [];
$device = Storage::getDevice('local');
if (!$upload->isValid($file['tmp_name'])) {
throw new Exception('Invalid file', 403);
}
// Save to storage
$size = $device->getFileSize($file['tmp_name']);
$path = $device->getPath(uniqid().'.'.pathinfo($file['name'], PATHINFO_EXTENSION));
if (!$device->upload($file['tmp_name'], $path)) { // TODO deprecate 'upload' and replace with 'move'
throw new Exception('Failed moving file', 500);
}
$mimeType = $device->getFileMimeType($path); // Get mime-type before compression and encryption
// Check if file size is exceeding allowed limit
if (!$antiVirus->fileScan($path)) {
$device->delete($path);
throw new Exception('Invalid file', 403);
}
// Compression
$compressor = new GZIP();
$data = $device->read($path);
$data = $compressor->compress($data);
$key = $request->getServer('_APP_OPENSSL_KEY_V1');
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
$data = OpenSSL::encrypt($data, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag);
if(!$device->write($path, $data)) {
throw new Exception('Failed to save file', 500);
}
$sizeActual = $device->getFileSize($path);
$file = $projectDB->createDocument([
'$collection' => Database::SYSTEM_COLLECTION_FILES,
'$permissions' => [
'read' => $read,
'write' => $write,
],
'dateCreated' => time(),
'folderId' => $folderId,
'name' => $file['name'],
'path' => $path,
'signature' => $device->getFileHash($path),
'mimeType' => $mimeType,
'sizeOriginal' => $size,
'sizeActual' => $sizeActual,
'algorithm' => $compressor->getName(),
'token' => bin2hex(random_bytes(64)),
'comment' => '',
'fileOpenSSLVersion' => '1',
'fileOpenSSLCipher' => OpenSSL::CIPHER_AES_128_GCM,
'fileOpenSSLTag' => bin2hex($tag),
'fileOpenSSLIV' => bin2hex($iv),
]);
if (false === $file) {
throw new Exception('Failed saving file to DB', 500);
}
$webhook
->setParam('payload', $file->getArrayCopy())
;
$audit
->setParam('event', 'storage.files.create')
->setParam('resource', 'storage/files/'.$file->getUid())
;
$usage
->setParam('storage', $sizeActual)
;
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($file->getArrayCopy())
;
}
);
$utopia->get('/v1/storage/files')
->desc('List Files')
->label('scope', 'files.read')
@ -403,140 +537,6 @@ $utopia->get('/v1/storage/files/:fileId/view')
}
);
$utopia->post('/v1/storage/files')
->desc('Create File')
->label('scope', 'files.write')
->label('webhook', 'storage.files.create')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'storage')
->label('sdk.method', 'createFile')
->label('sdk.description', '/docs/references/storage/create-file.md')
->label('sdk.consumes', 'multipart/form-data')
->param('file', [], function () { return new File(); }, 'Binary Files.', false)
->param('read', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with read permissions. By default no user is granted with any read permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
->param('write', [], function () { return new ArrayList(new Text(64)); }, 'An array of strings with write permissions. By default no user is granted with any write permissions. [learn more about permissions](/docs/permissions) and get a full list of available permissions.')
// ->param('folderId', '', function () { return new UID(); }, 'Folder to associate files with.', true)
->action(
function ($file, $read, $write, $folderId = '') use ($request, $response, $user, $projectDB, $webhook, $audit, $usage) {
$file = $request->getFiles('file');
$read = (empty($read)) ? ['user:'.$user->getUid()] : $read;
$write = (empty($write)) ? ['user:'.$user->getUid()] : $write;
/*
* Validators
*/
//$fileType = new FileType(array(FileType::FILE_TYPE_PNG, FileType::FILE_TYPE_GIF, FileType::FILE_TYPE_JPEG));
$fileSize = new FileSize(2097152 * 2); // 4MB
$upload = new Upload();
if (empty($file)) {
throw new Exception('No file sent', 400);
}
// Make sure we handle a single file and multiple files the same way
$file['name'] = (is_array($file['name']) && isset($file['name'][0])) ? $file['name'][0] : $file['name'];
$file['tmp_name'] = (is_array($file['tmp_name']) && isset($file['tmp_name'][0])) ? $file['tmp_name'][0] : $file['tmp_name'];
$file['size'] = (is_array($file['size']) && isset($file['size'][0])) ? $file['size'][0] : $file['size'];
// Check if file type is allowed (feature for project settings?)
//if (!$fileType->isValid($file['tmp_name'])) {
//throw new Exception('File type not allowed', 400);
//}
// Check if file size is exceeding allowed limit
if (!$fileSize->isValid($file['size'])) {
throw new Exception('File size not allowed', 400);
}
$antiVirus = new Network('clamav', 3310);
/*
* Models
*/
$list = [];
$device = Storage::getDevice('local');
if (!$upload->isValid($file['tmp_name'])) {
throw new Exception('Invalid file', 403);
}
// Save to storage
$size = $device->getFileSize($file['tmp_name']);
$path = $device->getPath(uniqid().'.'.pathinfo($file['name'], PATHINFO_EXTENSION));
if (!$device->upload($file['tmp_name'], $path)) { // TODO deprecate 'upload' and replace with 'move'
throw new Exception('Failed moving file', 500);
}
$mimeType = $device->getFileMimeType($path); // Get mime-type before compression and encryption
// Check if file size is exceeding allowed limit
if (!$antiVirus->fileScan($path)) {
$device->delete($path);
throw new Exception('Invalid file', 403);
}
// Compression
$compressor = new GZIP();
$data = $device->read($path);
$data = $compressor->compress($data);
$key = $request->getServer('_APP_OPENSSL_KEY_V1');
$iv = OpenSSL::randomPseudoBytes(OpenSSL::cipherIVLength(OpenSSL::CIPHER_AES_128_GCM));
$data = OpenSSL::encrypt($data, OpenSSL::CIPHER_AES_128_GCM, $key, 0, $iv, $tag);
if(!$device->write($path, $data)) {
throw new Exception('Failed to save file', 500);
}
$sizeActual = $device->getFileSize($path);
$file = $projectDB->createDocument([
'$collection' => Database::SYSTEM_COLLECTION_FILES,
'$permissions' => [
'read' => $read,
'write' => $write,
],
'dateCreated' => time(),
'folderId' => $folderId,
'name' => $file['name'],
'path' => $path,
'signature' => $device->getFileHash($path),
'mimeType' => $mimeType,
'sizeOriginal' => $size,
'sizeActual' => $sizeActual,
'algorithm' => $compressor->getName(),
'token' => bin2hex(random_bytes(64)),
'comment' => '',
'fileOpenSSLVersion' => '1',
'fileOpenSSLCipher' => OpenSSL::CIPHER_AES_128_GCM,
'fileOpenSSLTag' => bin2hex($tag),
'fileOpenSSLIV' => bin2hex($iv),
]);
if (false === $file) {
throw new Exception('Failed saving file to DB', 500);
}
$webhook
->setParam('payload', $file->getArrayCopy())
;
$audit
->setParam('event', 'storage.files.create')
->setParam('resource', 'storage/files/'.$file->getUid())
;
$usage
->setParam('storage', $sizeActual)
;
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->json($file->getArrayCopy())
;
}
);
$utopia->put('/v1/storage/files/:fileId')
->desc('Update File')
->label('scope', 'files.write')

View file

@ -20,55 +20,6 @@ use Auth\Auth;
include_once __DIR__ . '/../shared/api.php';
$utopia->get('/v1/teams')
->desc('List Teams')
->label('scope', 'teams.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'list')
->label('sdk.description', '/docs/references/teams/list-teams.md')
->param('search', '', function () { return new Text(256); }, 'Search term to filter your list results.', true)
->param('limit', 25, function () { return new Range(0, 100); }, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, function () { return new Range(0, 2000); }, 'Results offset. The default value is 0. Use this param to manage pagination.', true)
->param('orderType', 'ASC', function () { return new WhiteList(['ASC', 'DESC']); }, 'Order result by ASC or DESC order.', true)
->action(
function ($search, $limit, $offset, $orderType) use ($response, $projectDB) {
$results = $projectDB->getCollection([
'limit' => $limit,
'offset' => $offset,
'orderField' => 'dateCreated',
'orderType' => $orderType,
'orderCast' => 'int',
'search' => $search,
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_TEAMS,
],
]);
$response->json(['sum' => $projectDB->getSum(), 'teams' => $results]);
}
);
$utopia->get('/v1/teams/:teamId')
->desc('Get Team')
->label('scope', 'teams.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/teams/get-team.md')
->param('teamId', '', function () { return new UID(); }, 'Team unique ID.')
->action(
function ($teamId) use ($response, $projectDB) {
$team = $projectDB->getDocument($teamId);
if (empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
throw new Exception('Team not found', 404);
}
$response->json($team->getArrayCopy([]));
}
);
$utopia->post('/v1/teams')
->desc('Create Team')
->label('scope', 'teams.write')
@ -132,6 +83,55 @@ $utopia->post('/v1/teams')
}
);
$utopia->get('/v1/teams')
->desc('List Teams')
->label('scope', 'teams.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'list')
->label('sdk.description', '/docs/references/teams/list-teams.md')
->param('search', '', function () { return new Text(256); }, 'Search term to filter your list results.', true)
->param('limit', 25, function () { return new Range(0, 100); }, 'Results limit value. By default will return maximum 25 results. Maximum of 100 results allowed per request.', true)
->param('offset', 0, function () { return new Range(0, 2000); }, 'Results offset. The default value is 0. Use this param to manage pagination.', true)
->param('orderType', 'ASC', function () { return new WhiteList(['ASC', 'DESC']); }, 'Order result by ASC or DESC order.', true)
->action(
function ($search, $limit, $offset, $orderType) use ($response, $projectDB) {
$results = $projectDB->getCollection([
'limit' => $limit,
'offset' => $offset,
'orderField' => 'dateCreated',
'orderType' => $orderType,
'orderCast' => 'int',
'search' => $search,
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_TEAMS,
],
]);
$response->json(['sum' => $projectDB->getSum(), 'teams' => $results]);
}
);
$utopia->get('/v1/teams/:teamId')
->desc('Get Team')
->label('scope', 'teams.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'get')
->label('sdk.description', '/docs/references/teams/get-team.md')
->param('teamId', '', function () { return new UID(); }, 'Team unique ID.')
->action(
function ($teamId) use ($response, $projectDB) {
$team = $projectDB->getDocument($teamId);
if (empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
throw new Exception('Team not found', 404);
}
$response->json($team->getArrayCopy([]));
}
);
$utopia->put('/v1/teams/:teamId')
->desc('Update Team')
->label('scope', 'teams.write')
@ -200,63 +200,6 @@ $utopia->delete('/v1/teams/:teamId')
}
);
$utopia->get('/v1/teams/:teamId/memberships')
->desc('Get Team Memberships')
->label('scope', 'teams.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'getMemberships')
->label('sdk.description', '/docs/references/teams/get-team-members.md')
->param('teamId', '', function () { return new UID(); }, 'Team unique ID.')
->action(
function ($teamId) use ($response, $projectDB) {
$team = $projectDB->getDocument($teamId);
if (empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
throw new Exception('Team not found', 404);
}
$memberships = $projectDB->getCollection([
'limit' => 50,
'offset' => 0,
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS,
'teamId='.$teamId,
],
]);
$users = [];
foreach ($memberships as $membership) {
if (empty($membership->getAttribute('userId', null))) {
continue;
}
$temp = $projectDB->getDocument($membership->getAttribute('userId', null))->getArrayCopy(['email', 'name']);
$users[] = array_merge($temp, $membership->getArrayCopy([
'$uid',
'userId',
'teamId',
'roles',
'invited',
'joined',
'confirm',
]));
}
usort($users, function ($a, $b) {
if ($a['joined'] === 0 || $b['joined'] === 0) {
return $b['joined'] - $a['joined'];
}
return $a['joined'] - $b['joined'];
});
$response->json($users);
}
);
$utopia->post('/v1/teams/:teamId/memberships')
->desc('Create Team Membership')
->label('scope', 'account')
@ -413,6 +356,63 @@ $utopia->post('/v1/teams/:teamId/memberships')
}
);
$utopia->get('/v1/teams/:teamId/memberships')
->desc('Get Team Memberships')
->label('scope', 'teams.read')
->label('sdk.platform', [APP_PLATFORM_CLIENT, APP_PLATFORM_SERVER])
->label('sdk.namespace', 'teams')
->label('sdk.method', 'getMemberships')
->label('sdk.description', '/docs/references/teams/get-team-members.md')
->param('teamId', '', function () { return new UID(); }, 'Team unique ID.')
->action(
function ($teamId) use ($response, $projectDB) {
$team = $projectDB->getDocument($teamId);
if (empty($team->getUid()) || Database::SYSTEM_COLLECTION_TEAMS != $team->getCollection()) {
throw new Exception('Team not found', 404);
}
$memberships = $projectDB->getCollection([
'limit' => 50,
'offset' => 0,
'filters' => [
'$collection='.Database::SYSTEM_COLLECTION_MEMBERSHIPS,
'teamId='.$teamId,
],
]);
$users = [];
foreach ($memberships as $membership) {
if (empty($membership->getAttribute('userId', null))) {
continue;
}
$temp = $projectDB->getDocument($membership->getAttribute('userId', null))->getArrayCopy(['email', 'name']);
$users[] = array_merge($temp, $membership->getArrayCopy([
'$uid',
'userId',
'teamId',
'roles',
'invited',
'joined',
'confirm',
]));
}
usort($users, function ($a, $b) {
if ($a['joined'] === 0 || $b['joined'] === 0) {
return $b['joined'] - $a['joined'];
}
return $a['joined'] - $b['joined'];
});
$response->json($users);
}
);
$utopia->patch('/v1/teams/:teamId/memberships/:inviteId/status')
->desc('Update Team Membership Status')
->label('scope', 'public')

View file

@ -1,5 +1 @@
Allow the user to login into his account by providing a valid email and password combination. Use the success and failure arguments to provide a redirect URL's back to your app when login is completed.
Please note that in order to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface.
When accessing this route using Javascript from the browser, success and failure parameter URLs are required. Appwrite server will respond with a 301 redirect status code and will set the user session cookie. This behavior is enforced because modern browsers are limiting 3rd party cookies in XHR of fetch requests to protect user privacy.
Allow the user to login into his account by providing a valid email and password combination. This route will create a new session for the user.