diff --git a/app/config/roles.php b/app/config/roles.php index f0039841d2..84820362cc 100644 --- a/app/config/roles.php +++ b/app/config/roles.php @@ -84,7 +84,7 @@ return [ ], Auth::USER_ROLE_OWNER => [ 'label' => 'Owner', - 'scopes' => \array_merge($member, $admins, []), + 'scopes' => \array_merge($member, $admins, [ 'projects.transfer' ]), ], Auth::USER_ROLE_APPS => [ 'label' => 'Applications', diff --git a/app/config/services.php b/app/config/services.php index e0d5e263f2..5ac7625176 100644 --- a/app/config/services.php +++ b/app/config/services.php @@ -108,6 +108,19 @@ return [ 'optional' => false, 'icon' => '', ], + 'project' => [ + 'key' => 'project', + 'name' => 'Project', + 'subtitle' => 'The Project service allows you to manage all the projects in your Appwrite server.', + 'description' => '', + 'controller' => 'api/project.php', + 'sdk' => true, + 'docs' => true, + 'docsUrl' => '', + 'tests' => false, + 'optional' => false, + 'icon' => '', + ], 'storage' => [ 'key' => 'storage', 'name' => 'Storage', diff --git a/app/controllers/api/project.php b/app/controllers/api/project.php new file mode 100644 index 0000000000..50d42e2951 --- /dev/null +++ b/app/controllers/api/project.php @@ -0,0 +1,52 @@ +desc('Update Project Team') + ->groups(['api', 'project']) + ->label('scope', 'projects.transfer') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'project') + ->label('sdk.method', 'updateTeam') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_PROJECT) + ->param('projectId', '', new UID(), 'Project unique ID.') + ->param('teamId', '', new UID(), 'New Team ID.') + ->inject('response') + ->inject('user') + ->inject('dbForConsole') + ->inject('deletes') + ->action(function (string $projectId, string $teamId, Response $response, Document $user, Database $dbForConsole, Delete $deletes) { + $project = $dbForConsole->getDocument('projects', $projectId); + + if ($project->isEmpty()) { + throw new Exception(Exception::PROJECT_NOT_FOUND); + } + + $team = $dbForConsole->getDocument('teams', $teamId); + + if ($team->isEmpty()) { + throw new Exception(Exception::TEAM_NOT_FOUND); + } + + if ($team->getInternalId() === $project->getAttribute('teamInternalId')) { + throw new Exception(Exception::PROJECT_TEAM_ALREADY_MATCHES); + } + + $project + ->setAttribute('teamId', $team->getId()) + ->setAttribute('teamInternalId', $team->getInternalId()) + ; + + $project = $dbForConsole->updateDocument('projects', $project->getId(), $project); + + $response->dynamic($project, Response::MODEL_PROJECT); + }); \ No newline at end of file diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index c642f59274..f8e07c12f9 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -646,52 +646,6 @@ App::delete('/v1/projects/:projectId') $response->noContent(); }); - -App::patch('/v1/projects/:projectId/team') - ->desc('Update Project Team') - ->groups(['api', 'projects']) - ->label('scope', 'projects.write') - ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) - ->label('sdk.namespace', 'projects') - ->label('sdk.method', 'updateTeam') - ->label('sdk.response.code', Response::STATUS_CODE_OK) - ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) - ->label('sdk.response.model', Response::MODEL_PROJECT) - ->param('projectId', '', new UID(), 'Project unique ID.') - ->param('teamId', '', new UID(), 'New Team ID.') - ->inject('response') - ->inject('user') - ->inject('dbForConsole') - ->inject('deletes') - ->action(function (string $projectId, string $teamId, Response $response, Document $user, Database $dbForConsole, Delete $deletes) { - // TODO: Must be owner of project - - $project = $dbForConsole->getDocument('projects', $projectId); - - if ($project->isEmpty()) { - throw new Exception(Exception::PROJECT_NOT_FOUND); - } - - $team = $dbForConsole->getDocument('teams', $teamId); - - if ($team->isEmpty()) { - throw new Exception(Exception::TEAM_NOT_FOUND); - } - - if ($team->getInternalId() === $project->getAttribute('teamInternalId')) { - throw new Exception(Exception::PROJECT_TEAM_ALREADY_MATCHES); - } - - $project - ->setAttribute('teamId', $team->getId()) - ->setAttribute('teamInternalId', $team->getInternalId()) - ; - - $project = $dbForConsole->updateDocument('projects', $project->getId(), $project); - - $response->dynamic($project, Response::MODEL_PROJECT); - }); - // Webhooks App::post('/v1/projects/:projectId/webhooks') diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 9f12e7d6e8..89728011a9 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -2749,6 +2749,26 @@ class ProjectsConsoleClientTest extends Scope $team2Id = $team2['body']['$id']; + $response = $this->client->call(Client::METHOD_GET, "/teams/{$team1Id}/memberships", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(1, $response['body']['total']); + + $membership1Id = $response['body']['memberships'][0]['$id']; + + $response = $this->client->call(Client::METHOD_GET, "/teams/{$team2Id}/memberships", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals(1, $response['body']['total']); + + $membership2Id = $response['body']['memberships'][0]['$id']; + // Create project in team 1 $project = $this->client->call(Client::METHOD_POST, '/projects', array_merge([ 'content-type' => 'application/json', @@ -2787,9 +2807,10 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(0, $response['body']['total']); // Transfer project - $project = $this->client->call(Client::METHOD_PATCH, "/projects/{$projectId}/team", array_merge([ + $project = $this->client->call(Client::METHOD_PATCH, "/project/{$projectId}/team", array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $projectId, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), [ 'teamId' => $team2Id ]); @@ -2827,31 +2848,56 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals(0, $response['body']['total']); // Test failures - $project = $this->client->call(Client::METHOD_PATCH, "/projects/nonExistingProjectId/team", array_merge([ + $project = $this->client->call(Client::METHOD_PATCH, "/project/nonExistingProjectId/team", array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $projectId, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), [ 'teamId' => $team1Id ]); $this->assertEquals(404, $project['headers']['status-code']); - $project = $this->client->call(Client::METHOD_PATCH, "/projects/{$projectId}/team", array_merge([ + $project = $this->client->call(Client::METHOD_PATCH, "/project/{$projectId}/team", array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $projectId, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), [ 'teamId' => 'nonExistingTeamId' ]); $this->assertEquals(404, $project['headers']['status-code']); - $project = $this->client->call(Client::METHOD_PATCH, "/projects/{$projectId}/team", array_merge([ + $project = $this->client->call(Client::METHOD_PATCH, "/project/{$projectId}/team", array_merge([ 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], + 'x-appwrite-project' => $projectId, + 'x-appwrite-mode' => 'admin' ], $this->getHeaders()), [ 'teamId' => $team2Id ]); $this->assertEquals(400, $project['headers']['status-code']); + + $project = $this->client->call(Client::METHOD_PATCH, "/teams/{$team2Id}/memberships/{$membership2Id}", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'roles' => ['member'] + ]); + + $this->assertEquals(200, $project['headers']['status-code']); + $this->assertCount(1, $project['body']['roles']); + $this->assertContains('member', $project['body']['roles']); + $this->assertNotContains('owner', $project['body']['roles']); + + $project = $this->client->call(Client::METHOD_PATCH, "/project/{$projectId}/team", array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-mode' => 'admin' + ], $this->getHeaders()), [ + 'teamId' => $team1Id + ]); + + $this->assertEquals(401, $project['headers']['status-code']); } }