Skip to content

Commit 4f49b77

Browse files
author
Laurie T. Malau
committed
Remove team when sole owner and remove projects
Fixes #6655
1 parent 158acc4 commit 4f49b77

File tree

4 files changed

+90
-2
lines changed

4 files changed

+90
-2
lines changed

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

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

65+
@test(timeout(15000))
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 findTeamWhenUserIsSoleOwnerWithMembers() {
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+
user2.identities.push({ authProviderId: 'GitLab', authId: '4567', authName: 'Dudu', primaryEmail: '[email protected]' });
87+
await this.userDb.storeUser(user2);
88+
89+
const ownTeam = await this.db.createTeam(user.id, 'My Own Team With Members');
90+
await this.db.addMemberToTeam(user2.id, ownTeam.id);
91+
const teams = await this.db.findTeamsByUserAsSoleOwner(user.id);
92+
93+
expect(teams.length).to.eq(1);
94+
expect(teams[0].id).to.eq(ownTeam.id);
95+
96+
}
97+
98+
@test(timeout(10000))
99+
public async findNoTeamWhenCoOwned() {
100+
const user = await this.userDb.newUser();
101+
user.identities.push({ authProviderId: 'GitHub', authId: '2345', authName: 'Nana', primaryEmail: '[email protected]' });
102+
await this.userDb.storeUser(user);
103+
const user2 = await this.userDb.newUser();
104+
user2.identities.push({ authProviderId: 'GitLab', authId: '4567', authName: 'Dudu', primaryEmail: '[email protected]' });
105+
await this.userDb.storeUser(user2);
106+
107+
const jointTeam = await this.db.createTeam(user.id, 'Joint Team');
108+
await this.db.addMemberToTeam(user2.id, jointTeam.id);
109+
await this.db.setTeamMemberRole(user2.id, jointTeam.id, 'owner');
110+
111+
const teams = await this.db.findTeamsByUserAsSoleOwner(user.id);
112+
113+
expect(teams.length).to.eq(0);
114+
}
65115
}
66116

67117
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+
// Find the memberships of this user,
111+
// and among the memberships, get the teams where the user is the sole owner
112+
const soleOwnedTeams = [];
113+
const userTeams = await this.findTeamsByUser(userId);
114+
for (const team of userTeams) {
115+
const memberships = await this.findMembersByTeam(team.id);
116+
const ownerships = memberships.filter(m => m.role === 'owner');
117+
if (ownerships.length === 1 && ownerships[0].userId === userId) {
118+
soleOwnedTeams.push(team);
119+
}
120+
}
121+
return soleOwnedTeams;
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+
// Teams owned only by this user
79+
this.deleteSoleOwnedTeams(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 deleteSoleOwnedTeams(userId: string) {
149+
const ownedTeams = await this.teamDb.findTeamsByUserAsSoleOwner(userId);
150+
151+
for (const team of ownedTeams) {
152+
const teamProjects = await this.projectDb.findTeamProjects(team.id);
153+
await Promise.all(teamProjects.map(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)