From a52ad0ae6148d13c8c20cd0c89025684d9bd956d Mon Sep 17 00:00:00 2001 From: "Laurie T. Malau" Date: Fri, 12 Nov 2021 12:06:05 +0000 Subject: [PATCH] Remove team when sole owner and remove projects Fixes #6655 --- components/gitpod-db/src/team-db.ts | 1 + .../gitpod-db/src/typeorm/team-db-impl.ts | 17 ++++++++++++- .../server/src/user/user-deletion-service.ts | 24 ++++++++++++++++++- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/components/gitpod-db/src/team-db.ts b/components/gitpod-db/src/team-db.ts index 1c08e246613b69..06d658b1860672 100644 --- a/components/gitpod-db/src/team-db.ts +++ b/components/gitpod-db/src/team-db.ts @@ -11,6 +11,7 @@ export interface TeamDB { findTeamById(teamId: string): Promise; findMembersByTeam(teamId: string): Promise; findTeamsByUser(userId: string): Promise; + findTeamsByUserAsSoleOwner(userId: string): Promise; createTeam(userId: string, name: string): Promise; addMemberToTeam(userId: string, teamId: string): Promise; setTeamMemberRole(userId: string, teamId: string, role: TeamMemberRole): Promise; diff --git a/components/gitpod-db/src/typeorm/team-db-impl.ts b/components/gitpod-db/src/typeorm/team-db-impl.ts index 9e5f4d3bc36307..e72ff9d9991963 100644 --- a/components/gitpod-db/src/typeorm/team-db-impl.ts +++ b/components/gitpod-db/src/typeorm/team-db-impl.ts @@ -103,7 +103,22 @@ export class TeamDBImpl implements TeamDB { const membershipRepo = await this.getMembershipRepo(); const memberships = await membershipRepo.find({ userId, deleted: false }); const teams = await teamRepo.findByIds(memberships.map(m => m.teamId)); - return teams.filter(t => !t.deleted); + return teams.filter(t => !t.markedDeleted); + } + + public async findTeamsByUserAsSoleOwner(userId: string): Promise { + const teamRepo = await this.getTeamRepo(); + const membershipRepo = await this.getMembershipRepo(); + + // Find the memberships of this user, + // and among the memberships, get the teams where the user is the sole owner + const soleOwnedTeamIds = await membershipRepo.query(`SELECT tm_2.teamId FROM d_b_team_membership tm_1 + JOIN d_b_team_membership tm_2 ON tm_1.userId = ? AND tm_1.teamId=tm_2.teamId AND tm_1.role = 'owner' GROUP BY tm_2.teamId HAVING COUNT(tm_2.teamId) = 1;`, [userId]); + +// @ts-ignore + const teams = await teamRepo.findByIds(soleOwnedTeamIds.map(m => m.teamId)); + + return teams.filter(t => !t.markedDeleted); } public async createTeam(userId: string, name: string): Promise { diff --git a/components/server/src/user/user-deletion-service.ts b/components/server/src/user/user-deletion-service.ts index 0985b81a23bf2c..eb9fe763a3e6ec 100644 --- a/components/server/src/user/user-deletion-service.ts +++ b/components/server/src/user/user-deletion-service.ts @@ -5,7 +5,7 @@ */ import { injectable, inject } from "inversify"; -import { UserDB, WorkspaceDB, UserStorageResourcesDB, TeamDB } from '@gitpod/gitpod-db/lib'; +import { UserDB, WorkspaceDB, UserStorageResourcesDB, TeamDB, ProjectDB } from '@gitpod/gitpod-db/lib'; import { User, Workspace } from "@gitpod/gitpod-protocol"; import { StorageClient } from "../storage/storage-client"; import { log } from '@gitpod/gitpod-protocol/lib/util/logging'; @@ -23,6 +23,7 @@ export class UserDeletionService { @inject(WorkspaceDB) protected readonly workspaceDb: WorkspaceDB; @inject(UserStorageResourcesDB) protected readonly userStorageResourcesDb: UserStorageResourcesDB; @inject(TeamDB) protected readonly teamDb: TeamDB; + @inject(ProjectDB) protected readonly projectDb: ProjectDB; @inject(StorageClient) protected readonly storageClient: StorageClient; @inject(WorkspaceManagerClientProvider) protected readonly workspaceManagerClientProvider: WorkspaceManagerClientProvider; @inject(WorkspaceDeletionService) protected readonly workspaceDeletionService: WorkspaceDeletionService; @@ -74,8 +75,12 @@ export class UserDeletionService { this.userStorageResourcesDb.deleteAllForUser(user.id), // Bucket this.deleteUserBucket(id), + // Owned teams + this.deleteTeams(id), // Team memberships this.deleteTeamMemberships(id), + // User projects + this.deleteUserProjects(id), ]); // Track the deletion Event for Analytics Purposes @@ -140,6 +145,23 @@ export class UserDeletionService { await Promise.all(teams.map(t => this.teamDb.removeMemberFromTeam(userId, t.id))); } + protected async deleteTeams(userId: string) { + const ownedTeams = await this.teamDb.findTeamsByUserAsSoleOwner(userId); + + ownedTeams.forEach(async team => { + const teamProjects = this.projectDb.findTeamProjects(team.id); + (await teamProjects).forEach(project => this.projectDb.markDeleted(project.id)); + }) + + await Promise.all(ownedTeams.map(t => this.teamDb.deleteTeam(t.id))); + } + + protected async deleteUserProjects(id: string) { + const userProjects = await this.projectDb.findUserProjects(id); + + await Promise.all(userProjects.map(project => this.projectDb.markDeleted(project.id))); + } + anonymizeWorkspace(ws: Workspace) { ws.context.title = 'deleted-title'; ws.context.normalizedContextURL = 'deleted-normalizedContextURL';