Skip to content

Commit c4e44d6

Browse files
author
Laurie T. Malau
committed
Remove team when sole owner and remove projects
Fixes #6655
1 parent f3ecc7f commit c4e44d6

File tree

4 files changed

+72
-2
lines changed

4 files changed

+72
-2
lines changed

components/gitpod-db/src/team-db.spec.db.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,38 @@ import { DBIdentity } from './typeorm/entity/db-identity';
6262
expect(members[0].primaryEmail).to.eq('[email protected]');
6363
}
6464

65+
@test(timeout(10000))
66+
public async findTeamWhenUserIsSoleOwner() {
67+
const user = await this.userDb.newUser();
68+
user.identities.push({ authProviderId: 'GitHub', authId: '2345', authName: 'Nana', primaryEmail: '[email protected]' });
69+
await this.userDb.storeUser(user);
70+
71+
const ownTeam = await this.db.createTeam(user.id, 'My Own Team');
72+
73+
const teams = await this.db.findTeamsByUserAsSoleOwner(user.id);
74+
75+
expect(teams.length).to.eq(1);
76+
expect(teams[0].id).to.eq(ownTeam.id);
77+
78+
}
79+
80+
@test(timeout(10000))
81+
public async findNoTeamWhenCoOwned() {
82+
const user = await this.userDb.newUser();
83+
user.identities.push({ authProviderId: 'GitHub', authId: '2345', authName: 'Nana', primaryEmail: '[email protected]' });
84+
await this.userDb.storeUser(user);
85+
const user2 = await this.userDb.newUser();
86+
user.identities.push({ authProviderId: 'GitLab', authId: '4567', authName: 'Dudu', primaryEmail: '[email protected]' });
87+
await this.userDb.storeUser(user2);
88+
89+
const jointTeam = await this.db.createTeam(user.id, 'Joint Team');
90+
await this.db.addMemberToTeam(user2.id, jointTeam.id);
91+
await this.db.setTeamMemberRole(user2.id, jointTeam.id, 'owner');
92+
93+
const teams = await this.db.findTeamsByUserAsSoleOwner(user.id);
94+
95+
expect(teams.length).to.eq(0);
96+
}
6597
}
6698

6799
module.exports = new TeamDBSpec()

components/gitpod-db/src/team-db.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface TeamDB {
1111
findTeamById(teamId: string): Promise<Team | undefined>;
1212
findMembersByTeam(teamId: string): Promise<TeamMemberInfo[]>;
1313
findTeamsByUser(userId: string): Promise<Team[]>;
14+
findTeamsByUserAsSoleOwner(userId: string): Promise<Team[]>;
1415
createTeam(userId: string, name: string): Promise<Team>;
1516
addMemberToTeam(userId: string, teamId: string): Promise<void>;
1617
setTeamMemberRole(userId: string, teamId: string, role: TeamMemberRole): Promise<void>;

components/gitpod-db/src/typeorm/team-db-impl.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,22 @@ export class TeamDBImpl implements TeamDB {
103103
const membershipRepo = await this.getMembershipRepo();
104104
const memberships = await membershipRepo.find({ userId, deleted: false });
105105
const teams = await teamRepo.findByIds(memberships.map(m => m.teamId));
106-
return teams.filter(t => !t.deleted);
106+
return teams.filter(t => !t.markedDeleted);
107+
}
108+
109+
public async findTeamsByUserAsSoleOwner(userId: string): Promise<Team[]> {
110+
const teamRepo = await this.getTeamRepo();
111+
const membershipRepo = await this.getMembershipRepo();
112+
113+
// Find the memberships of this user,
114+
// and among the memberships, get the teams where the user is the sole owner
115+
const soleOwnedTeamIds = await membershipRepo.query(`SELECT tm_2.teamId FROM d_b_team_membership tm_1
116+
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]);
117+
118+
// @ts-ignore
119+
const teams = await teamRepo.findByIds(soleOwnedTeamIds.map(m => m.teamId));
120+
121+
return teams.filter(t => !t.markedDeleted);
107122
}
108123

109124
public async createTeam(userId: string, name: string): Promise<Team> {

components/server/src/user/user-deletion-service.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import { injectable, inject } from "inversify";
8-
import { UserDB, WorkspaceDB, UserStorageResourcesDB, TeamDB } from '@gitpod/gitpod-db/lib';
8+
import { UserDB, WorkspaceDB, UserStorageResourcesDB, TeamDB, ProjectDB } from '@gitpod/gitpod-db/lib';
99
import { User, Workspace } from "@gitpod/gitpod-protocol";
1010
import { StorageClient } from "../storage/storage-client";
1111
import { log } from '@gitpod/gitpod-protocol/lib/util/logging';
@@ -23,6 +23,7 @@ export class UserDeletionService {
2323
@inject(WorkspaceDB) protected readonly workspaceDb: WorkspaceDB;
2424
@inject(UserStorageResourcesDB) protected readonly userStorageResourcesDb: UserStorageResourcesDB;
2525
@inject(TeamDB) protected readonly teamDb: TeamDB;
26+
@inject(ProjectDB) protected readonly projectDb: ProjectDB;
2627
@inject(StorageClient) protected readonly storageClient: StorageClient;
2728
@inject(WorkspaceManagerClientProvider) protected readonly workspaceManagerClientProvider: WorkspaceManagerClientProvider;
2829
@inject(WorkspaceDeletionService) protected readonly workspaceDeletionService: WorkspaceDeletionService;
@@ -74,8 +75,12 @@ export class UserDeletionService {
7475
this.userStorageResourcesDb.deleteAllForUser(user.id),
7576
// Bucket
7677
this.deleteUserBucket(id),
78+
// Owned teams
79+
this.deleteTeams(id),
7780
// Team memberships
7881
this.deleteTeamMemberships(id),
82+
// User projects
83+
this.deleteUserProjects(id),
7984
]);
8085

8186
// Track the deletion Event for Analytics Purposes
@@ -140,6 +145,23 @@ export class UserDeletionService {
140145
await Promise.all(teams.map(t => this.teamDb.removeMemberFromTeam(userId, t.id)));
141146
}
142147

148+
protected async deleteTeams(userId: string) {
149+
const ownedTeams = await this.teamDb.findTeamsByUserAsSoleOwner(userId);
150+
151+
ownedTeams.forEach(async team => {
152+
const teamProjects = this.projectDb.findTeamProjects(team.id);
153+
(await teamProjects).forEach(project => this.projectDb.markDeleted(project.id));
154+
})
155+
156+
await Promise.all(ownedTeams.map(t => this.teamDb.deleteTeam(t.id)));
157+
}
158+
159+
protected async deleteUserProjects(id: string) {
160+
const userProjects = await this.projectDb.findUserProjects(id);
161+
162+
await Promise.all(userProjects.map(project => this.projectDb.markDeleted(project.id)));
163+
}
164+
143165
anonymizeWorkspace(ws: Workspace) {
144166
ws.context.title = 'deleted-title';
145167
ws.context.normalizedContextURL = 'deleted-normalizedContextURL';

0 commit comments

Comments
 (0)