@opengeni/api-router 0.30.3 → 2.1.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-websocket.d.ts +18 -0
- package/dist/app.js +1 -1
- package/dist/auth/managed-auth.d.ts +6 -0
- package/dist/{chunk-WCXHI3FF.js → chunk-QKDFBBUE.js} +26385 -15573
- package/dist/chunk-QKDFBBUE.js.map +1 -0
- package/dist/codemode.d.ts +8 -1
- package/dist/company-brain-okf.d.ts +24 -0
- package/dist/connected-machine-computer-access.d.ts +11 -0
- package/dist/connection-authority-owner.d.ts +59 -0
- package/dist/connection-ownership.d.ts +47 -0
- package/dist/controller-data-plane.d.ts +16 -0
- package/dist/editable-artifact-websocket.d.ts +5 -15
- package/dist/editable-artifact-workspace-files.d.ts +1 -1
- package/dist/http/api-error.d.ts +10 -0
- package/dist/http/cors.d.ts +5 -0
- package/dist/http/interaction-control-error.d.ts +13 -0
- package/dist/http/sse.d.ts +2 -3
- package/dist/index.d.ts +5 -2
- package/dist/index.js +326 -82
- package/dist/index.js.map +1 -1
- package/dist/integrations/atlassian.d.ts +17 -0
- package/dist/integrations/fiken.d.ts +7 -2
- package/dist/integrations/google-drive.d.ts +18 -0
- package/dist/integrations/oauth-client.d.ts +22 -3
- package/dist/integrations/oauth-profiles.d.ts +195 -0
- package/dist/integrations/personal-github-repositories.d.ts +45 -0
- package/dist/integrations/personal-github.d.ts +40 -0
- package/dist/integrations/pr-review-provider.d.ts +22 -0
- package/dist/integrations/provider-oauth.d.ts +8 -0
- package/dist/integrations/slack-app-home.d.ts +24 -0
- package/dist/integrations/slack-bot.d.ts +149 -9
- package/dist/integrations/slack-interactions.d.ts +4 -3
- package/dist/integrations/slack-routing.d.ts +120 -0
- package/dist/integrations/social-oauth.d.ts +5 -0
- package/dist/interaction-frame-proxy.d.ts +68 -0
- package/dist/mcp/company-brain-governed-writes.d.ts +26 -0
- package/dist/mcp/company-profile-agent-admin.d.ts +44 -0
- package/dist/mcp/documents.d.ts +1 -0
- package/dist/mcp/remember.d.ts +28 -0
- package/dist/mcp/request-abort.d.ts +19 -0
- package/dist/mcp/scheduled-task-view.d.ts +4 -4
- package/dist/mcp/server.d.ts +15 -2
- package/dist/mcp/session-view.d.ts +4 -0
- package/dist/mcp/session-wait.d.ts +137 -0
- package/dist/routes/automations.d.ts +4 -0
- package/dist/routes/billing.d.ts +5 -0
- package/dist/routes/browser-sessions.d.ts +4 -1
- package/dist/routes/company-brain.d.ts +3 -0
- package/dist/routes/company-profile.d.ts +2 -1
- package/dist/routes/connection-authorities.d.ts +3 -0
- package/dist/routes/personal-github-git-broker.d.ts +29 -0
- package/dist/routes/personal-github.d.ts +3 -0
- package/dist/routes/pr-review.d.ts +3 -0
- package/dist/routes/sessions.d.ts +1 -0
- package/dist/routes/user-resource-authorities.d.ts +3 -0
- package/dist/routes/workspace-capture.d.ts +10 -2
- package/dist/routes/workspace-learning.d.ts +3 -0
- package/dist/routes/workspaces.d.ts +19 -0
- package/dist/sandbox/channel-a.d.ts +11 -1
- package/dist/sandbox/connection-authority.d.ts +3 -0
- package/dist/sandbox/enrollment.d.ts +2 -0
- package/dist/sandbox/machines.d.ts +2 -2
- package/dist/sandbox/metrics-ingestion.d.ts +10 -20
- package/dist/sandbox/viewer.d.ts +24 -1
- package/dist/sandbox-file-artifacts.d.ts +11 -0
- package/dist/scheduled-task-deletion.d.ts +14 -0
- package/dist/slack-reaction-files.d.ts +1 -1
- package/dist/temporal-schedule-cleanup.d.ts +1 -0
- package/package.json +20 -18
- package/src/api-websocket.ts +23 -0
- package/src/app.ts +153 -23
- package/src/auth/managed-auth.ts +25 -3
- package/src/codemode.ts +53 -23
- package/src/codex-realtime.ts +8 -2
- package/src/company-brain-okf.ts +340 -0
- package/src/connected-machine-computer-access.ts +33 -0
- package/src/connection-authority-owner.ts +61 -0
- package/src/connection-ownership.ts +180 -0
- package/src/controller-data-plane.ts +47 -0
- package/src/editable-artifact-native-kernel.ts +26 -15
- package/src/editable-artifact-office-import.ts +28 -3
- package/src/editable-artifact-production.ts +13 -7
- package/src/editable-artifact-websocket.ts +11 -18
- package/src/editable-artifact-workspace-files.ts +31 -6
- package/src/http/api-error.ts +28 -0
- package/src/http/auth.ts +4 -0
- package/src/http/cors.ts +35 -0
- package/src/http/interaction-control-error.ts +164 -0
- package/src/http/sse.ts +112 -66
- package/src/index.ts +46 -6
- package/src/integrations/atlassian.ts +146 -35
- package/src/integrations/fiken.ts +102 -22
- package/src/integrations/google-drive.ts +301 -92
- package/src/integrations/oauth-client.ts +272 -143
- package/src/integrations/oauth-profiles.ts +477 -0
- package/src/integrations/personal-github-repositories.ts +445 -0
- package/src/integrations/personal-github.ts +705 -0
- package/src/integrations/pr-review-provider.ts +246 -0
- package/src/integrations/provider-oauth.ts +121 -5
- package/src/integrations/slack-app-home.ts +300 -0
- package/src/integrations/slack-bot.ts +337 -57
- package/src/integrations/slack-interactions.ts +1265 -208
- package/src/integrations/slack-routing.ts +324 -0
- package/src/integrations/social-oauth.ts +25 -0
- package/src/interaction-frame-proxy.ts +409 -0
- package/src/mcp/company-brain-governed-writes.ts +262 -0
- package/src/mcp/company-profile-agent-admin.ts +205 -0
- package/src/mcp/documents.ts +37 -6
- package/src/mcp/files.ts +16 -1
- package/src/mcp/remember.ts +181 -0
- package/src/mcp/request-abort.ts +44 -0
- package/src/mcp/scheduled-task-view.ts +1 -0
- package/src/mcp/server.ts +719 -369
- package/src/mcp/session-view.ts +54 -0
- package/src/mcp/session-wait.ts +554 -0
- package/src/model-catalog.ts +20 -12
- package/src/routes/api-integrations.ts +4 -0
- package/src/routes/api-keys.ts +1 -0
- package/src/routes/automations.ts +534 -0
- package/src/routes/billing.ts +57 -1
- package/src/routes/browser-sessions.ts +329 -79
- package/src/routes/channels.ts +17 -1
- package/src/routes/codex.ts +50 -0
- package/src/routes/company-brain.ts +367 -0
- package/src/routes/company-profile.ts +1 -1
- package/src/routes/computer-sessions.ts +320 -93
- package/src/routes/connection-authorities.ts +139 -0
- package/src/routes/connections.ts +144 -34
- package/src/routes/documents.ts +221 -3
- package/src/routes/editable-artifacts.ts +11 -3
- package/src/routes/enrollments.ts +116 -26
- package/src/routes/environments.ts +127 -55
- package/src/routes/files.ts +72 -11
- package/src/routes/install.ts +82 -28
- package/src/routes/integration-facets.ts +29 -0
- package/src/routes/interaction-resources.ts +20 -2
- package/src/routes/machines.ts +275 -13
- package/src/routes/organization-memberships.ts +717 -28
- package/src/routes/packs.ts +4 -3
- package/src/routes/personal-github-git-broker.ts +785 -0
- package/src/routes/personal-github.ts +319 -0
- package/src/routes/pr-review.ts +531 -0
- package/src/routes/rigs.ts +104 -39
- package/src/routes/scheduled-tasks.ts +19 -21
- package/src/routes/sessions.ts +775 -38
- package/src/routes/social.ts +14 -2
- package/src/routes/supergrok.ts +23 -4
- package/src/routes/transcription-recordings.ts +8 -2
- package/src/routes/user-resource-authorities.ts +138 -0
- package/src/routes/workspace-artifacts.ts +4 -1
- package/src/routes/workspace-capture.ts +21 -0
- package/src/routes/workspace-learning.ts +228 -0
- package/src/routes/workspaces.ts +59 -6
- package/src/sandbox/auth-callout.ts +38 -12
- package/src/sandbox/channel-a.ts +242 -19
- package/src/sandbox/connection-authority.ts +3 -0
- package/src/sandbox/enrollment.ts +15 -3
- package/src/sandbox/machines.ts +186 -64
- package/src/sandbox/metrics-ingestion.ts +220 -58
- package/src/sandbox/viewer.ts +243 -19
- package/src/sandbox-file-artifacts.ts +329 -0
- package/src/scheduled-task-deletion.ts +127 -0
- package/src/slack-reaction-files.ts +16 -5
- package/src/temporal-schedule-cleanup.ts +13 -0
- package/dist/chunk-WCXHI3FF.js.map +0 -1
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ListPersonalGitHubRepositoriesQuery,
|
|
3
|
+
ListPersonalGitHubRepositoriesResponse,
|
|
4
|
+
PersonalGitHubConnectionStatusResponse,
|
|
5
|
+
PersonalGitHubOAuthStartRequest,
|
|
6
|
+
PersonalGitHubRepositorySelectionState,
|
|
7
|
+
ReplacePersonalGitHubRepositorySelectionsRequest,
|
|
8
|
+
VerifyPersonalGitHubRepositorySelectionsRequest,
|
|
9
|
+
} from "@opengeni/contracts/personal-github";
|
|
10
|
+
import {
|
|
11
|
+
requireAccessGrant,
|
|
12
|
+
requireAccessGrantAuthorization,
|
|
13
|
+
type ApiRouteDeps,
|
|
14
|
+
} from "@opengeni/core";
|
|
15
|
+
import type { Hono } from "hono";
|
|
16
|
+
import {
|
|
17
|
+
getPersonalGitHubRepositorySelectionState,
|
|
18
|
+
PersonalGitHubRepositorySelectionChangedError,
|
|
19
|
+
PersonalGitHubRepositorySelectionIdempotencyError,
|
|
20
|
+
PersonalGitHubRepositorySelectionUnavailableError,
|
|
21
|
+
replacePersonalGitHubRepositorySelections,
|
|
22
|
+
verifyPersonalGitHubRepositorySelections,
|
|
23
|
+
type PersonalGitHubRepositorySelectionState as DbPersonalGitHubRepositorySelectionState,
|
|
24
|
+
} from "@opengeni/db";
|
|
25
|
+
import { HTTPException } from "hono/http-exception";
|
|
26
|
+
import { assertPersonalConnectionOwnerPrincipal } from "../connection-ownership";
|
|
27
|
+
import {
|
|
28
|
+
completePersonalGitHubOAuthCallback,
|
|
29
|
+
listPersonalGitHubConnections,
|
|
30
|
+
personalGitHubReviewUrl,
|
|
31
|
+
startPersonalGitHubOAuth,
|
|
32
|
+
} from "../integrations/personal-github";
|
|
33
|
+
import {
|
|
34
|
+
listLivePersonalGitHubRepositories,
|
|
35
|
+
personalGitHubRepositoryProviderHttpError,
|
|
36
|
+
PersonalGitHubRepositoryProviderError,
|
|
37
|
+
requirePersonalGitHubRepositoryConnection,
|
|
38
|
+
verifyLivePersonalGitHubRepositories,
|
|
39
|
+
} from "../integrations/personal-github-repositories";
|
|
40
|
+
|
|
41
|
+
export function registerPersonalGitHubRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
42
|
+
app.get("/v1/workspaces/:workspaceId/connections/github", async (c) => {
|
|
43
|
+
const workspaceId = c.req.param("workspaceId");
|
|
44
|
+
const grant = await requireAccessGrant(c, deps, workspaceId, "connections:read");
|
|
45
|
+
const connections = await listPersonalGitHubConnections(deps, {
|
|
46
|
+
workspaceId,
|
|
47
|
+
subjectId: grant.subjectId,
|
|
48
|
+
});
|
|
49
|
+
return c.json(
|
|
50
|
+
PersonalGitHubConnectionStatusResponse.parse({
|
|
51
|
+
enabled: deps.settings.githubPersonalOauthEnabled,
|
|
52
|
+
connection: canonicalPersonalGitHubConnection(connections),
|
|
53
|
+
reviewUrl: personalGitHubReviewUrl(deps.settings),
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
app.post("/v1/workspaces/:workspaceId/connections/github/oauth/start", async (c) => {
|
|
59
|
+
const workspaceId = c.req.param("workspaceId");
|
|
60
|
+
const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "connections:write");
|
|
61
|
+
assertPersonalConnectionOwnerPrincipal(access, "My GitHub account");
|
|
62
|
+
const payload = PersonalGitHubOAuthStartRequest.parse(await c.req.json().catch(() => ({})));
|
|
63
|
+
return c.json(
|
|
64
|
+
await startPersonalGitHubOAuth(deps, {
|
|
65
|
+
access,
|
|
66
|
+
workspaceId,
|
|
67
|
+
...(payload.connectionId ? { connectionId: payload.connectionId } : {}),
|
|
68
|
+
...(payload.returnPath ? { returnPath: payload.returnPath } : {}),
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
app.post("/v1/workspaces/:workspaceId/connections/:connectionId/github/reconnect", async (c) => {
|
|
74
|
+
const workspaceId = c.req.param("workspaceId");
|
|
75
|
+
const access = await requireAccessGrantAuthorization(c, deps, workspaceId, "connections:write");
|
|
76
|
+
assertPersonalConnectionOwnerPrincipal(access, "My GitHub account");
|
|
77
|
+
const payload = PersonalGitHubOAuthStartRequest.omit({ connectionId: true }).parse(
|
|
78
|
+
await c.req.json().catch(() => ({})),
|
|
79
|
+
);
|
|
80
|
+
return c.json(
|
|
81
|
+
await startPersonalGitHubOAuth(deps, {
|
|
82
|
+
access,
|
|
83
|
+
workspaceId,
|
|
84
|
+
connectionId: c.req.param("connectionId"),
|
|
85
|
+
...(payload.returnPath ? { returnPath: payload.returnPath } : {}),
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
app.get(
|
|
91
|
+
"/v1/workspaces/:workspaceId/connections/:connectionId/github/repositories",
|
|
92
|
+
async (c) => {
|
|
93
|
+
const workspaceId = c.req.param("workspaceId");
|
|
94
|
+
const connectionId = c.req.param("connectionId");
|
|
95
|
+
const access = await requireAccessGrantAuthorization(
|
|
96
|
+
c,
|
|
97
|
+
deps,
|
|
98
|
+
workspaceId,
|
|
99
|
+
"connections:read",
|
|
100
|
+
);
|
|
101
|
+
assertPersonalConnectionOwnerPrincipal(access, "My GitHub repositories");
|
|
102
|
+
const connection = await requirePersonalGitHubRepositoryConnection(deps, {
|
|
103
|
+
accountId: access.grant.accountId,
|
|
104
|
+
workspaceId,
|
|
105
|
+
subjectId: access.grant.subjectId,
|
|
106
|
+
connectionId,
|
|
107
|
+
});
|
|
108
|
+
const query = ListPersonalGitHubRepositoriesQuery.parse(c.req.query());
|
|
109
|
+
try {
|
|
110
|
+
const selection = await loadRepositorySelectionState(deps, access.grant, connection);
|
|
111
|
+
const page = await listLivePersonalGitHubRepositories(deps, {
|
|
112
|
+
accountId: access.grant.accountId,
|
|
113
|
+
workspaceId,
|
|
114
|
+
subjectId: access.grant.subjectId,
|
|
115
|
+
connectionId,
|
|
116
|
+
expectedConnectionAuthorityGeneration: selection.connectionAuthorityGeneration,
|
|
117
|
+
page: query.cursor,
|
|
118
|
+
limit: query.limit,
|
|
119
|
+
});
|
|
120
|
+
const selectedAccessById = new Map(
|
|
121
|
+
selection.repositories.map((repository) => [
|
|
122
|
+
repository.repositoryId,
|
|
123
|
+
repository.selectedAccess,
|
|
124
|
+
]),
|
|
125
|
+
);
|
|
126
|
+
return c.json(
|
|
127
|
+
ListPersonalGitHubRepositoriesResponse.parse({
|
|
128
|
+
repositories: page.repositories.map((repository) => ({
|
|
129
|
+
...repository,
|
|
130
|
+
selectedAccess: selectedAccessById.get(repository.repositoryId) ?? null,
|
|
131
|
+
})),
|
|
132
|
+
nextCursor: page.nextPage,
|
|
133
|
+
selection,
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
throw personalGitHubRepositoryRouteError(error);
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
app.put(
|
|
143
|
+
"/v1/workspaces/:workspaceId/connections/:connectionId/github/repositories",
|
|
144
|
+
async (c) => {
|
|
145
|
+
const workspaceId = c.req.param("workspaceId");
|
|
146
|
+
const connectionId = c.req.param("connectionId");
|
|
147
|
+
const access = await requireAccessGrantAuthorization(
|
|
148
|
+
c,
|
|
149
|
+
deps,
|
|
150
|
+
workspaceId,
|
|
151
|
+
"connections:write",
|
|
152
|
+
);
|
|
153
|
+
assertPersonalConnectionOwnerPrincipal(access, "My GitHub repositories");
|
|
154
|
+
const connection = await requirePersonalGitHubRepositoryConnection(deps, {
|
|
155
|
+
accountId: access.grant.accountId,
|
|
156
|
+
workspaceId,
|
|
157
|
+
subjectId: access.grant.subjectId,
|
|
158
|
+
connectionId,
|
|
159
|
+
});
|
|
160
|
+
const request = ReplacePersonalGitHubRepositorySelectionsRequest.parse(await c.req.json());
|
|
161
|
+
try {
|
|
162
|
+
const current = await loadRepositorySelectionState(deps, access.grant, connection);
|
|
163
|
+
assertRepositoryConnectionAuthorityFence(current, request);
|
|
164
|
+
const verified = await verifyLivePersonalGitHubRepositories(deps, {
|
|
165
|
+
accountId: access.grant.accountId,
|
|
166
|
+
workspaceId,
|
|
167
|
+
subjectId: access.grant.subjectId,
|
|
168
|
+
connectionId,
|
|
169
|
+
expectedConnectionAuthorityGeneration: request.expectedConnectionAuthorityGeneration,
|
|
170
|
+
repositories: request.repositories,
|
|
171
|
+
});
|
|
172
|
+
const lastVerifiedAt = new Date().toISOString();
|
|
173
|
+
const selection = await replacePersonalGitHubRepositorySelections(deps.db, {
|
|
174
|
+
accountId: access.grant.accountId,
|
|
175
|
+
originWorkspaceId: connection.workspaceId,
|
|
176
|
+
subjectId: access.grant.subjectId,
|
|
177
|
+
connectionId,
|
|
178
|
+
expectedConnectionAuthorityGeneration: request.expectedConnectionAuthorityGeneration,
|
|
179
|
+
expectedSelectionGeneration: request.expectedSelectionGeneration,
|
|
180
|
+
idempotencyKey: request.idempotencyKey,
|
|
181
|
+
repositories: verified.map((repository) => ({ ...repository, lastVerifiedAt })),
|
|
182
|
+
});
|
|
183
|
+
return c.json(PersonalGitHubRepositorySelectionState.parse(selection));
|
|
184
|
+
} catch (error) {
|
|
185
|
+
throw personalGitHubRepositoryRouteError(error);
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
app.post(
|
|
191
|
+
"/v1/workspaces/:workspaceId/connections/:connectionId/github/repositories/verify",
|
|
192
|
+
async (c) => {
|
|
193
|
+
const workspaceId = c.req.param("workspaceId");
|
|
194
|
+
const connectionId = c.req.param("connectionId");
|
|
195
|
+
const access = await requireAccessGrantAuthorization(
|
|
196
|
+
c,
|
|
197
|
+
deps,
|
|
198
|
+
workspaceId,
|
|
199
|
+
"connections:write",
|
|
200
|
+
);
|
|
201
|
+
assertPersonalConnectionOwnerPrincipal(access, "My GitHub repositories");
|
|
202
|
+
const connection = await requirePersonalGitHubRepositoryConnection(deps, {
|
|
203
|
+
accountId: access.grant.accountId,
|
|
204
|
+
workspaceId,
|
|
205
|
+
subjectId: access.grant.subjectId,
|
|
206
|
+
connectionId,
|
|
207
|
+
});
|
|
208
|
+
const request = VerifyPersonalGitHubRepositorySelectionsRequest.parse(await c.req.json());
|
|
209
|
+
try {
|
|
210
|
+
const current = await loadRepositorySelectionState(deps, access.grant, connection);
|
|
211
|
+
assertRepositoryConnectionAuthorityFence(current, request);
|
|
212
|
+
const verified = await verifyLivePersonalGitHubRepositories(deps, {
|
|
213
|
+
accountId: access.grant.accountId,
|
|
214
|
+
workspaceId,
|
|
215
|
+
subjectId: access.grant.subjectId,
|
|
216
|
+
connectionId,
|
|
217
|
+
expectedConnectionAuthorityGeneration: request.expectedConnectionAuthorityGeneration,
|
|
218
|
+
repositories: current.repositories.map((repository) => ({
|
|
219
|
+
repositoryId: repository.repositoryId,
|
|
220
|
+
fullName: repository.fullName,
|
|
221
|
+
access: repository.selectedAccess,
|
|
222
|
+
})),
|
|
223
|
+
});
|
|
224
|
+
const lastVerifiedAt = new Date().toISOString();
|
|
225
|
+
const selection = await verifyPersonalGitHubRepositorySelections(deps.db, {
|
|
226
|
+
accountId: access.grant.accountId,
|
|
227
|
+
originWorkspaceId: connection.workspaceId,
|
|
228
|
+
subjectId: access.grant.subjectId,
|
|
229
|
+
connectionId,
|
|
230
|
+
expectedConnectionAuthorityGeneration: request.expectedConnectionAuthorityGeneration,
|
|
231
|
+
expectedSelectionGeneration: request.expectedSelectionGeneration,
|
|
232
|
+
idempotencyKey: request.idempotencyKey,
|
|
233
|
+
repositories: verified.map((repository) => ({ ...repository, lastVerifiedAt })),
|
|
234
|
+
});
|
|
235
|
+
return c.json(PersonalGitHubRepositorySelectionState.parse(selection));
|
|
236
|
+
} catch (error) {
|
|
237
|
+
throw personalGitHubRepositoryRouteError(error);
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
app.get("/v1/integrations/github-personal/oauth/callback", async (c) => {
|
|
243
|
+
const code = c.req.query("code");
|
|
244
|
+
const state = c.req.query("state");
|
|
245
|
+
const error = c.req.query("error");
|
|
246
|
+
const result = await completePersonalGitHubOAuthCallback(deps, {
|
|
247
|
+
requestUrl: c.req.url,
|
|
248
|
+
...(code ? { code } : {}),
|
|
249
|
+
...(state ? { state } : {}),
|
|
250
|
+
...(error ? { error } : {}),
|
|
251
|
+
});
|
|
252
|
+
return c.redirect(result.redirectTo, 302);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function loadRepositorySelectionState(
|
|
257
|
+
deps: ApiRouteDeps,
|
|
258
|
+
grant: { accountId: string; subjectId: string },
|
|
259
|
+
connection: { id: string; workspaceId: string },
|
|
260
|
+
): Promise<DbPersonalGitHubRepositorySelectionState> {
|
|
261
|
+
const state = await getPersonalGitHubRepositorySelectionState(deps.db, {
|
|
262
|
+
accountId: grant.accountId,
|
|
263
|
+
originWorkspaceId: connection.workspaceId,
|
|
264
|
+
subjectId: grant.subjectId,
|
|
265
|
+
connectionId: connection.id,
|
|
266
|
+
});
|
|
267
|
+
if (!state) throw new PersonalGitHubRepositorySelectionUnavailableError();
|
|
268
|
+
return state;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function assertRepositoryConnectionAuthorityFence(
|
|
272
|
+
current: DbPersonalGitHubRepositorySelectionState,
|
|
273
|
+
expected: {
|
|
274
|
+
expectedConnectionAuthorityGeneration: number;
|
|
275
|
+
},
|
|
276
|
+
): void {
|
|
277
|
+
if (current.connectionAuthorityGeneration !== expected.expectedConnectionAuthorityGeneration) {
|
|
278
|
+
throw new PersonalGitHubRepositorySelectionChangedError();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function personalGitHubRepositoryRouteError(error: unknown): Error {
|
|
283
|
+
if (error instanceof PersonalGitHubRepositoryProviderError) {
|
|
284
|
+
return personalGitHubRepositoryProviderHttpError(error);
|
|
285
|
+
}
|
|
286
|
+
if (error instanceof PersonalGitHubRepositorySelectionChangedError) {
|
|
287
|
+
return new HTTPException(409, {
|
|
288
|
+
message: "personal GitHub repository selection changed; refresh and try again",
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
if (error instanceof PersonalGitHubRepositorySelectionIdempotencyError) {
|
|
292
|
+
return new HTTPException(409, {
|
|
293
|
+
message: "personal GitHub repository idempotency key was already used",
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
if (error instanceof PersonalGitHubRepositorySelectionUnavailableError) {
|
|
297
|
+
return new HTTPException(404, { message: "personal GitHub connection not found" });
|
|
298
|
+
}
|
|
299
|
+
return error instanceof Error ? error : new Error("personal GitHub repository request failed");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function canonicalPersonalGitHubConnection<
|
|
303
|
+
T extends { status: string; updatedAt: string; id: string },
|
|
304
|
+
>(connections: T[]): T | null {
|
|
305
|
+
const statusRank: Record<string, number> = {
|
|
306
|
+
active: 0,
|
|
307
|
+
needs_reauth: 1,
|
|
308
|
+
error: 2,
|
|
309
|
+
revoked: 3,
|
|
310
|
+
};
|
|
311
|
+
return (
|
|
312
|
+
[...connections].sort((left, right) => {
|
|
313
|
+
const rank = (statusRank[left.status] ?? 4) - (statusRank[right.status] ?? 4);
|
|
314
|
+
if (rank !== 0) return rank;
|
|
315
|
+
const updated = right.updatedAt.localeCompare(left.updatedAt);
|
|
316
|
+
return updated !== 0 ? updated : right.id.localeCompare(left.id);
|
|
317
|
+
})[0] ?? null
|
|
318
|
+
);
|
|
319
|
+
}
|