Skip to content

Commit 70fbd62

Browse files
committed
[orgs] Persist slug
1 parent 18281e4 commit 70fbd62

File tree

7 files changed

+49
-14
lines changed

7 files changed

+49
-14
lines changed

components/gitpod-db/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@jmondi/oauth2-server": "^2.6.1",
2828
"mysql": "^2.18.1",
2929
"reflect-metadata": "^0.1.13",
30+
"slugify": "^1.6.5",
3031
"the-big-username-blacklist": "^1.5.2",
3132
"typeorm": "0.2.38"
3233
},

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
import { Team } from "@gitpod/gitpod-protocol";
88
import { Entity, Column, PrimaryColumn } from "typeorm";
9-
import { Transformer } from "../transformer";
109
import { TypeORM } from "../typeorm";
1110

1211
@Entity()
@@ -18,12 +17,8 @@ export class DBTeam implements Team {
1817
@Column("varchar")
1918
name: string;
2019

21-
// Deprecated.
22-
@Column({
23-
type: "varchar",
24-
transformer: Transformer.ALWAYS_EMPTY_STRING,
25-
})
26-
slug: string = "";
20+
@Column("varchar")
21+
slug: string;
2722

2823
@Column("varchar")
2924
creationTime: string;

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

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ import { inject, injectable } from "inversify";
99
import { TypeORM } from "./typeorm";
1010
import { Repository } from "typeorm";
1111
import { v4 as uuidv4 } from "uuid";
12+
import { randomBytes } from "crypto";
1213
import { TeamDB } from "../team-db";
1314
import { DBTeam } from "./entity/db-team";
1415
import { DBTeamMembership } from "./entity/db-team-membership";
1516
import { DBUser } from "./entity/db-user";
1617
import { DBTeamMembershipInvite } from "./entity/db-team-membership-invite";
1718
import { ResponseError } from "vscode-jsonrpc";
1819
import { ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
20+
import slugify from "slugify";
1921

2022
@injectable()
2123
export class TeamDBImpl implements TeamDB {
@@ -121,17 +123,40 @@ export class TeamDBImpl implements TeamDB {
121123
return soleOwnedTeams;
122124
}
123125

124-
public async updateTeam(teamId: string, team: Pick<Team, "name">): Promise<Team> {
126+
public async updateTeam(teamId: string, team: Pick<Team, "name" | "slug">): Promise<Team> {
127+
const name = team.name && team.name.trim();
128+
const slug = team.slug && team.slug.trim();
129+
if (!name && !slug) {
130+
throw new ResponseError(ErrorCodes.BAD_REQUEST, "No update provided");
131+
}
132+
125133
const teamRepo = await this.getTeamRepo();
126134
const existingTeam = await teamRepo.findOne({ id: teamId, deleted: false, markedDeleted: false });
127135
if (!existingTeam) {
128136
throw new ResponseError(ErrorCodes.NOT_FOUND, "Organization not found");
129137
}
130-
const name = team.name && team.name.trim();
131-
if (!name || name.length === 0 || name.length > 32) {
132-
throw new ResponseError(ErrorCodes.INVALID_VALUE, "The name must be between 1 and 32 characters long");
138+
139+
// no changes
140+
if (existingTeam.name === name && existingTeam.slug === slug) {
141+
return existingTeam;
142+
}
143+
144+
if (!!name) {
145+
if (name.length > 32) {
146+
throw new ResponseError(ErrorCodes.INVALID_VALUE, "The name must be between 1 and 32 characters long");
147+
}
148+
existingTeam.name = name;
133149
}
134-
existingTeam.name = name;
150+
if (!!slug) {
151+
if (slug.length > 100) {
152+
throw new ResponseError(ErrorCodes.INVALID_VALUE, "Slug must be between 1 and 100 characters long");
153+
}
154+
if (!/^[A-Za-z0-9-]+$/.test(slug)) {
155+
throw new ResponseError(ErrorCodes.BAD_REQUEST, "Slug must contain only letters, or numbers.");
156+
}
157+
existingTeam.slug = slug;
158+
}
159+
135160
return teamRepo.save(existingTeam);
136161
}
137162

@@ -146,10 +171,18 @@ export class TeamDBImpl implements TeamDB {
146171
);
147172
}
148173

174+
let slug = slugify(name, { lower: true });
149175
const teamRepo = await this.getTeamRepo();
176+
177+
const existingTeam = await teamRepo.findOne({ slug, deleted: false, markedDeleted: false });
178+
if (!!existingTeam) {
179+
slug = slug + "-" + randomBytes(10).toString("hex");
180+
}
181+
150182
const team: Team = {
151183
id: uuidv4(),
152184
name,
185+
slug,
153186
creationTime: new Date().toISOString(),
154187
};
155188
await teamRepo.save(team);

components/gitpod-protocol/src/gitpod-service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
174174

175175
// Teams
176176
getTeam(teamId: string): Promise<Team>;
177-
updateTeam(teamId: string, team: Pick<Team, "name">): Promise<Team>;
177+
updateTeam(teamId: string, team: Pick<Team, "name" | "slug">): Promise<Team>;
178178
getTeams(): Promise<Team[]>;
179179
getTeamMembers(teamId: string): Promise<TeamMemberInfo[]>;
180180
createTeam(name: string): Promise<Team>;

components/gitpod-protocol/src/teams-projects-protocol.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ export type Team = Organization;
135135
export interface Organization {
136136
id: string;
137137
name: string;
138+
slug?: string;
138139
creationTime: string;
139140
markedDeleted?: boolean;
140141
/** This is a flag that triggers the HARD DELETION of this entity */

components/server/src/workspace/gitpod-server-impl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2246,7 +2246,7 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable {
22462246
return team;
22472247
}
22482248

2249-
public async updateTeam(ctx: TraceContext, teamId: string, team: Pick<Team, "name">): Promise<Team> {
2249+
public async updateTeam(ctx: TraceContext, teamId: string, team: Pick<Team, "name" | "slug">): Promise<Team> {
22502250
traceAPIParams(ctx, { teamId });
22512251
this.checkUser("updateTeam");
22522252

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16359,6 +16359,11 @@ slice-ansi@^4.0.0:
1635916359
astral-regex "^2.0.0"
1636016360
is-fullwidth-code-point "^3.0.0"
1636116361

16362+
slugify@^1.6.5:
16363+
version "1.6.5"
16364+
resolved "https://registry.yarnpkg.com/slugify/-/slugify-1.6.5.tgz#c8f5c072bf2135b80703589b39a3d41451fbe8c8"
16365+
integrity sha512-8mo9bslnBO3tr5PEVFzMPIWwWnipGS0xVbYf65zxDqfNwmzYn1LpiKNrR6DlClusuvo+hDHd1zKpmfAe83NQSQ==
16366+
1636216367
smart-buffer@^4.1.0:
1636316368
version "4.2.0"
1636416369
resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz"

0 commit comments

Comments
 (0)