Merge branch '1.5.x' into patch-5
This commit is contained in:
commit
58d4def6b8
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -330,7 +330,7 @@ App::get('/v1/account/sessions/oauth2/:provider')
|
||||||
->label('error', __DIR__ . '/../../views/general/error.phtml')
|
->label('error', __DIR__ . '/../../views/general/error.phtml')
|
||||||
->label('scope', 'sessions.write')
|
->label('scope', 'sessions.write')
|
||||||
->label('sdk.auth', [])
|
->label('sdk.auth', [])
|
||||||
->label('sdk.hideServer', true)
|
->label('sdk.hide', [APP_PLATFORM_SERVER])
|
||||||
->label('sdk.namespace', 'account')
|
->label('sdk.namespace', 'account')
|
||||||
->label('sdk.method', 'createOAuth2Session')
|
->label('sdk.method', 'createOAuth2Session')
|
||||||
->label('sdk.description', '/docs/references/account/create-session-oauth2.md')
|
->label('sdk.description', '/docs/references/account/create-session-oauth2.md')
|
||||||
|
@ -400,7 +400,6 @@ App::get('/v1/account/tokens/oauth2/:provider')
|
||||||
->label('error', __DIR__ . '/../../views/general/error.phtml')
|
->label('error', __DIR__ . '/../../views/general/error.phtml')
|
||||||
->label('scope', 'sessions.write')
|
->label('scope', 'sessions.write')
|
||||||
->label('sdk.auth', [])
|
->label('sdk.auth', [])
|
||||||
->label('sdk.hideServer', true)
|
|
||||||
->label('sdk.namespace', 'account')
|
->label('sdk.namespace', 'account')
|
||||||
->label('sdk.method', 'createOAuth2Token')
|
->label('sdk.method', 'createOAuth2Token')
|
||||||
->label('sdk.description', '/docs/references/account/create-token-oauth2.md')
|
->label('sdk.description', '/docs/references/account/create-token-oauth2.md')
|
||||||
|
@ -1635,8 +1634,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
|
||||||
};
|
};
|
||||||
|
|
||||||
App::put('/v1/account/sessions/magic-url')
|
App::put('/v1/account/sessions/magic-url')
|
||||||
->alias('/v1/account/sessions/phone')
|
->desc('Update magic URL session')
|
||||||
->desc('Create session (deprecated)')
|
|
||||||
->label('event', 'users.[userId].sessions.[sessionId].create')
|
->label('event', 'users.[userId].sessions.[sessionId].create')
|
||||||
->groups(['api', 'account'])
|
->groups(['api', 'account'])
|
||||||
->label('scope', 'sessions.write')
|
->label('scope', 'sessions.write')
|
||||||
|
@ -1644,8 +1642,39 @@ App::put('/v1/account/sessions/magic-url')
|
||||||
->label('audits.resource', 'user/{response.userId}')
|
->label('audits.resource', 'user/{response.userId}')
|
||||||
->label('audits.userId', '{response.userId}')
|
->label('audits.userId', '{response.userId}')
|
||||||
->label('sdk.auth', [])
|
->label('sdk.auth', [])
|
||||||
|
->label('sdk.deprecated', true)
|
||||||
->label('sdk.namespace', 'account')
|
->label('sdk.namespace', 'account')
|
||||||
->label('sdk.method', ['updateMagicURLSession', 'updatePhoneSession'])
|
->label('sdk.method', 'updateMagicURLSession')
|
||||||
|
->label('sdk.description', '/docs/references/account/create-session.md')
|
||||||
|
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||||
|
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||||
|
->label('sdk.response.model', Response::MODEL_SESSION)
|
||||||
|
->label('abuse-limit', 10)
|
||||||
|
->label('abuse-key', 'ip:{ip},userId:{param-userId}')
|
||||||
|
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||||
|
->param('secret', '', new Text(256), 'Valid verification token.')
|
||||||
|
->inject('request')
|
||||||
|
->inject('response')
|
||||||
|
->inject('user')
|
||||||
|
->inject('dbForProject')
|
||||||
|
->inject('project')
|
||||||
|
->inject('locale')
|
||||||
|
->inject('geodb')
|
||||||
|
->inject('queueForEvents')
|
||||||
|
->action($createSession);
|
||||||
|
|
||||||
|
App::put('/v1/account/sessions/phone')
|
||||||
|
->desc('Update phone session')
|
||||||
|
->label('event', 'users.[userId].sessions.[sessionId].create')
|
||||||
|
->groups(['api', 'account'])
|
||||||
|
->label('scope', 'sessions.write')
|
||||||
|
->label('audits.event', 'session.create')
|
||||||
|
->label('audits.resource', 'user/{response.userId}')
|
||||||
|
->label('audits.userId', '{response.userId}')
|
||||||
|
->label('sdk.auth', [])
|
||||||
|
->label('sdk.deprecated', true)
|
||||||
|
->label('sdk.namespace', 'account')
|
||||||
|
->label('sdk.method', 'updatePhoneSession')
|
||||||
->label('sdk.description', '/docs/references/account/create-session.md')
|
->label('sdk.description', '/docs/references/account/create-session.md')
|
||||||
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
|
||||||
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
|
||||||
|
@ -3918,11 +3947,11 @@ App::put('/v1/account/mfa/challenge')
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!$success && $provider === 'totp') {
|
if (!$success && $provider === 'totp') {
|
||||||
$backups = $user->getAttribute('mfaBackups', []);
|
$backups = $user->getAttribute('totpBackup', []);
|
||||||
if (in_array($otp, $backups)) {
|
if (in_array($otp, $backups)) {
|
||||||
$success = true;
|
$success = true;
|
||||||
$backups = array_diff($backups, [$otp]);
|
$backups = array_diff($backups, [$otp]);
|
||||||
$user->setAttribute('mfaBackups', $backups);
|
$user->setAttribute('totpBackup', $backups);
|
||||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1616,6 +1616,54 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/attributes/relati
|
||||||
$key ??= $relatedCollectionId;
|
$key ??= $relatedCollectionId;
|
||||||
$twoWayKey ??= $collectionId;
|
$twoWayKey ??= $collectionId;
|
||||||
|
|
||||||
|
$database = Authorization::skip(fn() => $dbForProject->getDocument('databases', $databaseId));
|
||||||
|
|
||||||
|
if ($database->isEmpty()) {
|
||||||
|
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
|
||||||
|
$collection = $dbForProject->getCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
|
||||||
|
|
||||||
|
if ($collection->isEmpty()) {
|
||||||
|
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
$relatedCollectionDocument = $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId);
|
||||||
|
$relatedCollection = $dbForProject->getCollection('database_' . $database->getInternalId() . '_collection_' . $relatedCollectionDocument->getInternalId());
|
||||||
|
|
||||||
|
if ($relatedCollection->isEmpty()) {
|
||||||
|
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
$attributes = $collection->getAttribute('attributes', []);
|
||||||
|
/** @var Document[] $attributes */
|
||||||
|
foreach ($attributes as $attribute) {
|
||||||
|
if ($attribute->getAttribute('type') !== Database::VAR_RELATIONSHIP) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (\strtolower($attribute->getId()) === \strtolower($key)) {
|
||||||
|
throw new Exception(Exception::ATTRIBUTE_ALREADY_EXISTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
\strtolower($attribute->getAttribute('options')['twoWayKey']) === \strtolower($twoWayKey) &&
|
||||||
|
$attribute->getAttribute('options')['relatedCollection'] === $relatedCollection->getId()
|
||||||
|
) {
|
||||||
|
// Console should provide a unique twoWayKey input!
|
||||||
|
throw new Exception(Exception::ATTRIBUTE_ALREADY_EXISTS, 'Attribute with the requested key already exists. Attribute keys must be unique, try again with a different key.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
$type === Database::RELATION_MANY_TO_MANY &&
|
||||||
|
$attribute->getAttribute('options')['relationType'] === Database::RELATION_MANY_TO_MANY &&
|
||||||
|
$attribute->getAttribute('options')['relatedCollection'] === $relatedCollection->getId()
|
||||||
|
) {
|
||||||
|
throw new Exception(Exception::ATTRIBUTE_ALREADY_EXISTS, 'Creating more than one "manyToMany" relationship on the same collection is currently not permitted.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$attribute = createAttribute(
|
$attribute = createAttribute(
|
||||||
$databaseId,
|
$databaseId,
|
||||||
$collectionId,
|
$collectionId,
|
||||||
|
|
|
@ -420,17 +420,23 @@ App::get('/v1/functions/runtimes')
|
||||||
->label('sdk.response.model', Response::MODEL_RUNTIME_LIST)
|
->label('sdk.response.model', Response::MODEL_RUNTIME_LIST)
|
||||||
->inject('response')
|
->inject('response')
|
||||||
->action(function (Response $response) {
|
->action(function (Response $response) {
|
||||||
|
|
||||||
$runtimes = Config::getParam('runtimes');
|
$runtimes = Config::getParam('runtimes');
|
||||||
|
|
||||||
$runtimes = array_map(function ($key) use ($runtimes) {
|
$allowList = \array_filter(\explode(',', App::getEnv('_APP_FUNCTIONS_RUNTIMES', '')));
|
||||||
|
|
||||||
|
$allowed = [];
|
||||||
|
foreach ($runtimes as $key => $runtime) {
|
||||||
|
if (!empty($allowList) && !\in_array($key, $allowList)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$runtimes[$key]['$id'] = $key;
|
$runtimes[$key]['$id'] = $key;
|
||||||
return $runtimes[$key];
|
$allowed[] = $runtimes[$key];
|
||||||
}, array_keys($runtimes));
|
}
|
||||||
|
|
||||||
$response->dynamic(new Document([
|
$response->dynamic(new Document([
|
||||||
'total' => count($runtimes),
|
'total' => count($allowed),
|
||||||
'runtimes' => $runtimes
|
'runtimes' => $allowed
|
||||||
]), Response::MODEL_RUNTIME_LIST);
|
]), Response::MODEL_RUNTIME_LIST);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1599,30 +1599,20 @@ App::delete('/v1/users/:userId/mfa/:type')
|
||||||
->label('sdk.response.model', Response::MODEL_USER)
|
->label('sdk.response.model', Response::MODEL_USER)
|
||||||
->param('userId', '', new UID(), 'User ID.')
|
->param('userId', '', new UID(), 'User ID.')
|
||||||
->param('type', null, new WhiteList(['totp']), 'Type of authenticator.')
|
->param('type', null, new WhiteList(['totp']), 'Type of authenticator.')
|
||||||
->param('otp', '', new Text(256), 'Valid verification token.')
|
|
||||||
->inject('requestTimestamp')
|
->inject('requestTimestamp')
|
||||||
->inject('response')
|
->inject('response')
|
||||||
->inject('dbForProject')
|
->inject('dbForProject')
|
||||||
->inject('queueForEvents')
|
->inject('queueForEvents')
|
||||||
->action(function (string $userId, string $type, string $otp, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents) {
|
->action(function (string $userId, string $type, ?\DateTime $requestTimestamp, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||||
$user = $dbForProject->getDocument('users', $userId);
|
$user = $dbForProject->getDocument('users', $userId);
|
||||||
|
|
||||||
if ($user->isEmpty()) {
|
if ($user->isEmpty()) {
|
||||||
throw new Exception(Exception::USER_NOT_FOUND);
|
throw new Exception(Exception::USER_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
$success = match ($type) {
|
if (!$user->getAttribute('totp')) {
|
||||||
'totp' => Challenge\TOTP::verify($user, $otp),
|
throw new Exception(Exception::GENERAL_UNKNOWN, 'TOTP not added.');
|
||||||
default => false
|
}
|
||||||
};
|
|
||||||
|
|
||||||
if (!$success) {
|
|
||||||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$user->getAttribute('totp')) {
|
|
||||||
throw new Exception(Exception::GENERAL_UNKNOWN, 'TOTP not added.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$user
|
$user
|
||||||
->setAttribute('totp', false)
|
->setAttribute('totp', false)
|
||||||
|
|
|
@ -99,33 +99,26 @@ class Schema
|
||||||
/** @var Route $route */
|
/** @var Route $route */
|
||||||
|
|
||||||
$namespace = $route->getLabel('sdk.namespace', '');
|
$namespace = $route->getLabel('sdk.namespace', '');
|
||||||
$methods = $route->getLabel('sdk.method', '');
|
$method = $route->getLabel('sdk.method', '');
|
||||||
|
$name = $namespace . \ucfirst($method);
|
||||||
|
|
||||||
if (!\is_array($methods)) {
|
if (empty($name)) {
|
||||||
$methods = [$methods];
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($methods as $method) {
|
foreach (Mapper::route($utopia, $route, $complexity) as $field) {
|
||||||
$name = $namespace . \ucfirst($method);
|
switch ($route->getMethod()) {
|
||||||
|
case 'GET':
|
||||||
if (empty($name)) {
|
$queries[$name] = $field;
|
||||||
continue;
|
break;
|
||||||
}
|
case 'POST':
|
||||||
|
case 'PUT':
|
||||||
foreach (Mapper::route($utopia, $route, $complexity) as $field) {
|
case 'PATCH':
|
||||||
switch ($route->getMethod()) {
|
case 'DELETE':
|
||||||
case 'GET':
|
$mutations[$name] = $field;
|
||||||
$queries[$name] = $field;
|
break;
|
||||||
break;
|
default:
|
||||||
case 'POST':
|
throw new \Exception("Unsupported method: {$route->getMethod()}");
|
||||||
case 'PUT':
|
|
||||||
case 'PATCH':
|
|
||||||
case 'DELETE':
|
|
||||||
$mutations[$name] = $field;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new \Exception("Unsupported method: {$route->getMethod()}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,30 +168,35 @@ class Specs extends Action
|
||||||
|
|
||||||
foreach ($appRoutes as $key => $method) {
|
foreach ($appRoutes as $key => $method) {
|
||||||
foreach ($method as $route) {
|
foreach ($method as $route) {
|
||||||
|
$hide = $route->getLabel('sdk.hide', false);
|
||||||
|
if ($hide === true || (\is_array($hide) && \in_array($platform, $hide))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
/** @var \Utopia\Route $route */
|
/** @var \Utopia\Route $route */
|
||||||
$routeSecurity = $route->getLabel('sdk.auth', []);
|
$routeSecurity = $route->getLabel('sdk.auth', []);
|
||||||
$sdkPlaforms = [];
|
$sdkPlatforms = [];
|
||||||
|
|
||||||
foreach ($routeSecurity as $value) {
|
foreach ($routeSecurity as $value) {
|
||||||
switch ($value) {
|
switch ($value) {
|
||||||
case APP_AUTH_TYPE_SESSION:
|
case APP_AUTH_TYPE_SESSION:
|
||||||
$sdkPlaforms[] = APP_PLATFORM_CLIENT;
|
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
|
||||||
break;
|
break;
|
||||||
case APP_AUTH_TYPE_KEY:
|
case APP_AUTH_TYPE_KEY:
|
||||||
$sdkPlaforms[] = APP_PLATFORM_SERVER;
|
$sdkPlatforms[] = APP_PLATFORM_SERVER;
|
||||||
break;
|
break;
|
||||||
case APP_AUTH_TYPE_JWT:
|
case APP_AUTH_TYPE_JWT:
|
||||||
$sdkPlaforms[] = APP_PLATFORM_SERVER;
|
$sdkPlatforms[] = APP_PLATFORM_SERVER;
|
||||||
break;
|
break;
|
||||||
case APP_AUTH_TYPE_ADMIN:
|
case APP_AUTH_TYPE_ADMIN:
|
||||||
$sdkPlaforms[] = APP_PLATFORM_CONSOLE;
|
$sdkPlatforms[] = APP_PLATFORM_CONSOLE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($routeSecurity)) {
|
if (empty($routeSecurity)) {
|
||||||
$sdkPlaforms[] = APP_PLATFORM_CLIENT;
|
$sdkPlatforms[] = APP_PLATFORM_SERVER;
|
||||||
$sdkPlaforms[] = APP_PLATFORM_SERVER;
|
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$route->getLabel('docs', true)) {
|
if (!$route->getLabel('docs', true)) {
|
||||||
|
@ -210,7 +215,7 @@ class Specs extends Action
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $sdkPlaforms)) {
|
if ($platform !== APP_PLATFORM_CONSOLE && !\in_array($platforms[$platform], $sdkPlatforms)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,18 +120,10 @@ class OpenAPI3 extends Format
|
||||||
foreach ($this->routes as $route) {
|
foreach ($this->routes as $route) {
|
||||||
$url = \str_replace('/v1', '', $route->getPath());
|
$url = \str_replace('/v1', '', $route->getPath());
|
||||||
$scope = $route->getLabel('scope', '');
|
$scope = $route->getLabel('scope', '');
|
||||||
$hide = $route->getLabel('sdk.hide', false);
|
|
||||||
$consumes = [$route->getLabel('sdk.request.type', 'application/json')];
|
$consumes = [$route->getLabel('sdk.request.type', 'application/json')];
|
||||||
|
|
||||||
if ($hide) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$method = $route->getLabel('sdk.method', [\uniqid()]);
|
|
||||||
if (\is_array($method)) {
|
|
||||||
$method = $method[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
$method = $route->getLabel('sdk.method', \uniqid());
|
||||||
$desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath(__DIR__ . '/../../../../' . $route->getLabel('sdk.description', '')) : null;
|
$desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath(__DIR__ . '/../../../../' . $route->getLabel('sdk.description', '')) : null;
|
||||||
$produces = $route->getLabel('sdk.response.type', null);
|
$produces = $route->getLabel('sdk.response.type', null);
|
||||||
$model = $route->getLabel('sdk.response.model', 'none');
|
$model = $route->getLabel('sdk.response.model', 'none');
|
||||||
|
@ -156,12 +148,8 @@ class OpenAPI3 extends Format
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($routeSecurity)) {
|
if (empty($routeSecurity)) {
|
||||||
if (!$route->getLabel('sdk.hideServer', false)) {
|
$sdkPlatforms[] = APP_PLATFORM_SERVER;
|
||||||
$sdkPlatforms[] = APP_PLATFORM_SERVER;
|
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
|
||||||
}
|
|
||||||
if (!$route->getLabel('sdk.hideClient', false)) {
|
|
||||||
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$temp = [
|
$temp = [
|
||||||
|
@ -175,6 +163,7 @@ class OpenAPI3 extends Format
|
||||||
'weight' => $route->getOrder(),
|
'weight' => $route->getOrder(),
|
||||||
'cookies' => $route->getLabel('sdk.cookies', false),
|
'cookies' => $route->getLabel('sdk.cookies', false),
|
||||||
'type' => $route->getLabel('sdk.methodType', ''),
|
'type' => $route->getLabel('sdk.methodType', ''),
|
||||||
|
'deprecated' => $route->getLabel('sdk.deprecated', false),
|
||||||
'demo' => Template::fromCamelCaseToDash($route->getLabel('sdk.namespace', 'default')) . '/' . Template::fromCamelCaseToDash($method) . '.md',
|
'demo' => Template::fromCamelCaseToDash($route->getLabel('sdk.namespace', 'default')) . '/' . Template::fromCamelCaseToDash($method) . '.md',
|
||||||
'edit' => 'https://github.com/appwrite/appwrite/edit/master' . $route->getLabel('sdk.description', ''),
|
'edit' => 'https://github.com/appwrite/appwrite/edit/master' . $route->getLabel('sdk.description', ''),
|
||||||
'rate-limit' => $route->getLabel('abuse-limit', 0),
|
'rate-limit' => $route->getLabel('abuse-limit', 0),
|
||||||
|
|
|
@ -118,18 +118,9 @@ class Swagger2 extends Format
|
||||||
/** @var \Utopia\Route $route */
|
/** @var \Utopia\Route $route */
|
||||||
$url = \str_replace('/v1', '', $route->getPath());
|
$url = \str_replace('/v1', '', $route->getPath());
|
||||||
$scope = $route->getLabel('scope', '');
|
$scope = $route->getLabel('scope', '');
|
||||||
$hide = $route->getLabel('sdk.hide', false);
|
|
||||||
$consumes = [$route->getLabel('sdk.request.type', 'application/json')];
|
$consumes = [$route->getLabel('sdk.request.type', 'application/json')];
|
||||||
|
|
||||||
if ($hide) {
|
$method = $route->getLabel('sdk.method', \uniqid());
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$method = $route->getLabel('sdk.method', [\uniqid()]);
|
|
||||||
if (\is_array($method)) {
|
|
||||||
$method = $method[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
$desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath(__DIR__ . '/../../../../' . $route->getLabel('sdk.description', '')) : null;
|
$desc = (!empty($route->getLabel('sdk.description', ''))) ? \realpath(__DIR__ . '/../../../../' . $route->getLabel('sdk.description', '')) : null;
|
||||||
$produces = $route->getLabel('sdk.response.type', null);
|
$produces = $route->getLabel('sdk.response.type', null);
|
||||||
$model = $route->getLabel('sdk.response.model', 'none');
|
$model = $route->getLabel('sdk.response.model', 'none');
|
||||||
|
@ -154,8 +145,8 @@ class Swagger2 extends Format
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($routeSecurity)) {
|
if (empty($routeSecurity)) {
|
||||||
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
|
|
||||||
$sdkPlatforms[] = APP_PLATFORM_SERVER;
|
$sdkPlatforms[] = APP_PLATFORM_SERVER;
|
||||||
|
$sdkPlatforms[] = APP_PLATFORM_CLIENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
$temp = [
|
$temp = [
|
||||||
|
@ -171,6 +162,7 @@ class Swagger2 extends Format
|
||||||
'weight' => $route->getOrder(),
|
'weight' => $route->getOrder(),
|
||||||
'cookies' => $route->getLabel('sdk.cookies', false),
|
'cookies' => $route->getLabel('sdk.cookies', false),
|
||||||
'type' => $route->getLabel('sdk.methodType', ''),
|
'type' => $route->getLabel('sdk.methodType', ''),
|
||||||
|
'deprecated' => $route->getLabel('sdk.deprecated', false),
|
||||||
'demo' => Template::fromCamelCaseToDash($route->getLabel('sdk.namespace', 'default')) . '/' . Template::fromCamelCaseToDash($method) . '.md',
|
'demo' => Template::fromCamelCaseToDash($route->getLabel('sdk.namespace', 'default')) . '/' . Template::fromCamelCaseToDash($method) . '.md',
|
||||||
'edit' => 'https://github.com/appwrite/appwrite/edit/master' . $route->getLabel('sdk.description', ''),
|
'edit' => 'https://github.com/appwrite/appwrite/edit/master' . $route->getLabel('sdk.description', ''),
|
||||||
'rate-limit' => $route->getLabel('abuse-limit', 0),
|
'rate-limit' => $route->getLabel('abuse-limit', 0),
|
||||||
|
|
|
@ -25,10 +25,7 @@ class Request extends UtopiaRequest
|
||||||
$parameters = parent::getParams();
|
$parameters = parent::getParams();
|
||||||
|
|
||||||
if (self::hasFilter() && self::hasRoute()) {
|
if (self::hasFilter() && self::hasRoute()) {
|
||||||
$method = self::getRoute()->getLabel('sdk.method', ['unknown']);
|
$method = self::getRoute()->getLabel('sdk.method', 'unknown');
|
||||||
if (\is_array($method)) {
|
|
||||||
$method = $method[0];
|
|
||||||
}
|
|
||||||
$endpointIdentifier = self::getRoute()->getLabel('sdk.namespace', 'unknown') . '.' . $method;
|
$endpointIdentifier = self::getRoute()->getLabel('sdk.namespace', 'unknown') . '.' . $method;
|
||||||
$parameters = self::getFilter()->parse($parameters, $endpointIdentifier);
|
$parameters = self::getFilter()->parse($parameters, $endpointIdentifier);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use Tests\E2E\Client;
|
||||||
use Tests\E2E\Scopes\Scope;
|
use Tests\E2E\Scopes\Scope;
|
||||||
use Tests\E2E\Scopes\ProjectCustom;
|
use Tests\E2E\Scopes\ProjectCustom;
|
||||||
use Tests\E2E\Scopes\SideClient;
|
use Tests\E2E\Scopes\SideClient;
|
||||||
|
use Utopia\Database\Database;
|
||||||
use Utopia\Database\Helpers\ID;
|
use Utopia\Database\Helpers\ID;
|
||||||
use Utopia\Database\Helpers\Permission;
|
use Utopia\Database\Helpers\Permission;
|
||||||
use Utopia\Database\Helpers\Role;
|
use Utopia\Database\Helpers\Role;
|
||||||
|
@ -316,6 +317,164 @@ class DatabasesCustomClientTest extends Scope
|
||||||
$this->assertEquals('restrict', $collection1RelationAttribute['onDelete']);
|
$this->assertEquals('restrict', $collection1RelationAttribute['onDelete']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testRelationshipSameTwoWayKey(): void
|
||||||
|
{
|
||||||
|
$database = $this->client->call(Client::METHOD_POST, '/databases', [
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||||
|
], [
|
||||||
|
'databaseId' => ID::unique(),
|
||||||
|
'name' => 'Same two way key'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$databaseId = $database['body']['$id'];
|
||||||
|
|
||||||
|
$collection1 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||||
|
]), [
|
||||||
|
'collectionId' => ID::unique(),
|
||||||
|
'name' => 'c1',
|
||||||
|
'documentSecurity' => false,
|
||||||
|
'permissions' => [
|
||||||
|
Permission::create(Role::user($this->getUser()['$id'])),
|
||||||
|
Permission::read(Role::user($this->getUser()['$id'])),
|
||||||
|
Permission::update(Role::user($this->getUser()['$id'])),
|
||||||
|
Permission::delete(Role::user($this->getUser()['$id'])),
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$collection2 = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||||
|
]), [
|
||||||
|
'collectionId' => ID::unique(),
|
||||||
|
'name' => 'c2',
|
||||||
|
'documentSecurity' => false,
|
||||||
|
'permissions' => [
|
||||||
|
Permission::create(Role::user($this->getUser()['$id'])),
|
||||||
|
Permission::read(Role::user($this->getUser()['$id'])),
|
||||||
|
Permission::update(Role::user($this->getUser()['$id'])),
|
||||||
|
Permission::delete(Role::user($this->getUser()['$id'])),
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
\sleep(2);
|
||||||
|
|
||||||
|
$relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1['body']['$id'] . '/attributes/relationship', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||||
|
]), [
|
||||||
|
'relatedCollectionId' => $collection2['body']['$id'],
|
||||||
|
'type' => Database::RELATION_ONE_TO_ONE,
|
||||||
|
'twoWay' => false,
|
||||||
|
'onDelete' => 'cascade',
|
||||||
|
'key' => 'attr1',
|
||||||
|
'twoWayKey' => 'same_key'
|
||||||
|
]);
|
||||||
|
|
||||||
|
\sleep(2);
|
||||||
|
|
||||||
|
$this->assertEquals(202, $relation['headers']['status-code']);
|
||||||
|
$this->assertEquals('same_key', $relation['body']['twoWayKey']);
|
||||||
|
|
||||||
|
$relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1['body']['$id'] . '/attributes/relationship', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||||
|
]), [
|
||||||
|
'relatedCollectionId' => $collection2['body']['$id'],
|
||||||
|
'type' => Database::RELATION_ONE_TO_MANY,
|
||||||
|
'twoWay' => false,
|
||||||
|
'onDelete' => 'cascade',
|
||||||
|
'key' => 'attr2',
|
||||||
|
'twoWayKey' => 'same_key'
|
||||||
|
]);
|
||||||
|
|
||||||
|
\sleep(2);
|
||||||
|
|
||||||
|
$this->assertEquals(409, $relation['body']['code']);
|
||||||
|
$this->assertEquals('Attribute with the requested key already exists. Attribute keys must be unique, try again with a different key.', $relation['body']['message']);
|
||||||
|
|
||||||
|
// twoWayKey is null TwoWayKey is default
|
||||||
|
$relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1['body']['$id'] . '/attributes/relationship', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||||
|
]), [
|
||||||
|
'relatedCollectionId' => $collection2['body']['$id'],
|
||||||
|
'type' => Database::RELATION_ONE_TO_MANY,
|
||||||
|
'twoWay' => false,
|
||||||
|
'onDelete' => 'cascade',
|
||||||
|
'key' => 'attr3',
|
||||||
|
]);
|
||||||
|
|
||||||
|
\sleep(2);
|
||||||
|
|
||||||
|
$this->assertEquals(202, $relation['headers']['status-code']);
|
||||||
|
$this->assertArrayHasKey('twoWayKey', $relation['body']);
|
||||||
|
|
||||||
|
// twoWayKey is null, TwoWayKey is default, second POST
|
||||||
|
$relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1['body']['$id'] . '/attributes/relationship', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||||
|
]), [
|
||||||
|
'relatedCollectionId' => $collection2['body']['$id'],
|
||||||
|
'type' => Database::RELATION_ONE_TO_MANY,
|
||||||
|
'twoWay' => false,
|
||||||
|
'onDelete' => 'cascade',
|
||||||
|
'key' => 'attr4',
|
||||||
|
]);
|
||||||
|
|
||||||
|
\sleep(2);
|
||||||
|
|
||||||
|
$this->assertEquals('Attribute with the requested key already exists. Attribute keys must be unique, try again with a different key.', $relation['body']['message']);
|
||||||
|
$this->assertEquals(409, $relation['body']['code']);
|
||||||
|
|
||||||
|
// RelationshipManyToMany
|
||||||
|
$relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1['body']['$id'] . '/attributes/relationship', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||||
|
]), [
|
||||||
|
'relatedCollectionId' => $collection2['body']['$id'],
|
||||||
|
'type' => Database::RELATION_MANY_TO_MANY,
|
||||||
|
'twoWay' => true,
|
||||||
|
'onDelete' => 'setNull',
|
||||||
|
'key' => 'songs',
|
||||||
|
'twoWayKey' => 'playlist',
|
||||||
|
]);
|
||||||
|
|
||||||
|
\sleep(2);
|
||||||
|
|
||||||
|
$this->assertEquals(202, $relation['headers']['status-code']);
|
||||||
|
$this->assertArrayHasKey('twoWayKey', $relation['body']);
|
||||||
|
|
||||||
|
// Second RelationshipManyToMany on Same collections
|
||||||
|
$relation = $this->client->call(Client::METHOD_POST, '/databases/' . $databaseId . '/collections/' . $collection1['body']['$id'] . '/attributes/relationship', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
'x-appwrite-key' => $this->getProject()['apiKey']
|
||||||
|
]), [
|
||||||
|
'relatedCollectionId' => $collection2['body']['$id'],
|
||||||
|
'type' => Database::RELATION_MANY_TO_MANY,
|
||||||
|
'twoWay' => true,
|
||||||
|
'onDelete' => 'setNull',
|
||||||
|
'key' => 'songs2',
|
||||||
|
'twoWayKey' => 'playlist2',
|
||||||
|
]);
|
||||||
|
|
||||||
|
\sleep(2);
|
||||||
|
|
||||||
|
$this->assertEquals(409, $relation['body']['code']);
|
||||||
|
$this->assertEquals('Creating more than one "manyToMany" relationship on the same collection is currently not permitted.', $relation['body']['message']);
|
||||||
|
}
|
||||||
|
|
||||||
public function testUpdateWithoutRelationPermission(): void
|
public function testUpdateWithoutRelationPermission(): void
|
||||||
{
|
{
|
||||||
$userId = $this->getUser()['$id'];
|
$userId = $this->getUser()['$id'];
|
||||||
|
|
Loading…
Reference in a new issue