Merge branch '0.16.x' of https://github.com/appwrite/appwrite into feat-migration-1.0.0

This commit is contained in:
Torsten Dittmann
2022-09-14 09:28:25 +02:00
23 changed files with 3676 additions and 172 deletions
+275
View File
@@ -0,0 +1,275 @@
<?php
namespace Appwrite\Utopia\Request\Filters;
use Appwrite\Utopia\Request\Filter;
use Utopia\Database\Database;
use Utopia\Database\Permission;
use Utopia\Database\Query;
use Utopia\Database\Role;
class V15 extends Filter
{
// Convert 0.15 params format to 0.16 format
public function parse(array $content, string $model): array
{
switch ($model) {
case 'account.listLogs':
case 'databases.listLogs':
case 'databases.listCollectionLogs':
case 'databases.listDocumentLogs':
case 'teams.listLogs':
case 'users.listLogs':
$content = $this->convertLimitAndOffset($content);
break;
case 'account.initials':
unset($content['color']);
break;
case 'databases.list':
case 'databases.listCollections':
case 'functions.list':
case 'functions.listDeployments':
case 'projects.list':
case 'storage.listBuckets':
case 'storage.listFiles':
case 'teams.list':
case 'teams.listMemberships':
case 'users.list':
$content = $this->convertLimitAndOffset($content);
$content = $this->convertCursor($content);
$content = $this->convertOrderType($content);
break;
case 'databases.createCollection':
case 'databases.updateCollection':
$content = $this->convertCollectionPermission($content);
$content = $this->convertReadWrite($content);
break;
case 'databases.createDocument':
case 'databases.updateDocument':
case 'storage.createFile':
case 'storage.updateFile':
$content = $this->convertReadWrite($content);
break;
case 'databases.listDocuments':
$content = $this->convertFilters($content);
$content = $this->convertLimitAndOffset($content);
$content = $this->convertCursor($content);
$content = $this->convertOrders($content);
break;
case 'functions.create':
case 'functions.update':
$content = $this->convertExecute($content);
break;
case 'functions.listExecutions':
$content = $this->convertLimitAndOffset($content);
$content = $this->convertCursor($content);
break;
case 'projects.createKey':
case 'projects.updateKey':
$content = $this->convertExpire($content);
break;
case 'storage.createBucket':
case 'storage.updateBucket':
$content = $this->convertBucketPermission($content);
$content = $this->convertReadWrite($content);
break;
}
return $content;
}
protected function convertLimitAndOffset($content)
{
if (isset($content['limit'])) {
$content['queries'][] = 'limit(' . $content['limit'] . ')';
}
if (isset($content['offset'])) {
$content['queries'][] = 'offset(' . $content['offset'] . ')';
}
unset($content['limit']);
unset($content['offset']);
return $content;
}
protected function convertCursor($content)
{
if (isset($content['cursor'])) {
$cursorDirection = $content['cursorDirection'] ?? Database::CURSOR_AFTER;
if ($cursorDirection === Database::CURSOR_BEFORE) {
$content['queries'][] = 'cursorBefore("' . $content["cursor"] . '")';
} else {
$content['queries'][] = 'cursorAfter("' . $content["cursor"] . '")';
}
}
unset($content['cursor']);
unset($content['cursorDirection']);
return $content;
}
protected function convertOrderType($content)
{
if (isset($content['orderType'])) {
if ($content['orderType'] === Database::ORDER_DESC) {
$content['queries'][] = 'orderDesc("")';
} else {
$content['queries'][] = 'orderAsc("")';
}
}
unset($content['orderType']);
return $content;
}
protected function convertOrders($content)
{
if (isset($content['orderTypes'])) {
foreach ($content['orderTypes'] as $i => $type) {
$attribute = $content['orderAttributes'][$i] ?? '';
if ($type === Database::ORDER_DESC) {
$content['queries'][] = 'orderDesc("' . $attribute . '")';
} else {
$content['queries'][] = 'orderAsc("' . $attribute . '")';
}
}
}
unset($content['orderAttributes']);
unset($content['orderTypes']);
return $content;
}
protected function convertCollectionPermission($content)
{
if (isset($content['permission'])) {
$content['documentSecurity'] = $content['permission'] === 'document';
}
unset($content['permission']);
return $content;
}
protected function convertReadWrite($content)
{
if (isset($content['read'])) {
foreach ($content['read'] as $read) {
if ($read === 'role:all') {
$content['permissions'][] = Permission::read(Role::any());
} elseif ($read === 'role:guest') {
$content['permissions'][] = Permission::read(Role::guests());
} elseif ($read === 'role:member') {
$content['permissions'][] = Permission::read(Role::users());
} elseif (str_contains($read, ':')) {
$content['permissions'][] = Permission::read(Role::parse($read));
}
}
}
if (isset($content['write'])) {
foreach ($content['write'] as $write) {
if ($write === 'role:all' || $write === 'role:member') {
$content['permissions'][] = Permission::write(Role::users());
} elseif ($write === 'role:guest') {
// don't add because, historically,
// role:guest for write did nothing
} elseif (str_contains($write, ':')) {
$content['permissions'][] = Permission::write(Role::parse($write));
}
}
}
unset($content['read']);
unset($content['write']);
return $content;
}
protected function convertFilters($content)
{
if (!isset($content['queries'])) {
return $content;
}
$operations = [
'equal' => Query::TYPE_EQUAL,
'notEqual' => Query::TYPE_NOTEQUAL,
'lesser' => Query::TYPE_LESSER,
'lesserEqual' => Query::TYPE_LESSEREQUAL,
'greater' => Query::TYPE_GREATER,
'greaterEqual' => Query::TYPE_GREATEREQUAL,
'search' => Query::TYPE_SEARCH,
];
foreach ($content['queries'] as $i => $query) {
foreach ($operations as $oldOperation => $newOperation) {
$middle = ".$oldOperation(";
if (str_contains($query, $middle)) {
$parts = explode($middle, $query);
if (count($parts) > 1) {
$attribute = $parts[0];
$value = rtrim($parts[1], ")");
$content['queries'][$i] = $newOperation . '("' . $attribute . '", [' . $value . '])';
}
}
}
}
return $content;
}
protected function convertExecute($content)
{
if (!isset($content['execute'])) {
return $content;
}
$execute = [];
foreach ($content['execute'] as $role) {
if ($role === 'role:all' || $role === 'role:member') {
$execute[] = Role::users()->toString();
} elseif ($role === 'role:guest') {
// don't add because, historically,
// role:guest for write did nothing
} elseif (str_contains($role, ':')) {
$execute[] = $role;
}
}
$content['execute'] = $execute;
return $content;
}
protected function convertExpire($content)
{
if (!isset($content['expire'])) {
return $content;
}
$expire = (int) $content['expire'];
if ($expire === 0) {
$content['expire'] = null;
} else {
$content['expire'] = date(\DateTime::RFC3339_EXTENDED, $expire);
}
return $content;
}
protected function convertBucketPermission($content)
{
if (isset($content['permission'])) {
$content['fileSecurity'] = $content['permission'] === 'file';
}
unset($content['permission']);
return $content;
}
}
@@ -0,0 +1,577 @@
<?php
namespace Appwrite\Utopia\Response\Filters;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Filter;
use Utopia\Database\Database;
use Utopia\Database\Permission;
use Utopia\Database\Role;
class V15 extends Filter
{
// Convert 0.16 Data format to 0.15 format
public function parse(array $content, string $model): array
{
$parsedResponse = $content;
switch ($model) {
case Response::MODEL_ACCOUNT:
case Response::MODEL_USER:
$parsedResponse = $this->parseUser($parsedResponse);
break;
case Response::MODEL_USER_LIST:
$listKey = 'users';
$parsedResponse[$listKey] = array_map(fn ($content) => $this->parseUser($content), $parsedResponse[$listKey]);
break;
case Response::MODEL_METRIC:
$parsedResponse = $this->parseMetric($parsedResponse);
break;
case Response::MODEL_BUILD:
$parsedResponse = $this->parseBuild($parsedResponse);
break;
case Response::MODEL_BUILD_LIST:
$listKey = 'builds';
$parsedResponse[$listKey] = array_map(fn ($content) => $this->parseBuild($content), $parsedResponse[$listKey]);
break;
case Response::MODEL_BUCKET:
$parsedResponse = $this->parseBucket($parsedResponse);
break;
case Response::MODEL_BUCKET_LIST:
$listKey = 'buckets';
$parsedResponse[$listKey] = array_map(fn ($content) => $this->parseBucket($content), $parsedResponse[$listKey]);
break;
case Response::MODEL_COLLECTION:
$parsedResponse = $this->parseCollection($parsedResponse);
break;
case Response::MODEL_COLLECTION_LIST:
$listKey = 'collections';
$parsedResponse[$listKey] = array_map(fn ($content) => $this->parseCollection($content), $parsedResponse[$listKey]);
break;
case Response::MODEL_DATABASE:
case Response::MODEL_DEPLOYMENT:
case Response::MODEL_DOMAIN:
case Response::MODEL_PLATFORM:
case Response::MODEL_PROJECT:
case Response::MODEL_TEAM:
case Response::MODEL_WEBHOOK:
$parsedResponse = $this->parseCreatedAtUpdatedAt($parsedResponse);
break;
case Response::MODEL_DATABASE_LIST:
case Response::MODEL_DEPLOYMENT_LIST:
case Response::MODEL_DOMAIN_LIST:
case Response::MODEL_PLATFORM_LIST:
case Response::MODEL_PROJECT_LIST:
case Response::MODEL_TEAM_LIST:
case Response::MODEL_WEBHOOK_LIST:
$listKey = '';
switch ($model) {
case Response::MODEL_DATABASE_LIST:
$listKey = 'databases';
break;
case Response::MODEL_DEPLOYMENT_LIST:
$listKey = 'deployments';
break;
case Response::MODEL_DOMAIN_LIST:
$listKey = 'domains';
break;
case Response::MODEL_PLATFORM_LIST:
$listKey = 'platforms';
break;
case Response::MODEL_PROJECT_LIST:
$listKey = 'projects';
break;
case Response::MODEL_TEAM_LIST:
$listKey = 'teams';
break;
case Response::MODEL_WEBHOOK_LIST:
$listKey = 'webhooks';
break;
}
$parsedResponse[$listKey] = array_map(fn ($content) => $this->parseCreatedAtUpdatedAt($content), $parsedResponse[$listKey]);
break;
case Response::MODEL_DOCUMENT:
case Response::MODEL_FILE:
$parsedResponse = $this->parsePermissionsCreatedAtUpdatedAt($parsedResponse);
break;
case Response::MODEL_DOCUMENT_LIST:
case Response::MODEL_FILE_LIST:
$listKey = '';
switch ($model) {
case Response::MODEL_DOCUMENT_LIST:
$listKey = 'documents';
break;
case Response::MODEL_FILE_LIST:
$listKey = 'files';
break;
}
$parsedResponse[$listKey] = array_map(fn ($content) => $this->parsePermissionsCreatedAtUpdatedAt($content), $parsedResponse[$listKey]);
break;
case Response::MODEL_EXECUTION:
$parsedResponse = $this->parseExecution($parsedResponse);
break;
case Response::MODEL_EXECUTION_LIST:
$listKey = 'executions';
$parsedResponse[$listKey] = array_map(fn ($content) => $this->parseExecution($content), $parsedResponse[$listKey]);
break;
case Response::MODEL_FUNCTION:
$parsedResponse = $this->parseFunction($parsedResponse);
break;
case Response::MODEL_FUNCTION_LIST:
$listKey = 'functions';
$parsedResponse[$listKey] = array_map(fn ($content) => $this->parseFunction($content), $parsedResponse[$listKey]);
break;
case Response::MODEL_KEY:
$parsedResponse = $this->parseKey($parsedResponse);
break;
case Response::MODEL_KEY_LIST:
$listKey = 'keys';
$parsedResponse[$listKey] = array_map(fn ($content) => $this->parseKey($content), $parsedResponse[$listKey]);
break;
case Response::MODEL_LOG:
$parsedResponse = $this->parseLog($parsedResponse);
break;
case Response::MODEL_LOG_LIST:
$listKey = 'logs';
$parsedResponse[$listKey] = array_map(fn ($content) => $this->parseLog($content), $parsedResponse[$listKey]);
break;
case Response::MODEL_MEMBERSHIP:
$parsedResponse = $this->parseMembership($parsedResponse);
break;
case Response::MODEL_MEMBERSHIP_LIST:
$listKey = 'memberships';
$parsedResponse[$listKey] = array_map(fn ($content) => $this->parseMembership($content), $parsedResponse[$listKey]);
break;
case Response::MODEL_SESSION:
$parsedResponse = $this->parseSession($parsedResponse);
break;
case Response::MODEL_SESSION_LIST:
$listKey = 'sessions';
$parsedResponse[$listKey] = array_map(fn ($content) => $this->parseSession($content), $parsedResponse[$listKey]);
break;
case Response::MODEL_TOKEN:
$parsedResponse = $this->parseDatetimeAttributes($parsedResponse, ['$createdAt', 'expire']);
break;
case Response::MODEL_USAGE_DATABASES:
$parsedResponse = $this->parseUsageDatabases($parsedResponse);
break;
case Response::MODEL_USAGE_DATABASE:
$parsedResponse = $this->parseUsageDatabase($parsedResponse);
break;
case Response::MODEL_USAGE_COLLECTION:
$parsedResponse = $this->parseUsageCollection($parsedResponse);
break;
case Response::MODEL_USAGE_USERS:
$parsedResponse = $this->parseUsageUsers($parsedResponse);
break;
case Response::MODEL_USAGE_BUCKETS:
$parsedResponse = $this->parseUsageBuckets($parsedResponse);
break;
case Response::MODEL_USAGE_FUNCTIONS:
$parsedResponse = $this->parseUsageFuncs($parsedResponse);
break;
case Response::MODEL_USAGE_PROJECT:
$parsedResponse = $this->parseUsageProject($parsedResponse);
break;
case Response::MODEL_USAGE_STORAGE:
$parsedResponse = $this->parseUsageStorage($parsedResponse);
break;
}
return $parsedResponse;
}
protected function parseBuild(array $content)
{
$content = $this->parseDatetimeAttributes($content, ['startTime', 'endTime']);
return $content;
}
protected function parseBucket(array $content)
{
if (isset($content['fileSecurity'])) {
if ($content['fileSecurity']) {
$content['permission'] = 'file';
} else {
$content['permission'] = 'bucket';
}
}
unset($content['fileSecurity']);
unset($content['compression']);
$content = $this->parsePermissions($content);
$content = $this->parseDatetimeAttributes($content, ['$createdAt', '$updatedAt']);
return $content;
}
protected function parseDatetimeAttributes(array $content, array $attributes): array
{
foreach ($attributes as $attribute) {
if (isset($content[$attribute])) {
$content[$attribute] = strtotime($content[$attribute]);
}
}
return $content;
}
protected function parseUser(array $content): array
{
unset($content['password']);
unset($content['hash']);
unset($content['hashOptions']);
$content = $this->parseDatetimeAttributes($content, ['registration', 'passwordUpdate', '$createdAt', '$updatedAt']);
return $content;
}
protected function parseMetric(array $content)
{
$content = $this->parseDatetimeAttributes($content, ['date']);
return $content;
}
protected function parsePermissions(array $content)
{
if (!isset($content['$permissions'])) {
return $content;
}
$read = [];
$write = [];
// downgrade the permissions
foreach ($content['$permissions'] as $permission) {
$permission = Permission::parse($permission);
$permission_value = $permission->getRole();
if ($permission->getIdentifier()) {
$permission_value .= ':' . $permission->getIdentifier();
}
if ($permission->getDimension()) {
$permission_value .= '/' . $permission->getDimension();
}
// Old type permissions meant that 'write' is equivalent to 'create', 'update' and 'delete'
switch ($permission->getPermission()) {
case Database::PERMISSION_UPDATE:
case Database::PERMISSION_DELETE:
case Database::PERMISSION_WRITE:
case Database::PERMISSION_CREATE:
$write[$this->parseRole($permission_value)] = true;
break;
case Database::PERMISSION_READ:
$read[$this->parseRole($permission_value)] = true;
break;
}
}
$content['$read'] = array_keys($read);
$content['$write'] = array_keys($write);
unset($content['$permissions']);
return $content;
}
protected function parseRole(string $role)
{
switch ($role) {
case Role::any()->toString():
return 'role:all';
case Role::users()->toString():
return 'role:member';
case Role::guests()->toString():
return 'role:guest';
default:
return $role;
}
return $role;
}
protected function parseCollection(array $content)
{
if (isset($content['documentSecurity'])) {
if ($content['documentSecurity']) {
$content['permission'] = 'document';
} else {
$content['permission'] = 'collection';
}
}
unset($content['documentSecurity']);
$content = $this->parsePermissions($content);
$content = $this->parseDatetimeAttributes($content, ['$createdAt', '$updatedAt']);
return $content;
}
protected function parsePermissionsCreatedAtUpdatedAt(array $content)
{
$content = $this->parsePermissions($content);
$content = $this->parseDatetimeAttributes($content, ['$createdAt', '$updatedAt']);
return $content;
}
private function parseExecution($content)
{
unset($content['stdout']);
if (isset($content['$permissions'])) {
$read = [];
foreach ($content['$permissions'] as $role) {
$read[] = $this->parseRole($role);
}
$content['$read'] = $read;
unset($content['$permissions']);
}
if (isset($content['duration'])) {
$content['time'] = $content['duration'];
unset($content['duration']);
}
$content = $this->parseDatetimeAttributes($content, ['$createdAt', '$updatedAt']);
return $content;
}
private function parseCreatedAtUpdatedAt($content)
{
$content = $this->parseDatetimeAttributes($content, ['$createdAt', '$updatedAt']);
return $content;
}
private function parseFunction($content)
{
if (isset($content['execute'])) {
foreach ($content['execute'] as $i => $role) {
$content['execute'][$i] = $this->parseRole($role);
}
}
if (isset($content['vars'])) {
$vars = [];
foreach ($content['vars'] as $i => $var) {
$vars[$var['key']] = $var['value'];
}
$content['vars'] = $vars;
}
if (isset($content['enabled'])) {
$content['status'] = $content['enabled'] ? 'enabled' : 'disabled';
unset($content['enabled']);
}
$content = $this->parseDatetimeAttributes($content, ['$createdAt', '$updatedAt', 'scheduleNext', 'schedulePrevious']);
return $content;
}
private function parseKey($content)
{
$content = $this->parseDatetimeAttributes($content, ['$createdAt', '$updatedAt', 'expire']);
return $content;
}
private function parseLog($content)
{
$content = $this->parseDatetimeAttributes($content, ['time']);
return $content;
}
private function parseMembership($content)
{
$content = $this->parseDatetimeAttributes($content, ['$createdAt', '$updatedAt', 'invited', 'joined']);
return $content;
}
private function parseSession($content)
{
$content = $this->parseDatetimeAttributes($content, ['$createdAt', 'expire', 'providerAccessTokenExpiry']);
return $content;
}
private function parseUsage($content, $keys)
{
foreach ($keys as $key) {
$data = [];
foreach ($content[$key] as $metric) {
$data[] = $this->parseMetric($metric);
}
$content[$key] = $data;
}
return $content;
}
private function parseUsageDatabases($content)
{
$keys = [
'databasesCount',
'documentsCount',
'collectionsCount',
'databasesCreate',
'databasesRead',
'databasesUpdate',
'databasesDelete',
'documentsCreate',
'documentsRead',
'documentsUpdate',
'documentsDelete',
'collectionsCreate',
'collectionsRead',
'collectionsUpdate',
'collectionsDelete',
];
$content = $this->parseUsage($content, $keys);
return $content;
}
private function parseUsageDatabase($content)
{
$keys = [
'documentsCount',
'collectionsCount',
'documentsCreate',
'documentsRead',
'documentsUpdate',
'documentsDelete',
'collectionsCreate',
'collectionsRead',
'collectionsUpdate',
'collectionsDelete',
];
$content = $this->parseUsage($content, $keys);
return $content;
}
private function parseUsageCollection($content)
{
$keys = [
'documentsCount',
'documentsCreate',
'documentsRead',
'documentsUpdate',
'documentsDelete',
];
$content = $this->parseUsage($content, $keys);
return $content;
}
private function parseUsageUsers($content)
{
$keys = [
'usersCount',
'usersCreate',
'usersRead',
'usersUpdate',
'usersDelete',
'sessionsCreate',
'sessionsProviderCreate',
'sessionsDelete',
];
$content = $this->parseUsage($content, $keys);
return $content;
}
private function parseUsageBuckets($content)
{
$keys = [
'filesCount',
'filesStorage',
'filesCreate',
'filesRead',
'filesUpdate',
'filesDelete',
];
$content = $this->parseUsage($content, $keys);
return $content;
}
private function parseUsageFuncs($content)
{
$mapping = [
'executionsTotal' => 'functionsExecutions',
'executionsFailure' => 'functionsFailures',
'executionsTime' => 'functionsCompute',
];
foreach ($mapping as $new => $old) {
if (isset($content[$new])) {
$data = [];
foreach ($content[$new] as $metric) {
$data[] = $this->parseMetric($metric);
}
$content[$old] = $data;
unset($content[$new]);
}
}
unset($content['functionExecutions']);
unset($content['functionFailure']);
unset($content['executionsTime']);
unset($content['buildsTotal']);
unset($content['executionsSuccess']);
unset($content['buildsFailure']);
unset($content['buildsSuccess']);
unset($content['buildsTime']);
return $content;
}
private function parseUsageProject($content)
{
$content['functions'] = $content['executions'];
unset($content['executions']);
$keys = [
'collections',
'documents',
'functions',
'network',
'requests',
'storage',
'users',
];
$content = $this->parseUsage($content, $keys);
return $content;
}
private function parseUsageStorage($content)
{
$content['filesStorage'] = $content['storage'];
unset($content['storage']);
$keys = [
'bucketsCount',
'bucketsCreate',
'bucketsDelete',
'bucketsRead',
'bucketsUpdate',
'filesCount',
'filesCreate',
'filesDelete',
'filesRead',
'filesStorage',
'filesUpdate',
];
$content = $this->parseUsage($content, $keys);
return $content;
}
}
@@ -17,12 +17,11 @@ class Metric extends Model
'example' => 1,
])
->addRule('date', [
'type' => self::TYPE_INTEGER,
'description' => 'The UNIX timestamp at which this metric was aggregated.',
'default' => 0,
'example' => 1592981250
])
;
'type' => self::TYPE_DATETIME,
'description' => 'The date at which this metric was aggregated in ISO 8601 format.',
'default' => '',
'example' => self::TYPE_DATETIME_EXAMPLE
]);
}
/**