Skip to content

Commit 17a92b9

Browse files
committed
[server] Introduce RequestContext
1 parent 5c70155 commit 17a92b9

25 files changed

+520
-197
lines changed

components/gitpod-protocol/src/analytics.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66

77
export const IAnalyticsWriter = Symbol("IAnalyticsWriter");
88

9-
type Identity =
10-
| { userId: string | number; anonymousId?: string | number }
11-
| { userId?: string | number; anonymousId: string | number };
9+
type Identity = { userId?: string | number; anonymousId?: string | number; subjectId?: string };
1210

1311
interface Message {
1412
messageId?: string;

components/gitpod-protocol/src/messaging/error.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ export const ErrorCodes = {
8989
// 404 Not Found
9090
NOT_FOUND: 404 as const,
9191

92+
// 408 Request Timeout
93+
REQUEST_TIMEOUT: 408 as const,
94+
9295
// 409 Conflict (e.g. already existing)
9396
CONFLICT: 409 as const,
9497

components/gitpod-protocol/src/util/logging.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export interface LogContext {
1717
organizationId?: string;
1818
sessionId?: string;
1919
userId?: string;
20+
subjectId?: string;
2021
workspaceId?: string;
2122
instanceId?: string;
2223
}

components/server/src/analytics.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
*/
66
import { User } from "@gitpod/gitpod-protocol";
77
import { Request } from "express";
8-
import { IAnalyticsWriter } from "@gitpod/gitpod-protocol/lib/analytics";
8+
import { IAnalyticsWriter, IdentifyMessage, PageMessage, TrackMessage } from "@gitpod/gitpod-protocol/lib/analytics";
99
import * as crypto from "crypto";
1010
import { clientIp } from "./express-util";
11+
import { ctxTrySubjectId } from "./util/request-context";
1112

1213
export async function trackLogin(user: User, request: Request, authHost: string, analytics: IAnalyticsWriter) {
1314
// make new complete identify call for each login
@@ -129,3 +130,31 @@ function stripCookie(cookie: string) {
129130
return cookie;
130131
}
131132
}
133+
134+
export class ContextAwareAnalyticsWriter implements IAnalyticsWriter {
135+
constructor(readonly writer: IAnalyticsWriter) {}
136+
137+
identify(msg: IdentifyMessage): void {}
138+
139+
track(msg: TrackMessage): void {}
140+
141+
page(msg: PageMessage): void {
142+
const traceIds = this.getAnalyticsIds();
143+
this.writer.page({
144+
...msg,
145+
userId: msg.userId || traceIds.userId,
146+
subjectId: msg.subjectId || traceIds.subjectId,
147+
});
148+
}
149+
150+
private getAnalyticsIds(): { userId?: string; subjectId?: string } {
151+
const subjectId = ctxTrySubjectId();
152+
if (!subjectId) {
153+
return {};
154+
}
155+
return {
156+
userId: subjectId.userId(),
157+
subjectId: subjectId.toString(),
158+
};
159+
}
160+
}

components/server/src/api/configuration-service-api.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import {
2020
} from "@gitpod/public-api/lib/gitpod/v1/configuration_pb";
2121
import { PaginationResponse } from "@gitpod/public-api/lib/gitpod/v1/pagination_pb";
2222
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
23+
import { ctxUserId } from "../util/request-context";
24+
import { UserService } from "../user/user-service";
2325

2426
@injectable()
2527
export class ConfigurationServiceAPI implements ServiceImpl<typeof ConfigurationServiceInterface> {
@@ -28,11 +30,13 @@ export class ConfigurationServiceAPI implements ServiceImpl<typeof Configuration
2830
private readonly projectService: ProjectsService,
2931
@inject(PublicAPIConverter)
3032
private readonly apiConverter: PublicAPIConverter,
33+
@inject(UserService)
34+
private readonly userService: UserService,
3135
) {}
3236

3337
async createConfiguration(
3438
req: CreateConfigurationRequest,
35-
context: HandlerContext,
39+
_: HandlerContext,
3640
): Promise<CreateConfigurationResponse> {
3741
if (!req.organizationId) {
3842
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "organization_id is required");
@@ -44,6 +48,11 @@ export class ConfigurationServiceAPI implements ServiceImpl<typeof Configuration
4448
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "name is required");
4549
}
4650

51+
const installer = await this.userService.findUserById(ctxUserId(), ctxUserId());
52+
if (!installer) {
53+
throw new ApplicationError(ErrorCodes.NOT_FOUND, "user not found");
54+
}
55+
4756
const project = await this.projectService.createProject(
4857
{
4958
teamId: req.organizationId,
@@ -52,35 +61,35 @@ export class ConfigurationServiceAPI implements ServiceImpl<typeof Configuration
5261
appInstallationId: "",
5362
slug: "",
5463
},
55-
context.user,
64+
installer,
5665
);
5766

5867
return new CreateConfigurationResponse({
5968
configuration: this.apiConverter.toConfiguration(project),
6069
});
6170
}
6271

63-
async getConfiguration(req: GetConfigurationRequest, context: HandlerContext) {
72+
async getConfiguration(req: GetConfigurationRequest, _: HandlerContext) {
6473
if (!req.configurationId) {
6574
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "configuration_id is required");
6675
}
6776

68-
const project = await this.projectService.getProject(context.user.id, req.configurationId);
77+
const project = await this.projectService.getProject(ctxUserId(), req.configurationId);
6978

7079
return {
7180
configuration: this.apiConverter.toConfiguration(project),
7281
};
7382
}
7483

75-
async listConfigurations(req: ListConfigurationsRequest, context: HandlerContext) {
84+
async listConfigurations(req: ListConfigurationsRequest, _: HandlerContext) {
7685
if (!req.organizationId) {
7786
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "organization_id is required");
7887
}
7988

8089
const limit = req.pagination?.pageSize || 25;
8190
const offset = (req.pagination?.page ?? 0) * limit;
8291

83-
const { rows, total } = await this.projectService.findProjects(context.user.id, {
92+
const { rows, total } = await this.projectService.findProjects(ctxUserId(), {
8493
searchTerm: req.searchTerm,
8594
orderBy: "name",
8695
orderDir: "ASC",
@@ -96,12 +105,12 @@ export class ConfigurationServiceAPI implements ServiceImpl<typeof Configuration
96105
});
97106
}
98107

99-
async deleteConfiguration(req: DeleteConfigurationRequest, handler: HandlerContext) {
108+
async deleteConfiguration(req: DeleteConfigurationRequest, _: HandlerContext) {
100109
if (!req.configurationId) {
101110
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "configuration_id is required");
102111
}
103112

104-
await this.projectService.deleteProject(handler.user.id, req.configurationId);
113+
await this.projectService.deleteProject(ctxUserId(), req.configurationId);
105114

106115
return new DeleteConfigurationResponse();
107116
}

components/server/src/api/handler-context-augmentation.d.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.

components/server/src/api/hello-service-api.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
*/
66

77
import { HandlerContext, ServiceImpl } from "@connectrpc/connect";
8-
import { User } from "@gitpod/gitpod-protocol";
98
import { HelloService } from "@gitpod/public-api/lib/gitpod/experimental/v1/dummy_connect";
109
import {
1110
LotsOfRepliesRequest,
@@ -14,27 +13,30 @@ import {
1413
SayHelloResponse,
1514
} from "@gitpod/public-api/lib/gitpod/experimental/v1/dummy_pb";
1615
import { injectable } from "inversify";
16+
import { ctxCheckAborted, ctxTrySubjectId } from "../util/request-context";
1717

1818
@injectable()
1919
export class HelloServiceAPI implements ServiceImpl<typeof HelloService> {
20-
async sayHello(req: SayHelloRequest, context: HandlerContext): Promise<SayHelloResponse> {
20+
async sayHello(req: SayHelloRequest, _: HandlerContext): Promise<SayHelloResponse> {
2121
const response = new SayHelloResponse();
22-
response.reply = "Hello " + this.getSubject(context);
22+
response.reply = "Hello " + getSubject();
2323
return response;
2424
}
25-
async *lotsOfReplies(req: LotsOfRepliesRequest, context: HandlerContext): AsyncGenerator<LotsOfRepliesResponse> {
25+
async *lotsOfReplies(req: LotsOfRepliesRequest, _: HandlerContext): AsyncGenerator<LotsOfRepliesResponse> {
2626
let count = req.previousCount || 0;
27-
while (!context.signal.aborted) {
27+
while (true) {
28+
ctxCheckAborted();
29+
2830
const response = new LotsOfRepliesResponse();
29-
response.reply = `Hello ${this.getSubject(context)} ${count}`;
31+
response.reply = `Hello ${getSubject()} ${count}`;
3032
response.count = count;
3133
yield response;
3234
count++;
3335
await new Promise((resolve) => setTimeout(resolve, 30000));
3436
}
3537
}
38+
}
3639

37-
private getSubject(context: HandlerContext): string {
38-
return User.getName(context.user) || "World";
39-
}
40+
function getSubject(): string {
41+
return ctxTrySubjectId()?.toString() || "World";
4042
}

components/server/src/api/organization-service-api.ts

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import {
3939
import { PublicAPIConverter } from "@gitpod/gitpod-protocol/lib/public-api-converter";
4040
import { OrganizationService } from "../orgs/organization-service";
4141
import { PaginationResponse } from "@gitpod/public-api/lib/gitpod/v1/pagination_pb";
42+
import { ctxUserId } from "../util/request-context";
43+
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
4244

4345
@injectable()
4446
export class OrganizationServiceAPI implements ServiceImpl<typeof OrganizationServiceInterface> {
@@ -49,18 +51,20 @@ export class OrganizationServiceAPI implements ServiceImpl<typeof OrganizationSe
4951
private readonly apiConverter: PublicAPIConverter,
5052
) {}
5153

52-
async createOrganization(
53-
req: CreateOrganizationRequest,
54-
context: HandlerContext,
55-
): Promise<CreateOrganizationResponse> {
56-
const org = await this.orgService.createOrganization(context.user.id, req.name);
54+
async createOrganization(req: CreateOrganizationRequest, _: HandlerContext): Promise<CreateOrganizationResponse> {
55+
// TODO(gpl) This mimicks the current behavior of adding the subjectId as owner
56+
const ownerId = ctxUserId();
57+
if (!ownerId) {
58+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "No userId available");
59+
}
60+
const org = await this.orgService.createOrganization(ownerId, req.name);
5761
const response = new CreateOrganizationResponse();
5862
response.organization = this.apiConverter.toOrganization(org);
5963
return response;
6064
}
6165

62-
async getOrganization(req: GetOrganizationRequest, context: HandlerContext): Promise<GetOrganizationResponse> {
63-
const org = await this.orgService.getOrganization(context.user.id, req.organizationId);
66+
async getOrganization(req: GetOrganizationRequest, _: HandlerContext): Promise<GetOrganizationResponse> {
67+
const org = await this.orgService.getOrganization(ctxUserId(), req.organizationId);
6468
const response = new GetOrganizationResponse();
6569
response.organization = this.apiConverter.toOrganization(org);
6670
return response;
@@ -70,7 +74,7 @@ export class OrganizationServiceAPI implements ServiceImpl<typeof OrganizationSe
7074
req: UpdateOrganizationRequest,
7175
context: HandlerContext,
7276
): Promise<UpdateOrganizationResponse> {
73-
const org = await this.orgService.updateOrganization(context.user.id, req.organizationId, {
77+
const org = await this.orgService.updateOrganization(ctxUserId(), req.organizationId, {
7478
name: req.name,
7579
});
7680
return new UpdateOrganizationResponse({
@@ -83,7 +87,7 @@ export class OrganizationServiceAPI implements ServiceImpl<typeof OrganizationSe
8387
context: HandlerContext,
8488
): Promise<ListOrganizationsResponse> {
8589
const orgs = await this.orgService.listOrganizations(
86-
context.user.id,
90+
ctxUserId(),
8791
{
8892
limit: req.pagination?.pageSize || 100,
8993
offset: (req.pagination?.page || 0) * (req.pagination?.pageSize || 0),
@@ -97,46 +101,43 @@ export class OrganizationServiceAPI implements ServiceImpl<typeof OrganizationSe
97101
return response;
98102
}
99103

100-
async deleteOrganization(
101-
req: DeleteOrganizationRequest,
102-
context: HandlerContext,
103-
): Promise<DeleteOrganizationResponse> {
104-
await this.orgService.deleteOrganization(context.user.id, req.organizationId);
104+
async deleteOrganization(req: DeleteOrganizationRequest, _: HandlerContext): Promise<DeleteOrganizationResponse> {
105+
await this.orgService.deleteOrganization(ctxUserId(), req.organizationId);
105106
return new DeleteOrganizationResponse();
106107
}
107108

108109
async getOrganizationInvitation(
109110
req: GetOrganizationInvitationRequest,
110-
context: HandlerContext,
111+
_: HandlerContext,
111112
): Promise<GetOrganizationInvitationResponse> {
112-
const invitation = await this.orgService.getOrCreateInvite(context.user.id, req.organizationId);
113+
const invitation = await this.orgService.getOrCreateInvite(ctxUserId(), req.organizationId);
113114
const response = new GetOrganizationInvitationResponse();
114115
response.invitationId = invitation.id;
115116
return response;
116117
}
117118

118-
async joinOrganization(req: JoinOrganizationRequest, context: HandlerContext): Promise<JoinOrganizationResponse> {
119-
const orgId = await this.orgService.joinOrganization(context.user.id, req.invitationId);
119+
async joinOrganization(req: JoinOrganizationRequest, _: HandlerContext): Promise<JoinOrganizationResponse> {
120+
const orgId = await this.orgService.joinOrganization(ctxUserId(), req.invitationId);
120121
const result = new JoinOrganizationResponse();
121122
result.organizationId = orgId;
122123
return result;
123124
}
124125

125126
async resetOrganizationInvitation(
126127
req: ResetOrganizationInvitationRequest,
127-
context: HandlerContext,
128+
_: HandlerContext,
128129
): Promise<ResetOrganizationInvitationResponse> {
129-
const inviteId = await this.orgService.resetInvite(context.user.id, req.organizationId);
130+
const inviteId = await this.orgService.resetInvite(ctxUserId(), req.organizationId);
130131
const result = new ResetOrganizationInvitationResponse();
131132
result.invitationId = inviteId.id;
132133
return result;
133134
}
134135

135136
async listOrganizationMembers(
136137
req: ListOrganizationMembersRequest,
137-
context: HandlerContext,
138+
_: HandlerContext,
138139
): Promise<ListOrganizationMembersResponse> {
139-
const members = await this.orgService.listMembers(context.user.id, req.organizationId);
140+
const members = await this.orgService.listMembers(ctxUserId(), req.organizationId);
140141
//TODO pagination
141142
const response = new ListOrganizationMembersResponse();
142143
response.members = members.map((member) => this.apiConverter.toOrganizationMember(member));
@@ -147,16 +148,16 @@ export class OrganizationServiceAPI implements ServiceImpl<typeof OrganizationSe
147148

148149
async updateOrganizationMember(
149150
req: UpdateOrganizationMemberRequest,
150-
context: HandlerContext,
151+
_: HandlerContext,
151152
): Promise<UpdateOrganizationMemberResponse> {
152153
await this.orgService.addOrUpdateMember(
153-
context.user.id,
154+
ctxUserId(),
154155
req.organizationId,
155156
req.userId,
156157
this.apiConverter.fromOrgMemberRole(req.role),
157158
);
158159
const member = await this.orgService
159-
.listMembers(context.user.id, req.organizationId)
160+
.listMembers(ctxUserId(), req.organizationId)
160161
.then((members) => members.find((member) => member.userId === req.userId));
161162
return new UpdateOrganizationMemberResponse({
162163
member: member && this.apiConverter.toOrganizationMember(member),
@@ -165,27 +166,27 @@ export class OrganizationServiceAPI implements ServiceImpl<typeof OrganizationSe
165166

166167
async deleteOrganizationMember(
167168
req: DeleteOrganizationMemberRequest,
168-
context: HandlerContext,
169+
_: HandlerContext,
169170
): Promise<DeleteOrganizationMemberResponse> {
170-
await this.orgService.removeOrganizationMember(context.user.id, req.organizationId, req.userId);
171+
await this.orgService.removeOrganizationMember(ctxUserId(), req.organizationId, req.userId);
171172
return new DeleteOrganizationMemberResponse();
172173
}
173174

174175
async getOrganizationSettings(
175176
req: GetOrganizationSettingsRequest,
176-
context: HandlerContext,
177+
_: HandlerContext,
177178
): Promise<GetOrganizationSettingsResponse> {
178-
const settings = await this.orgService.getSettings(context.user.id, req.organizationId);
179+
const settings = await this.orgService.getSettings(ctxUserId(), req.organizationId);
179180
const response = new GetOrganizationSettingsResponse();
180181
response.settings = this.apiConverter.toOrganizationSettings(settings);
181182
return response;
182183
}
183184

184185
async updateOrganizationSettings(
185186
req: UpdateOrganizationSettingsRequest,
186-
context: HandlerContext,
187+
_: HandlerContext,
187188
): Promise<UpdateOrganizationSettingsResponse> {
188-
const settings = await this.orgService.updateSettings(context.user.id, req.organizationId, {
189+
const settings = await this.orgService.updateSettings(ctxUserId(), req.organizationId, {
189190
workspaceSharingDisabled: req.settings?.workspaceSharingDisabled,
190191
defaultWorkspaceImage: req.settings?.defaultWorkspaceImage,
191192
});

0 commit comments

Comments
 (0)