Skip to content

Commit b2ee55e

Browse files
committed
[server, db] Cleanup UpdateOrgSettings API handling
Tool: gitpod/catfood.gitpod.cloud
1 parent fd2825e commit b2ee55e

File tree

10 files changed

+262
-217
lines changed

10 files changed

+262
-217
lines changed

components/gitpod-db/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"prom-client": "^14.2.0",
4444
"reflect-metadata": "^0.1.13",
4545
"slugify": "^1.6.5",
46+
"ts-deepmerge": "^7.0.2",
4647
"the-big-username-blacklist": "^1.5.2",
4748
"typeorm": "0.2.38"
4849
},

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,14 @@ export interface TeamDB extends TransactionalDB<TeamDB> {
4242
deleteTeam(teamId: string): Promise<void>;
4343

4444
findOrgSettings(teamId: string): Promise<OrganizationSettings | undefined>;
45-
setOrgSettings(teamId: string, settings: Partial<OrganizationSettings>): Promise<OrganizationSettings>;
45+
setOrgSettings(
46+
teamId: string,
47+
settings: Partial<OrganizationSettings>,
48+
merge: (
49+
currentSettings: OrganizationSettings,
50+
partialUpdate: Partial<OrganizationSettings>,
51+
) => OrganizationSettings,
52+
): Promise<OrganizationSettings>;
4653

4754
hasActiveSSO(organizationId: string): Promise<boolean>;
4855

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

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -394,18 +394,27 @@ export class TeamDBImpl extends TransactionalDBImpl<TeamDB> implements TeamDB {
394394
});
395395
}
396396

397-
public async setOrgSettings(orgId: string, settings: Partial<OrganizationSettings>): Promise<OrganizationSettings> {
398-
const repo = await this.getOrgSettingsRepo();
399-
const team = await repo.findOne({ where: { orgId, deleted: false } });
400-
if (!team) {
401-
return await repo.save({
402-
...settings,
403-
orgId,
404-
});
405-
}
406-
return await repo.save({
407-
...team,
408-
...settings,
397+
public async setOrgSettings(
398+
orgId: string,
399+
partialUpdate: Partial<OrganizationSettings>,
400+
merge: (
401+
currentSettings: OrganizationSettings,
402+
partialUpdate: Partial<OrganizationSettings>,
403+
) => OrganizationSettings,
404+
): Promise<OrganizationSettings> {
405+
return this.transaction(async (db) => {
406+
const teamDb = db as TeamDBImpl;
407+
const repo = await teamDb.getOrgSettingsRepo();
408+
const currentSettings = await repo.findOne({ where: { orgId, deleted: false } });
409+
if (!currentSettings) {
410+
return await repo.save({
411+
...partialUpdate,
412+
orgId,
413+
});
414+
}
415+
416+
const settings = merge(currentSettings, partialUpdate);
417+
return await repo.save(settings);
409418
});
410419
}
411420

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -285,11 +285,14 @@ export interface OnboardingSettings {
285285
/**
286286
* the welcome message for new members of the organization
287287
*/
288-
welcomeMessage?: {
289-
featuredMemberId?: string;
290-
message?: string;
291-
footer?: string;
292-
};
288+
welcomeMessage?: WelcomeMessage;
289+
}
290+
291+
export interface WelcomeMessage {
292+
enabled?: boolean;
293+
featuredMemberId?: string;
294+
featuredMemberResolvedAvatarUrl?: string;
295+
message?: string;
293296
}
294297

295298
export type TeamMemberInfo = OrgMemberInfo;

components/public-api/typescript-common/src/public-api-converter.ts

Lines changed: 104 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ import {
5656
Organization as ProtocolOrganization,
5757
OrgMemberPermission,
5858
OrgMemberRole,
59+
RoleRestrictions,
60+
TimeoutSettings as TimeoutSettingsProtocol,
61+
OnboardingSettings as OnboardingSettingsProtocol,
62+
WelcomeMessage as WelcomeMessageProtocol,
5963
} from "@gitpod/gitpod-protocol/lib/teams-projects-protocol";
6064
import type { DeepPartial } from "@gitpod/gitpod-protocol/lib/util/deep-partial";
6165
import { parseGoDurationToMs } from "@gitpod/gitpod-protocol/lib/util/timeutil";
@@ -113,11 +117,15 @@ import {
113117
OnboardingState,
114118
} from "@gitpod/public-api/lib/gitpod/v1/installation_pb";
115119
import {
120+
OnboardingSettings,
121+
OnboardingSettings_WelcomeMessage,
116122
Organization,
117123
OrganizationMember,
118124
OrganizationPermission,
119125
OrganizationRole,
120126
OrganizationSettings,
127+
RoleRestrictionEntry,
128+
TimeoutSettings,
121129
} from "@gitpod/public-api/lib/gitpod/v1/organization_pb";
122130
import {
123131
Prebuild,
@@ -1040,6 +1048,61 @@ export class PublicAPIConverter {
10401048
}
10411049
}
10421050

1051+
fromOrgMemberRoleString(role: string): OrgMemberRole {
1052+
switch (role) {
1053+
case "owner":
1054+
case "member":
1055+
case "collaborator":
1056+
return role;
1057+
default:
1058+
throw new Error("invalid org member role");
1059+
}
1060+
}
1061+
1062+
fromTimeoutSettings(timeoutSettings: TimeoutSettings): TimeoutSettingsProtocol {
1063+
const result: TimeoutSettingsProtocol = {
1064+
denyUserTimeouts: timeoutSettings.denyUserTimeouts,
1065+
};
1066+
if (timeoutSettings.inactivity) {
1067+
result.inactivity = this.toDurationString(timeoutSettings.inactivity);
1068+
}
1069+
return result;
1070+
}
1071+
1072+
fromRoleRestrictions(roleRestrictions: RoleRestrictionEntry[]): RoleRestrictions {
1073+
const result: RoleRestrictions = {};
1074+
for (const roleRestriction of roleRestrictions) {
1075+
const role = this.fromOrgMemberRole(roleRestriction.role);
1076+
const permissions = roleRestriction.permissions.map((p) => this.fromOrganizationPermission(p));
1077+
result[role] = permissions;
1078+
}
1079+
return result;
1080+
}
1081+
1082+
fromOnboardingSettings(onboardingSettings: OnboardingSettings): OnboardingSettingsProtocol {
1083+
const result: OnboardingSettingsProtocol = {
1084+
internalLink: onboardingSettings.internalLink,
1085+
};
1086+
1087+
if (onboardingSettings.welcomeMessage) {
1088+
result.welcomeMessage = this.fromWelcomeMessage(onboardingSettings.welcomeMessage);
1089+
}
1090+
1091+
if (onboardingSettings.updateRecommendedRepositories) {
1092+
result.recommendedRepositories = onboardingSettings.recommendedRepositories;
1093+
}
1094+
1095+
return result;
1096+
}
1097+
1098+
fromWelcomeMessage(welcomeMessage: OnboardingSettings_WelcomeMessage): WelcomeMessageProtocol {
1099+
return {
1100+
enabled: welcomeMessage.enabled,
1101+
message: welcomeMessage.message,
1102+
featuredMemberId: welcomeMessage.featuredMemberId,
1103+
};
1104+
}
1105+
10431106
fromWorkspaceSettings(settings?: DeepPartial<WorkspaceSettings>) {
10441107
const result: Partial<
10451108
Pick<
@@ -1132,26 +1195,52 @@ export class PublicAPIConverter {
11321195
pinnedEditorVersions: settings.pinnedEditorVersions || {},
11331196
restrictedEditorNames: settings.restrictedEditorNames || [],
11341197
defaultRole: settings.defaultRole || undefined,
1135-
timeoutSettings: {
1136-
inactivity: settings.timeoutSettings?.inactivity
1137-
? this.toDuration(settings.timeoutSettings?.inactivity)
1138-
: undefined,
1139-
denyUserTimeouts: settings.timeoutSettings?.denyUserTimeouts,
1140-
},
1141-
roleRestrictions: Object.entries(settings.roleRestrictions ?? {}).map(([role, permissions]) => ({
1142-
role: this.toOrgMemberRole(role as OrgMemberRole),
1143-
permissions: permissions.map((permission) => this.toOrganizationPermission(permission)),
1144-
})),
1198+
timeoutSettings: settings.timeoutSettings ? this.toTimeoutSettings(settings.timeoutSettings) : undefined,
1199+
roleRestrictions: settings.roleRestrictions
1200+
? this.toRoleRestrictions(settings.roleRestrictions)
1201+
: undefined,
11451202
maxParallelRunningWorkspaces: settings.maxParallelRunningWorkspaces ?? 0,
1146-
onboardingSettings: {
1147-
internalLink: settings?.onboardingSettings?.internalLink ?? undefined,
1148-
welcomeMessage: settings?.onboardingSettings?.welcomeMessage ?? undefined,
1149-
recommendedRepositories: settings?.onboardingSettings?.recommendedRepositories ?? [],
1150-
},
1203+
onboardingSettings: settings?.onboardingSettings
1204+
? this.toOnboardingSettings(settings.onboardingSettings)
1205+
: undefined,
11511206
annotateGitCommits: settings.annotateGitCommits ?? false,
11521207
});
11531208
}
11541209

1210+
toTimeoutSettings(settings: TimeoutSettingsProtocol): TimeoutSettings {
1211+
return new TimeoutSettings({
1212+
inactivity: settings.inactivity ? this.toDuration(settings.inactivity) : undefined,
1213+
denyUserTimeouts: settings.denyUserTimeouts,
1214+
});
1215+
}
1216+
1217+
toRoleRestrictions(roleRestrictions: RoleRestrictions): RoleRestrictionEntry[] {
1218+
return Object.entries(roleRestrictions ?? {}).map(
1219+
([role, permissions]) =>
1220+
new RoleRestrictionEntry({
1221+
role: this.toOrgMemberRole(role as OrgMemberRole),
1222+
permissions: permissions.map((permission) => this.toOrganizationPermission(permission)),
1223+
}),
1224+
);
1225+
}
1226+
1227+
toOnboardingSettings(settings: OnboardingSettingsProtocol): OnboardingSettings {
1228+
return new OnboardingSettings({
1229+
internalLink: settings.internalLink,
1230+
welcomeMessage: settings.welcomeMessage ? this.toWelcomeMessage(settings.welcomeMessage) : undefined,
1231+
recommendedRepositories: settings.recommendedRepositories,
1232+
});
1233+
}
1234+
1235+
toWelcomeMessage(settings: WelcomeMessageProtocol): OnboardingSettings_WelcomeMessage {
1236+
return new OnboardingSettings_WelcomeMessage({
1237+
enabled: settings.enabled,
1238+
message: settings.message,
1239+
featuredMemberId: settings.featuredMemberId,
1240+
featuredMemberResolvedAvatarUrl: settings.featuredMemberResolvedAvatarUrl,
1241+
});
1242+
}
1243+
11551244
toConfiguration(project: Project): Configuration {
11561245
const result = new Configuration();
11571246
result.id = project.id;

0 commit comments

Comments
 (0)