Merge branch 'swoole-and-functions' of github.com:appwrite/appwrite into swoole-and-functions
This commit is contained in:
commit
4438be0627
14 changed files with 303 additions and 186 deletions
|
@ -32,6 +32,7 @@
|
||||||
- API Key name max length is now 128 chars and not 256 for better API consistency
|
- API Key name max length is now 128 chars and not 256 for better API consistency
|
||||||
- Task name max length is now 128 chars and not 256 for better API consistency
|
- Task name max length is now 128 chars and not 256 for better API consistency
|
||||||
- Platform name max length is now 128 chars and not 256 for better API consistency
|
- Platform name max length is now 128 chars and not 256 for better API consistency
|
||||||
|
- Webhooks payloads are now exactly the same as any of the API response objects
|
||||||
- New and consistent response format for all API object + new response examples in the docs
|
- New and consistent response format for all API object + new response examples in the docs
|
||||||
- Removed user roles attribute from user object (can be fetched from /v1/teams/memberships) **
|
- Removed user roles attribute from user object (can be fetched from /v1/teams/memberships) **
|
||||||
- Removed type attribute from session object response (used only internally)
|
- Removed type attribute from session object response (used only internally)
|
||||||
|
|
|
@ -41,7 +41,7 @@ App::post('/v1/account')
|
||||||
->param('email', '', function () { return new Email(); }, 'User email.')
|
->param('email', '', function () { return new Email(); }, 'User email.')
|
||||||
->param('password', '', function () { return new Password(); }, 'User password. Must be between 6 to 32 chars.')
|
->param('password', '', function () { return new Password(); }, 'User password. Must be between 6 to 32 chars.')
|
||||||
->param('name', '', function () { return new Text(128); }, 'User name. Max length: 128 chars.', true)
|
->param('name', '', function () { return new Text(128); }, 'User name. Max length: 128 chars.', true)
|
||||||
->action(function ($email, $password, $name, $request, $response, $project, $projectDB, $webhooks, $audits) use ($oauth2Keys) {
|
->action(function ($email, $password, $name, $request, $response, $project, $projectDB, $webhooks, $audits) {
|
||||||
/** @var Utopia\Swoole\Request $request */
|
/** @var Utopia\Swoole\Request $request */
|
||||||
/** @var Appwrite\Utopia\Response $response */
|
/** @var Appwrite\Utopia\Response $response */
|
||||||
/** @var Appwrite\Database\Document $project */
|
/** @var Appwrite\Database\Document $project */
|
||||||
|
@ -107,13 +107,6 @@ App::post('/v1/account')
|
||||||
throw new Exception('Failed saving user to DB', 500);
|
throw new Exception('Failed saving user to DB', 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
$webhooks
|
|
||||||
->setParam('payload', [
|
|
||||||
'name' => $name,
|
|
||||||
'email' => $email,
|
|
||||||
])
|
|
||||||
;
|
|
||||||
|
|
||||||
$audits
|
$audits
|
||||||
->setParam('userId', $user->getId())
|
->setParam('userId', $user->getId())
|
||||||
->setParam('event', 'account.create')
|
->setParam('event', 'account.create')
|
||||||
|
@ -238,13 +231,6 @@ App::post('/v1/account/sessions')
|
||||||
throw new Exception('Failed saving user to DB', 500);
|
throw new Exception('Failed saving user to DB', 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
$webhooks
|
|
||||||
->setParam('payload', [
|
|
||||||
'name' => $profile->getAttribute('name', ''),
|
|
||||||
'email' => $profile->getAttribute('email', ''),
|
|
||||||
])
|
|
||||||
;
|
|
||||||
|
|
||||||
$audits
|
$audits
|
||||||
->setParam('userId', $profile->getId())
|
->setParam('userId', $profile->getId())
|
||||||
->setParam('event', 'account.sessions.create')
|
->setParam('event', 'account.sessions.create')
|
||||||
|
@ -990,10 +976,7 @@ App::delete('/v1/account')
|
||||||
;
|
;
|
||||||
|
|
||||||
$webhooks
|
$webhooks
|
||||||
->setParam('payload', [
|
->setParam('payload', $response->output($user, Response::MODEL_USER))
|
||||||
'name' => $user->getAttribute('name', ''),
|
|
||||||
'email' => $user->getAttribute('email', ''),
|
|
||||||
])
|
|
||||||
;
|
;
|
||||||
|
|
||||||
if (!Config::getParam('domainVerification')) {
|
if (!Config::getParam('domainVerification')) {
|
||||||
|
@ -1048,10 +1031,7 @@ App::delete('/v1/account/sessions/:sessionId')
|
||||||
;
|
;
|
||||||
|
|
||||||
$webhooks
|
$webhooks
|
||||||
->setParam('payload', [
|
->setParam('payload', $response->output($user, Response::MODEL_USER))
|
||||||
'name' => $user->getAttribute('name', ''),
|
|
||||||
'email' => $user->getAttribute('email', ''),
|
|
||||||
])
|
|
||||||
;
|
;
|
||||||
|
|
||||||
if (!Config::getParam('domainVerification')) {
|
if (!Config::getParam('domainVerification')) {
|
||||||
|
@ -1107,10 +1087,7 @@ App::delete('/v1/account/sessions')
|
||||||
;
|
;
|
||||||
|
|
||||||
$webhooks
|
$webhooks
|
||||||
->setParam('payload', [
|
->setParam('payload', $response->output($user, Response::MODEL_USER))
|
||||||
'name' => $user->getAttribute('name', ''),
|
|
||||||
'email' => $user->getAttribute('email', ''),
|
|
||||||
])
|
|
||||||
;
|
;
|
||||||
|
|
||||||
if (!Config::getParam('domainVerification')) {
|
if (!Config::getParam('domainVerification')) {
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
use Utopia\App;
|
use Utopia\App;
|
||||||
use Utopia\Exception;
|
use Utopia\Exception;
|
||||||
use Utopia\Response;
|
|
||||||
use Utopia\Validator\Range;
|
use Utopia\Validator\Range;
|
||||||
use Utopia\Validator\WhiteList;
|
use Utopia\Validator\WhiteList;
|
||||||
use Utopia\Validator\Text;
|
use Utopia\Validator\Text;
|
||||||
|
@ -20,6 +19,7 @@ use Appwrite\Database\Validator\Collection;
|
||||||
use Appwrite\Database\Validator\Authorization;
|
use Appwrite\Database\Validator\Authorization;
|
||||||
use Appwrite\Database\Exception\Authorization as AuthorizationException;
|
use Appwrite\Database\Exception\Authorization as AuthorizationException;
|
||||||
use Appwrite\Database\Exception\Structure as StructureException;
|
use Appwrite\Database\Exception\Structure as StructureException;
|
||||||
|
use Appwrite\Utopia\Response;
|
||||||
|
|
||||||
App::post('/v1/database/collections')
|
App::post('/v1/database/collections')
|
||||||
->desc('Create Collection')
|
->desc('Create Collection')
|
||||||
|
@ -77,22 +77,14 @@ App::post('/v1/database/collections')
|
||||||
throw new Exception('Failed saving collection to DB', 500);
|
throw new Exception('Failed saving collection to DB', 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = $data->getArrayCopy();
|
|
||||||
|
|
||||||
$webhooks
|
|
||||||
->setParam('payload', $data)
|
|
||||||
;
|
|
||||||
|
|
||||||
$audits
|
$audits
|
||||||
->setParam('event', 'database.collections.create')
|
->setParam('event', 'database.collections.create')
|
||||||
->setParam('resource', 'database/collection/'.$data['$id'])
|
->setParam('resource', 'database/collection/'.$data->getId())
|
||||||
->setParam('data', $data)
|
->setParam('data', $data->getArrayCopy())
|
||||||
;
|
;
|
||||||
|
|
||||||
$response
|
$response->setStatusCode(Response::STATUS_CODE_CREATED);
|
||||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
$response->dynamic($data, Response::MODEL_COLLECTION);
|
||||||
->json($data)
|
|
||||||
;
|
|
||||||
}, ['response', 'projectDB', 'webhooks', 'audits']);
|
}, ['response', 'projectDB', 'webhooks', 'audits']);
|
||||||
|
|
||||||
App::get('/v1/database/collections')
|
App::get('/v1/database/collections')
|
||||||
|
@ -123,7 +115,10 @@ App::get('/v1/database/collections')
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$response->json(['sum' => $projectDB->getSum(), 'collections' => $results]);
|
$response->dynamic(new Document([
|
||||||
|
'sum' => $projectDB->getSum(),
|
||||||
|
'collections' => $results
|
||||||
|
]), Response::MODEL_COLLECTION_LIST);
|
||||||
}, ['response', 'projectDB']);
|
}, ['response', 'projectDB']);
|
||||||
|
|
||||||
App::get('/v1/database/collections/:collectionId')
|
App::get('/v1/database/collections/:collectionId')
|
||||||
|
@ -145,74 +140,9 @@ App::get('/v1/database/collections/:collectionId')
|
||||||
throw new Exception('Collection not found', 404);
|
throw new Exception('Collection not found', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$response->json($collection->getArrayCopy());
|
$response->dynamic($collection, Response::MODEL_COLLECTION);
|
||||||
}, ['response', 'projectDB']);
|
}, ['response', 'projectDB']);
|
||||||
|
|
||||||
// App::get('/v1/database/collections/:collectionId/logs')
|
|
||||||
// ->desc('Get Collection Logs')
|
|
||||||
// ->groups(['api', 'database'])
|
|
||||||
// ->label('scope', 'collections.read')
|
|
||||||
// ->label('sdk.platform', [APP_PLATFORM_SERVER])
|
|
||||||
// ->label('sdk.namespace', 'database')
|
|
||||||
// ->label('sdk.method', 'getCollectionLogs')
|
|
||||||
// ->label('sdk.description', '/docs/references/database/get-collection-logs.md')
|
|
||||||
// ->param('collectionId', '', function () { return new UID(); }, 'Collection unique ID.')
|
|
||||||
// ->action(
|
|
||||||
// function ($collectionId) use ($response, $register, $projectDB, $project) {
|
|
||||||
// $collection = $projectDB->getDocument($collectionId, false);
|
|
||||||
|
|
||||||
// if (empty($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) {
|
|
||||||
// throw new Exception('Collection not found', 404);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// $adapter = new AuditAdapter($register->get('db'));
|
|
||||||
// $adapter->setNamespace('app_'.$project->getId());
|
|
||||||
|
|
||||||
// $audit = new Audit($adapter);
|
|
||||||
|
|
||||||
// $countries = Locale::getText('countries');
|
|
||||||
|
|
||||||
// $logs = $audit->getLogsByResource('database/collection/'.$collection->getId());
|
|
||||||
|
|
||||||
// $reader = new Reader(__DIR__.'/../../db/DBIP/dbip-country-lite-2020-01.mmdb');
|
|
||||||
// $output = [];
|
|
||||||
|
|
||||||
// foreach ($logs as $i => &$log) {
|
|
||||||
// $log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
|
|
||||||
|
|
||||||
// $dd = new DeviceDetector($log['userAgent']);
|
|
||||||
|
|
||||||
// $dd->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
|
|
||||||
|
|
||||||
// $dd->parse();
|
|
||||||
|
|
||||||
// $output[$i] = [
|
|
||||||
// 'event' => $log['event'],
|
|
||||||
// 'ip' => $log['ip'],
|
|
||||||
// 'time' => strtotime($log['time']),
|
|
||||||
// 'OS' => $dd->getOs(),
|
|
||||||
// 'client' => $dd->getClient(),
|
|
||||||
// 'device' => $dd->getDevice(),
|
|
||||||
// 'brand' => $dd->getBrand(),
|
|
||||||
// 'model' => $dd->getModel(),
|
|
||||||
// 'geo' => [],
|
|
||||||
// ];
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// $record = $reader->country($log['ip']);
|
|
||||||
// $output[$i]['geo']['isoCode'] = strtolower($record->country->isoCode);
|
|
||||||
// $output[$i]['geo']['country'] = $record->country->name;
|
|
||||||
// $output[$i]['geo']['country'] = (isset($countries[$record->country->isoCode])) ? $countries[$record->country->isoCode] : Locale::getText('locale.country.unknown');
|
|
||||||
// } catch (\Exception $e) {
|
|
||||||
// $output[$i]['geo']['isoCode'] = '--';
|
|
||||||
// $output[$i]['geo']['country'] = Locale::getText('locale.country.unknown');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// $response->json($output);
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
|
|
||||||
App::put('/v1/database/collections/:collectionId')
|
App::put('/v1/database/collections/:collectionId')
|
||||||
->desc('Update Collection')
|
->desc('Update Collection')
|
||||||
->groups(['api', 'database'])
|
->groups(['api', 'database'])
|
||||||
|
@ -274,19 +204,13 @@ App::put('/v1/database/collections/:collectionId')
|
||||||
throw new Exception('Failed saving collection to DB', 500);
|
throw new Exception('Failed saving collection to DB', 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = $collection->getArrayCopy();
|
|
||||||
|
|
||||||
$webhooks
|
|
||||||
->setParam('payload', $data)
|
|
||||||
;
|
|
||||||
|
|
||||||
$audits
|
$audits
|
||||||
->setParam('event', 'database.collections.update')
|
->setParam('event', 'database.collections.update')
|
||||||
->setParam('resource', 'database/collections/'.$data['$id'])
|
->setParam('resource', 'database/collections/'.$collection->getId())
|
||||||
->setParam('data', $data)
|
->setParam('data', $collection->getArrayCopy())
|
||||||
;
|
;
|
||||||
|
|
||||||
$response->json($collection->getArrayCopy());
|
$response->dynamic($collection, Response::MODEL_COLLECTION);
|
||||||
}, ['response', 'projectDB', 'webhooks', 'audits']);
|
}, ['response', 'projectDB', 'webhooks', 'audits']);
|
||||||
|
|
||||||
App::delete('/v1/database/collections/:collectionId')
|
App::delete('/v1/database/collections/:collectionId')
|
||||||
|
@ -315,16 +239,14 @@ App::delete('/v1/database/collections/:collectionId')
|
||||||
throw new Exception('Failed to remove collection from DB', 500);
|
throw new Exception('Failed to remove collection from DB', 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = $collection->getArrayCopy();
|
|
||||||
|
|
||||||
$webhooks
|
$webhooks
|
||||||
->setParam('payload', $data)
|
->setParam('payload', $response->output($collection, Response::MODEL_COLLECTION))
|
||||||
;
|
;
|
||||||
|
|
||||||
$audits
|
$audits
|
||||||
->setParam('event', 'database.collections.delete')
|
->setParam('event', 'database.collections.delete')
|
||||||
->setParam('resource', 'database/collections/'.$data['$id'])
|
->setParam('resource', 'database/collections/'.$collection->getId())
|
||||||
->setParam('data', $data)
|
->setParam('data', $collection->getArrayCopy())
|
||||||
;
|
;
|
||||||
|
|
||||||
$response->noContent();
|
$response->noContent();
|
||||||
|
@ -433,22 +355,17 @@ App::post('/v1/database/collections/:collectionId/documents')
|
||||||
throw new Exception('Failed saving document to DB'.$exception->getMessage(), 500);
|
throw new Exception('Failed saving document to DB'.$exception->getMessage(), 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = $data->getArrayCopy();
|
|
||||||
|
|
||||||
$webhooks
|
|
||||||
->setParam('payload', $data)
|
|
||||||
;
|
|
||||||
|
|
||||||
$audits
|
$audits
|
||||||
->setParam('event', 'database.documents.create')
|
->setParam('event', 'database.documents.create')
|
||||||
->setParam('resource', 'database/document/'.$data['$id'])
|
->setParam('resource', 'database/document/'.$data['$id'])
|
||||||
->setParam('data', $data)
|
->setParam('data', $data->getArrayCopy())
|
||||||
;
|
;
|
||||||
|
|
||||||
$response
|
$response
|
||||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||||
->json($data)
|
|
||||||
;
|
;
|
||||||
|
|
||||||
|
$response->dynamic($data, Response::MODEL_ANY);
|
||||||
}, ['response', 'projectDB', 'webhooks', 'audits']);
|
}, ['response', 'projectDB', 'webhooks', 'audits']);
|
||||||
|
|
||||||
App::get('/v1/database/collections/:collectionId/documents')
|
App::get('/v1/database/collections/:collectionId/documents')
|
||||||
|
@ -531,27 +448,7 @@ App::get('/v1/database/collections/:collectionId/documents/:documentId')
|
||||||
throw new Exception('No document found', 404);
|
throw new Exception('No document found', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
$output = $document->getArrayCopy();
|
$response->dynamic($document, Response::MODEL_ANY);
|
||||||
|
|
||||||
$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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$response->json($output);
|
|
||||||
}, ['request', 'response', 'projectDB']);
|
}, ['request', 'response', 'projectDB']);
|
||||||
|
|
||||||
App::patch('/v1/database/collections/:collectionId/documents/:documentId')
|
App::patch('/v1/database/collections/:collectionId/documents/:documentId')
|
||||||
|
@ -620,19 +517,13 @@ App::patch('/v1/database/collections/:collectionId/documents/:documentId')
|
||||||
throw new Exception('Failed saving document to DB', 500);
|
throw new Exception('Failed saving document to DB', 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = $data->getArrayCopy();
|
|
||||||
|
|
||||||
$webhooks
|
|
||||||
->setParam('payload', $data)
|
|
||||||
;
|
|
||||||
|
|
||||||
$audits
|
$audits
|
||||||
->setParam('event', 'database.documents.update')
|
->setParam('event', 'database.documents.update')
|
||||||
->setParam('resource', 'database/document/'.$data['$id'])
|
->setParam('resource', 'database/document/'.$data->getId())
|
||||||
->setParam('data', $data)
|
->setParam('data', $data->getArrayCopy())
|
||||||
;
|
;
|
||||||
|
|
||||||
$response->json($data);
|
$response->dynamic($data, Response::MODEL_ANY);
|
||||||
}, ['response', 'projectDB', 'webhooks', 'audits']);
|
}, ['response', 'projectDB', 'webhooks', 'audits']);
|
||||||
|
|
||||||
App::delete('/v1/database/collections/:collectionId/documents/:documentId')
|
App::delete('/v1/database/collections/:collectionId/documents/:documentId')
|
||||||
|
@ -673,16 +564,14 @@ App::delete('/v1/database/collections/:collectionId/documents/:documentId')
|
||||||
throw new Exception('Failed to remove document from DB', 500);
|
throw new Exception('Failed to remove document from DB', 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = $document->getArrayCopy();
|
|
||||||
|
|
||||||
$webhooks
|
$webhooks
|
||||||
->setParam('payload', $data)
|
->setParam('payload', $response->output($document, Response::MODEL_ANY))
|
||||||
;
|
;
|
||||||
|
|
||||||
$audits
|
$audits
|
||||||
->setParam('event', 'database.documents.delete')
|
->setParam('event', 'database.documents.delete')
|
||||||
->setParam('resource', 'database/document/'.$data['$id'])
|
->setParam('resource', 'database/document/'.$document->getId())
|
||||||
->setParam('data', $data) // Audit document in case of malicious or disastrous action
|
->setParam('data', $document->getArrayCopy()) // Audit document in case of malicious or disastrous action
|
||||||
;
|
;
|
||||||
|
|
||||||
$response->noContent();
|
$response->noContent();
|
||||||
|
|
|
@ -141,10 +141,6 @@ App::post('/v1/storage/files')
|
||||||
throw new Exception('Failed saving file to DB', 500);
|
throw new Exception('Failed saving file to DB', 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
$webhooks
|
|
||||||
->setParam('payload', $file->getArrayCopy())
|
|
||||||
;
|
|
||||||
|
|
||||||
$audits
|
$audits
|
||||||
->setParam('event', 'storage.files.create')
|
->setParam('event', 'storage.files.create')
|
||||||
->setParam('resource', 'storage/files/'.$file->getId())
|
->setParam('resource', 'storage/files/'.$file->getId())
|
||||||
|
@ -504,10 +500,6 @@ App::put('/v1/storage/files/:fileId')
|
||||||
throw new Exception('Failed saving file to DB', 500);
|
throw new Exception('Failed saving file to DB', 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
$webhooks
|
|
||||||
->setParam('payload', $file->getArrayCopy())
|
|
||||||
;
|
|
||||||
|
|
||||||
$audits
|
$audits
|
||||||
->setParam('event', 'storage.files.update')
|
->setParam('event', 'storage.files.update')
|
||||||
->setParam('resource', 'storage/files/'.$file->getId())
|
->setParam('resource', 'storage/files/'.$file->getId())
|
||||||
|
@ -547,10 +539,6 @@ App::delete('/v1/storage/files/:fileId')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$webhooks
|
|
||||||
->setParam('payload', $file->getArrayCopy())
|
|
||||||
;
|
|
||||||
|
|
||||||
$audits
|
$audits
|
||||||
->setParam('event', 'storage.files.delete')
|
->setParam('event', 'storage.files.delete')
|
||||||
->setParam('resource', 'storage/files/'.$file->getId())
|
->setParam('resource', 'storage/files/'.$file->getId())
|
||||||
|
|
|
@ -274,11 +274,18 @@ App::shutdown(function ($utopia, $request, $response, $project, $webhooks, $audi
|
||||||
/** @var bool $mode */
|
/** @var bool $mode */
|
||||||
|
|
||||||
if (!empty($functions->getParam('event'))) {
|
if (!empty($functions->getParam('event'))) {
|
||||||
$functions->setParam('payload', $webhooks->getParam('payload'));
|
if(empty($functions->getParam('payload'))) {
|
||||||
|
$functions->setParam('payload', $response->getPayload());
|
||||||
|
}
|
||||||
|
|
||||||
$functions->trigger();
|
$functions->trigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($webhooks->getParam('event'))) {
|
if (!empty($webhooks->getParam('event'))) {
|
||||||
|
if(empty($webhooks->getParam('payload'))) {
|
||||||
|
$webhooks->setParam('payload', $response->getPayload());
|
||||||
|
}
|
||||||
|
|
||||||
$webhooks->trigger();
|
$webhooks->trigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -136,7 +136,7 @@ $events = array_keys($this->getParam('events', []));
|
||||||
<button class="danger reverse">Delete</button>
|
<button class="danger reverse">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<span data-ls-bind="{{webhook.name}}"></span> (<span data-ls-bind="{{webhook.events.sum}}"></span> events)
|
<span data-ls-bind="{{webhook.name}}"></span> (<span data-ls-bind="{{webhook.events.length}}"></span> events)
|
||||||
<span data-ls-if="false === {{webhook.security}}">
|
<span data-ls-if="false === {{webhook.security}}">
|
||||||
<small class="text-danger">(SSL/TLS Disabled)</small>
|
<small class="text-danger">(SSL/TLS Disabled)</small>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -134,6 +134,7 @@ services:
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
- mariadb
|
- mariadb
|
||||||
|
- request-catcher
|
||||||
environment:
|
environment:
|
||||||
- _APP_ENV
|
- _APP_ENV
|
||||||
- _APP_REDIS_HOST
|
- _APP_REDIS_HOST
|
||||||
|
@ -292,7 +293,7 @@ services:
|
||||||
- MYSQL_PASSWORD=password
|
- MYSQL_PASSWORD=password
|
||||||
command: 'mysqld --innodb-flush-method=fsync' # add ' --query_cache_size=0' for DB tests
|
command: 'mysqld --innodb-flush-method=fsync' # add ' --query_cache_size=0' for DB tests
|
||||||
|
|
||||||
maildev:
|
maildev: # used mainly for dev tests
|
||||||
image: djfarrelly/maildev
|
image: djfarrelly/maildev
|
||||||
container_name: appwrite-maildev
|
container_name: appwrite-maildev
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
@ -301,6 +302,15 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- appwrite
|
- appwrite
|
||||||
|
|
||||||
|
request-catcher: # used mainly for dev tests
|
||||||
|
image: smarterdm/http-request-catcher
|
||||||
|
container_name: appwrite-request-catcher
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- '5000:5000'
|
||||||
|
networks:
|
||||||
|
- appwrite
|
||||||
|
|
||||||
# smtp:
|
# smtp:
|
||||||
# image: appwrite/smtp:1.0.1
|
# image: appwrite/smtp:1.0.1
|
||||||
# container_name: appwrite-smtp
|
# container_name: appwrite-smtp
|
||||||
|
|
|
@ -21,7 +21,7 @@ class Document extends ArrayObject
|
||||||
* @param int $flags
|
* @param int $flags
|
||||||
* @param string $iterator_class
|
* @param string $iterator_class
|
||||||
*/
|
*/
|
||||||
public function __construct($input = null, $flags = 0, $iterator_class = 'ArrayIterator')
|
public function __construct($input = [], $flags = 0, $iterator_class = 'ArrayIterator')
|
||||||
{
|
{
|
||||||
foreach ($input as $key => &$value) {
|
foreach ($input as $key => &$value) {
|
||||||
if (\is_array($value)) {
|
if (\is_array($value)) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ use Utopia\Swoole\Response as SwooleResponse;
|
||||||
use Swoole\Http\Response as SwooleHTTPResponse;
|
use Swoole\Http\Response as SwooleHTTPResponse;
|
||||||
use Appwrite\Database\Document;
|
use Appwrite\Database\Document;
|
||||||
use Appwrite\Utopia\Response\Model;
|
use Appwrite\Utopia\Response\Model;
|
||||||
|
use Appwrite\Utopia\Response\Model\Any;
|
||||||
use Appwrite\Utopia\Response\Model\BaseList;
|
use Appwrite\Utopia\Response\Model\BaseList;
|
||||||
use Appwrite\Utopia\Response\Model\Collection;
|
use Appwrite\Utopia\Response\Model\Collection;
|
||||||
use Appwrite\Utopia\Response\Model\Continent;
|
use Appwrite\Utopia\Response\Model\Continent;
|
||||||
|
@ -36,6 +37,7 @@ use Appwrite\Utopia\Response\Model\Webhook;
|
||||||
class Response extends SwooleResponse
|
class Response extends SwooleResponse
|
||||||
{
|
{
|
||||||
// General
|
// General
|
||||||
|
const MODEL_ANY = 'any';
|
||||||
const MODEL_LOG = 'log';
|
const MODEL_LOG = 'log';
|
||||||
const MODEL_LOG_LIST = 'logList';
|
const MODEL_LOG_LIST = 'logList';
|
||||||
const MODEL_ERROR = 'error';
|
const MODEL_ERROR = 'error';
|
||||||
|
@ -101,6 +103,11 @@ class Response extends SwooleResponse
|
||||||
const MODEL_DOMAIN = 'domain';
|
const MODEL_DOMAIN = 'domain';
|
||||||
const MODEL_DOMAIN_LIST = 'domainList';
|
const MODEL_DOMAIN_LIST = 'domainList';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $payload = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response constructor.
|
* Response constructor.
|
||||||
*/
|
*/
|
||||||
|
@ -111,7 +118,7 @@ class Response extends SwooleResponse
|
||||||
->setModel(new Error())
|
->setModel(new Error())
|
||||||
->setModel(new ErrorDev())
|
->setModel(new ErrorDev())
|
||||||
// Lists
|
// Lists
|
||||||
->setModel(new BaseList('Collections List', self::MODEL_COLLECTION_LIST, 'users', self::MODEL_COLLECTION))
|
->setModel(new BaseList('Collections List', self::MODEL_COLLECTION_LIST, 'collections', self::MODEL_COLLECTION))
|
||||||
->setModel(new BaseList('Users List', self::MODEL_USER_LIST, 'users', self::MODEL_USER))
|
->setModel(new BaseList('Users List', self::MODEL_USER_LIST, 'users', self::MODEL_USER))
|
||||||
->setModel(new BaseList('Sessions List', self::MODEL_SESSION_LIST, 'sessions', self::MODEL_SESSION))
|
->setModel(new BaseList('Sessions List', self::MODEL_SESSION_LIST, 'sessions', self::MODEL_SESSION))
|
||||||
->setModel(new BaseList('Logs List', self::MODEL_LOG_LIST, 'logs', self::MODEL_LOG, false))
|
->setModel(new BaseList('Logs List', self::MODEL_LOG_LIST, 'logs', self::MODEL_LOG, false))
|
||||||
|
@ -133,6 +140,7 @@ class Response extends SwooleResponse
|
||||||
->setModel(new BaseList('Currencies List', self::MODEL_CURRENCY_LIST, 'currencies', self::MODEL_CURRENCY))
|
->setModel(new BaseList('Currencies List', self::MODEL_CURRENCY_LIST, 'currencies', self::MODEL_CURRENCY))
|
||||||
->setModel(new BaseList('Phones List', self::MODEL_PHONE_LIST, 'phones', self::MODEL_PHONE))
|
->setModel(new BaseList('Phones List', self::MODEL_PHONE_LIST, 'phones', self::MODEL_PHONE))
|
||||||
// Entities
|
// Entities
|
||||||
|
->setModel(new Any())
|
||||||
->setModel(new Collection())
|
->setModel(new Collection())
|
||||||
->setModel(new Rule())
|
->setModel(new Rule())
|
||||||
->setModel(new Log())
|
->setModel(new Log())
|
||||||
|
@ -210,12 +218,16 @@ class Response extends SwooleResponse
|
||||||
/**
|
/**
|
||||||
* Generate valid response object from document data
|
* Generate valid response object from document data
|
||||||
*/
|
*/
|
||||||
protected function output(Document $document, string $model): array
|
public function output(Document $document, string $model): array
|
||||||
{
|
{
|
||||||
$data = $document;
|
$data = $document;
|
||||||
$model = $this->getModel($model);
|
$model = $this->getModel($model);
|
||||||
$output = [];
|
$output = [];
|
||||||
|
|
||||||
|
if($model->isAny()) {
|
||||||
|
return $document->getArrayCopy();
|
||||||
|
}
|
||||||
|
|
||||||
foreach($model->getRules() as $key => $rule) {
|
foreach($model->getRules() as $key => $rule) {
|
||||||
if(!$document->isSet($key)) {
|
if(!$document->isSet($key)) {
|
||||||
if(!is_null($rule['default'])) {
|
if(!is_null($rule['default'])) {
|
||||||
|
@ -245,6 +257,8 @@ class Response extends SwooleResponse
|
||||||
$output[$key] = $data[$key];
|
$output[$key] = $data[$key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->payload = $output;
|
||||||
|
|
||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,4 +283,12 @@ class Response extends SwooleResponse
|
||||||
->send(yaml_emit($data, YAML_UTF8_ENCODING))
|
->send(yaml_emit($data, YAML_UTF8_ENCODING))
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getPayload():array
|
||||||
|
{
|
||||||
|
return $this->payload;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,14 @@ namespace Appwrite\Utopia\Response;
|
||||||
|
|
||||||
abstract class Model
|
abstract class Model
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $any = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
protected $rules = [];
|
protected $rules = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,4 +53,9 @@ abstract class Model
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isAny(): bool
|
||||||
|
{
|
||||||
|
return $this->any;
|
||||||
|
}
|
||||||
}
|
}
|
34
src/Appwrite/Utopia/Response/Model/Any.php
Normal file
34
src/Appwrite/Utopia/Response/Model/Any.php
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Appwrite\Utopia\Response\Model;
|
||||||
|
|
||||||
|
use Appwrite\Utopia\Response;
|
||||||
|
use Appwrite\Utopia\Response\Model;
|
||||||
|
|
||||||
|
class Any extends Model
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $any = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Name
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName():string
|
||||||
|
{
|
||||||
|
return 'Any';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Collection
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getType():string
|
||||||
|
{
|
||||||
|
return Response::MODEL_ANY;
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,11 @@ class Rule extends Model
|
||||||
'description' => 'Rule ID.',
|
'description' => 'Rule ID.',
|
||||||
'example' => '5e5ea5c16897e',
|
'example' => '5e5ea5c16897e',
|
||||||
])
|
])
|
||||||
|
->addRule('$collection', [ // TODO remove this from public response
|
||||||
|
'type' => 'string',
|
||||||
|
'description' => 'Rule Collection.',
|
||||||
|
'example' => '5e5e66c16897e',
|
||||||
|
])
|
||||||
->addRule('type', [
|
->addRule('type', [
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'description' => 'Rule type. Possible values: ',
|
'description' => 'Rule type. Possible values: ',
|
||||||
|
@ -34,6 +39,7 @@ class Rule extends Model
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'description' => 'Rule default value.',
|
'description' => 'Rule default value.',
|
||||||
'example' => 'Movie Name',
|
'example' => 'Movie Name',
|
||||||
|
'default' => '',
|
||||||
])
|
])
|
||||||
->addRule('array', [
|
->addRule('array', [
|
||||||
'type' => 'boolean',
|
'type' => 'boolean',
|
||||||
|
@ -45,6 +51,13 @@ class Rule extends Model
|
||||||
'description' => 'Is required?',
|
'description' => 'Is required?',
|
||||||
'example' => true,
|
'example' => true,
|
||||||
])
|
])
|
||||||
|
->addRule('list', [
|
||||||
|
'type' => 'string',
|
||||||
|
'description' => 'List of allowed values',
|
||||||
|
'array' => true,
|
||||||
|
'default' => [],
|
||||||
|
'example' => ['5e5ea5c168099'],
|
||||||
|
])
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ abstract class Scope extends TestCase
|
||||||
protected function getLastEmail():array
|
protected function getLastEmail():array
|
||||||
{
|
{
|
||||||
sleep(10);
|
sleep(10);
|
||||||
|
|
||||||
$emails = json_decode(file_get_contents('http://maildev/email'), true);
|
$emails = json_decode(file_get_contents('http://maildev/email'), true);
|
||||||
|
|
||||||
if($emails && is_array($emails)) {
|
if($emails && is_array($emails)) {
|
||||||
|
@ -43,6 +44,16 @@ abstract class Scope extends TestCase
|
||||||
return [];
|
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
|
* @return array
|
||||||
*/
|
*/
|
||||||
|
|
152
tests/e2e/Services/Workers/WebhooksTest.php
Normal file
152
tests/e2e/Services/Workers/WebhooksTest.php
Normal 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']);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue