diff --git a/components/dashboard/src/projects/Projects.tsx b/components/dashboard/src/projects/Projects.tsx index 97f72e170bf834..17e0c6db94cdae 100644 --- a/components/dashboard/src/projects/Projects.tsx +++ b/components/dashboard/src/projects/Projects.tsx @@ -21,7 +21,7 @@ import ConfirmationModal from "../components/ConfirmationModal"; import { prebuildStatusIcon } from "./Prebuilds"; import Alert from "../components/Alert"; import { FeatureFlagContext } from "../contexts/FeatureFlagContext"; -import { listAllProjects } from "../service/public-api"; +import { listAllProjects, projectsService } from "../service/public-api"; import { UserContext } from "../user-context"; export default function () { @@ -86,7 +86,9 @@ export default function () { const onRemoveProject = async (p: Project) => { setRemoveModalVisible(false); - await getGitpodService().server.deleteProject(p.id); + usePublicApiProjectsService + ? await projectsService.deleteProject({ projectId: p.id }) + : await getGitpodService().server.deleteProject(p.id); await updateProjects(); }; diff --git a/components/public-api-server/pkg/apiv1/project.go b/components/public-api-server/pkg/apiv1/project.go index 070cc25d6f9fb6..314f609b9c1875 100644 --- a/components/public-api-server/pkg/apiv1/project.go +++ b/components/public-api-server/pkg/apiv1/project.go @@ -144,6 +144,25 @@ func (s *ProjectsService) ListProjects(ctx context.Context, req *connect.Request }), nil } +func (s *ProjectsService) DeleteProject(ctx context.Context, req *connect.Request[v1.DeleteProjectRequest]) (*connect.Response[v1.DeleteProjectResponse], error) { + projectID, err := validateProjectID(req.Msg.GetProjectId()) + if err != nil { + return nil, err + } + + conn, err := s.getConnection(ctx) + if err != nil { + return nil, err + } + + err = conn.DeleteProject(ctx, projectID.String()) + if err != nil { + return nil, proxy.ConvertError(err) + } + + return connect.NewResponse(&v1.DeleteProjectResponse{}), nil +} + func (s *ProjectsService) getConnection(ctx context.Context) (protocol.APIInterface, error) { token, err := auth.TokenFromContext(ctx) if err != nil { @@ -210,3 +229,18 @@ func workspaceClassesToAPIResponse(s *protocol.WorkspaceClassesSettings) *v1.Wor Prebuild: s.Prebuild, } } + +func validateProjectID(id string) (uuid.UUID, error) { + trimmed := strings.TrimSpace(id) + + if trimmed == "" { + return uuid.Nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("Project ID is a required argument.")) + } + + projectID, err := uuid.Parse(trimmed) + if err != nil { + return uuid.Nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("Project ID must be a valid UUID.")) + } + + return projectID, nil +} diff --git a/components/public-api-server/pkg/apiv1/project_test.go b/components/public-api-server/pkg/apiv1/project_test.go index ef46edf451e444..0ee81920a5a7d0 100644 --- a/components/public-api-server/pkg/apiv1/project_test.go +++ b/components/public-api-server/pkg/apiv1/project_test.go @@ -313,6 +313,43 @@ func TestProjectsService_ListProjects(t *testing.T) { }) } +func TestProjectsService_DeleteProject(t *testing.T) { + + t.Run("invalid argument when project ID is empty", func(t *testing.T) { + _, client := setupProjectsService(t) + + _, err := client.DeleteProject(context.Background(), connect.NewRequest(&v1.DeleteProjectRequest{ + ProjectId: "", + })) + require.Error(t, err) + require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) + }) + + t.Run("invalid argument when project ID is not a valid uuid", func(t *testing.T) { + _, client := setupProjectsService(t) + + _, err := client.DeleteProject(context.Background(), connect.NewRequest(&v1.DeleteProjectRequest{ + ProjectId: "something", + })) + require.Error(t, err) + require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) + }) + + t.Run("proxies to server", func(t *testing.T) { + serverMock, client := setupProjectsService(t) + + projectID := uuid.New().String() + + serverMock.EXPECT().DeleteProject(gomock.Any(), projectID).Return(nil) + + resp, err := client.DeleteProject(context.Background(), connect.NewRequest(&v1.DeleteProjectRequest{ + ProjectId: projectID, + })) + require.NoError(t, err) + requireEqualProto(t, &v1.DeleteProjectResponse{}, resp.Msg) + }) +} + func setupProjectsService(t *testing.T) (*protocol.MockAPIInterface, v1connect.ProjectsServiceClient) { t.Helper()