diff --git a/app/config/collections.php b/app/config/collections.php index f6fff4c474..929a8572b9 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -1704,16 +1704,16 @@ $commonCollections = [ 'format' => '', 'size' => Database::LENGTH_KEY, 'signed' => true, - 'required' => true, + 'required' => false, 'default' => null, 'array' => false, - 'filters' => [], + 'filters' => ['subQueryProviderType'], ], [ '$id' => ID::custom('identifier'), 'type' => Database::VAR_STRING, 'format' => '', - 'size' => 2048, + 'size' => Database::LENGTH_KEY, 'signed' => true, 'required' => true, 'default' => null, diff --git a/app/config/errors.php b/app/config/errors.php index 5006df0e89..9e8bdd2da7 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -232,7 +232,12 @@ return [ ], Exception::USER_TARGET_NOT_FOUND => [ 'name' => Exception::USER_TARGET_NOT_FOUND, - 'description' => 'The current user target could not be found.', + 'description' => 'The target could not be found.', + 'code' => 404, + ], + Exception::USER_TARGET_ALREADY_EXISTS => [ + 'name' => Exception::USER_TARGET_ALREADY_EXISTS, + 'description' => 'A target with the same ID already exists.', 'code' => 404, ], @@ -664,4 +669,11 @@ return [ 'description' => 'Too many queries.', 'code' => 400, ], + + /** Provider Errors */ + Exception::PROVIDER_NOT_FOUND => [ + 'name' => Exception::PROVIDER_NOT_FOUND, + 'description' => 'Provider with the request ID could not be found.', + 'code' => 400, + ], ]; diff --git a/app/config/roles.php b/app/config/roles.php index f0039841d2..8574a1392c 100644 --- a/app/config/roles.php +++ b/app/config/roles.php @@ -20,6 +20,8 @@ $member = [ 'avatars.read', 'execution.read', 'execution.write', + 'targets.read', + 'targets.write', ]; $admins = [ @@ -51,6 +53,8 @@ $admins = [ 'functions.write', 'execution.read', 'execution.write', + 'targets.read', + 'targets.write', ]; return [ diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 0e69ccdcd9..64fe71a0dd 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1672,7 +1672,7 @@ App::get('/v1/account/targets') $response->dynamic(new Document([ 'targets' => $targets, - 'total' => count($targets), + 'total' => \count($targets), ]), Response::MODEL_TARGET_LIST); }); diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index c1b664ca26..2290a77c3e 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -363,6 +363,66 @@ App::post('/v1/users/scrypt-modified') ->dynamic($user, Response::MODEL_USER); }); +App::post('/v1/users/:userId/targets') + ->desc('Create User Target') + ->groups(['api', 'users']) + ->label('event', 'users.[userId].targets.[targetId].create') + ->label('scope', 'targets.write') + ->label('sdk.auth', [APP_AUTH_TYPE_KEY]) + ->label('sdk.namespace', 'users') + ->label('sdk.method', 'createTarget') + ->label('sdk.description', '/docs/references/users/create-target.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_TARGET) + ->param('targetId', '', new UID(), 'Target ID.', false) + ->param('userId', '', new UID(), 'ID of the user.', false) + ->param('providerId', '', new UID(), 'ID of the provider.', false) + ->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)', false) + ->inject('response') + ->inject('project') + ->inject('dbForProject') + ->inject('events') + ->action(function (string $targetId, string $userId, string $providerId, string $identifier, Response $response, Document $project, Database $dbForProject, Event $events) { + $provider = $dbForProject->getDocument('providers', $providerId); + + if ($provider->isEmpty()) { + throw new Exception(Exception::PROVIDER_NOT_FOUND); + } + + $user = $dbForProject->getDocument('users', $userId); + + if($user->isEmpty()) { + throw new Exception(Exception::USER_NOT_FOUND); + } + + $target = $dbForProject->getDocument('targets', $targetId); + + if(!$target->isEmpty()) { + throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS); + } + + $target = $dbForProject->createDocument('targets', new Document([ + '$id' => $targetId, + // TO DO: what permissions should be given when created a target. + '$permissions' => [ + Permission::read(Role::any()) + ], + 'providerId' => $providerId, + 'providerInternalId' => $provider->getInternalId(), + 'providerType' => null, + 'userId' => $userId, + 'userInternalId' => $user->getInternalId(), + 'identifier' => $identifier, + ])); + $events + ->setParam('userId', $userId) + ->setParam('targetId', $targetId); + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($target, Response::MODEL_TARGET); + }); + App::get('/v1/users') ->desc('List Users') ->groups(['api', 'users']) @@ -677,10 +737,9 @@ App::get('/v1/users/:userId/targets') } $targets = $user->getAttribute('targets', []); - var_dump($user); $response->dynamic(new Document([ 'targets' => $targets, - 'total' => count($targets), + 'total' => \count($targets), ]), Response::MODEL_TARGET_LIST); }); diff --git a/app/init.php b/app/init.php index 65cf4034a6..46b19cbe16 100644 --- a/app/init.php +++ b/app/init.php @@ -535,6 +535,25 @@ Database::addFilter( ])); } ); + +Database::addFilter( + 'subQueryProviderType', + function (mixed $value) { + return null; + }, + function (mixed $value, Document $document, Database $database) { + $provider = Authorization::skip(fn() => $database + ->findOne('providers', [ + Query::equal('$id', [$document->getAttribute('providerId')]), + Query::select(['type']), + Query::limit(APP_LIMIT_SUBQUERY), + ])); + if($provider) + return $provider->getAttribute('type'); + return null; + } +); + /** * DB Formats */ diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 1392faaed6..92bbd5c6c4 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -82,7 +82,7 @@ class Exception extends \Exception public const USER_OAUTH2_UNAUTHORIZED = 'user_oauth2_unauthorized'; public const USER_OAUTH2_PROVIDER_ERROR = 'user_oauth2_provider_error'; public const USER_TARGET_NOT_FOUND = 'user_target_not_found'; - + public const USER_TARGET_ALREADY_EXISTS = 'user_target_already_exists'; /** Teams */ public const TEAM_NOT_FOUND = 'team_not_found'; public const TEAM_INVITE_ALREADY_EXISTS = 'team_invite_already_exists'; @@ -205,6 +205,9 @@ class Exception extends \Exception public const GRAPHQL_NO_QUERY = 'graphql_no_query'; public const GRAPHQL_TOO_MANY_QUERIES = 'graphql_too_many_queries'; + /** Provider */ + public const PROVIDER_NOT_FOUND = 'provider_not_found'; + protected $type = ''; protected $errors = [];