@opengeni/api-router 0.30.3 → 2.2.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-3TP54PPX.js} +26692 -15389
- package/dist/chunk-3TP54PPX.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 +157 -9
- package/dist/integrations/slack-interactions.d.ts +39 -5
- 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 +161 -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 +342 -57
- package/src/integrations/slack-interactions.ts +1898 -259
- package/src/integrations/slack-routing.ts +337 -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 +353 -4
- 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
|
@@ -1,53 +1,742 @@
|
|
|
1
|
-
import { ListManagedOrganizationMembershipsResponse } from "@opengeni/contracts";
|
|
2
|
-
import { getManagedSession, type ApiRouteDeps } from "@opengeni/core";
|
|
3
1
|
import {
|
|
2
|
+
AddOrganizationWorkspaceMemberRequest,
|
|
3
|
+
AcceptOrganizationInvitationRequest,
|
|
4
|
+
CreateOrganizationRequest,
|
|
5
|
+
CreateOrganizationResponse,
|
|
6
|
+
CreateOrganizationWorkspaceRequest,
|
|
7
|
+
CreateOrganizationInvitationRequest,
|
|
8
|
+
ListManagedOrganizationMembershipsResponse,
|
|
9
|
+
ListOrganizationInvitationsPageQuery,
|
|
10
|
+
ListOrganizationInvitationsPageResponse,
|
|
11
|
+
ListOrganizationMembersResponse,
|
|
12
|
+
OrganizationAdministrationOverview,
|
|
13
|
+
OrganizationInvitation,
|
|
14
|
+
OrganizationMember,
|
|
15
|
+
OrganizationPrivateSessionSettings,
|
|
16
|
+
OrganizationRetentionPolicy,
|
|
17
|
+
OrganizationSummary,
|
|
18
|
+
Permission,
|
|
19
|
+
RevokeOrganizationInvitationRequest,
|
|
20
|
+
UpdateOrganizationMemberRequest,
|
|
21
|
+
UpdateOrganizationNameRequest,
|
|
22
|
+
UpdateOrganizationPrivateSessionSettingsRequest,
|
|
23
|
+
UpdateOrganizationRetentionPolicyRequest,
|
|
24
|
+
UpdateWorkspaceMemberRequest,
|
|
25
|
+
UpdateWorkspaceRequest,
|
|
26
|
+
UpdateWorkspaceSettingsRequest,
|
|
27
|
+
Workspace,
|
|
28
|
+
WorkspaceMember,
|
|
29
|
+
} from "@opengeni/contracts";
|
|
30
|
+
import {
|
|
31
|
+
getManagedSession,
|
|
32
|
+
organizationMembershipHttpStatus,
|
|
33
|
+
type ApiRouteDeps,
|
|
34
|
+
} from "@opengeni/core";
|
|
35
|
+
import {
|
|
36
|
+
acceptOrganizationInvitation,
|
|
37
|
+
bindPendingOrganizationInvitationsForVerifiedEmail,
|
|
38
|
+
createManagedOrganization,
|
|
39
|
+
createOrganizationSharedWorkspace,
|
|
40
|
+
createOrganizationInvitation,
|
|
4
41
|
ensureManagedAccessForUserWithOrganizationMemberships,
|
|
42
|
+
getManagedUserProfilesByIds,
|
|
43
|
+
getOrganizationAdministrationOverview,
|
|
44
|
+
getOrganizationPrivateSessionSettings,
|
|
45
|
+
getSelfOrganizationInvitation,
|
|
46
|
+
getOrganizationRetentionPolicy,
|
|
47
|
+
listOrganizationMembers,
|
|
48
|
+
listOrganizationInvitations,
|
|
49
|
+
listSelfOrganizationInvitations,
|
|
5
50
|
nestedPostgresSqlState,
|
|
51
|
+
revokeOrganizationInvitation,
|
|
52
|
+
removeWorkspaceMember,
|
|
53
|
+
updateOrganizationSharedWorkspace,
|
|
54
|
+
updateOrganizationSharedWorkspaceSettings,
|
|
55
|
+
updateOrganizationMember,
|
|
56
|
+
updateOrganizationName,
|
|
57
|
+
updateOrganizationPrivateSessionSettings,
|
|
58
|
+
updateOrganizationRetentionPolicy,
|
|
59
|
+
upsertOrganizationSharedWorkspaceMember,
|
|
6
60
|
} from "@opengeni/db";
|
|
7
|
-
import type { Hono } from "hono";
|
|
61
|
+
import type { Context, Hono } from "hono";
|
|
8
62
|
import { HTTPException } from "hono/http-exception";
|
|
63
|
+
import { z } from "zod";
|
|
9
64
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
65
|
+
const OrganizationId = z.string().uuid();
|
|
66
|
+
const WorkspaceId = z.string().uuid();
|
|
67
|
+
const MembershipId = z.string().uuid();
|
|
68
|
+
const InvitationId = z.string().uuid();
|
|
69
|
+
|
|
70
|
+
async function requireManagedHuman(context: Context, deps: ApiRouteDeps) {
|
|
71
|
+
if (
|
|
72
|
+
deps.settings.productAccessMode !== "managed" ||
|
|
73
|
+
!deps.managedAuth ||
|
|
74
|
+
!context.req.header("cookie") ||
|
|
75
|
+
context.req.header("authorization")
|
|
76
|
+
) {
|
|
77
|
+
throw new HTTPException(401, { message: "managed human session required" });
|
|
78
|
+
}
|
|
79
|
+
const session = await getManagedSession(context, deps.managedAuth, {
|
|
80
|
+
db: deps.db,
|
|
81
|
+
});
|
|
82
|
+
if (!session?.user) {
|
|
83
|
+
throw new HTTPException(401, { message: "managed human session required" });
|
|
84
|
+
}
|
|
85
|
+
return { session, subjectId: `user:${session.user.id}` };
|
|
86
|
+
}
|
|
22
87
|
|
|
23
|
-
|
|
24
|
-
|
|
88
|
+
async function parseBody<S extends z.ZodType>(context: Context, schema: S): Promise<z.infer<S>> {
|
|
89
|
+
const parsed = schema.safeParse(await context.req.json().catch(() => null));
|
|
90
|
+
if (!parsed.success) {
|
|
91
|
+
throw new HTTPException(422, {
|
|
92
|
+
message: "invalid organization membership request",
|
|
25
93
|
});
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
94
|
+
}
|
|
95
|
+
return parsed.data;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function parseId(schema: z.ZodString, value: string, label: string): string {
|
|
99
|
+
const parsed = schema.safeParse(value);
|
|
100
|
+
if (!parsed.success) throw new HTTPException(422, { message: `invalid ${label}` });
|
|
101
|
+
return parsed.data;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function rethrowMembershipError(error: unknown): never {
|
|
105
|
+
const status = organizationMembershipHttpStatus(nestedPostgresSqlState(error));
|
|
106
|
+
if (status !== null) {
|
|
107
|
+
throw new HTTPException(status, {
|
|
108
|
+
message:
|
|
109
|
+
status === 403
|
|
110
|
+
? "organization administration is not authorized"
|
|
111
|
+
: status === 404
|
|
112
|
+
? "organization resource not found"
|
|
113
|
+
: status === 409
|
|
114
|
+
? "organization membership state changed; refresh and retry"
|
|
115
|
+
: "invalid organization membership operation",
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function parseWorkspacePermissions(permissions: string[]) {
|
|
122
|
+
const parsed = z.array(Permission).max(128).safeParse(permissions);
|
|
123
|
+
if (!parsed.success) {
|
|
124
|
+
throw new HTTPException(422, { message: "invalid workspace permissions" });
|
|
125
|
+
}
|
|
126
|
+
return parsed.data;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function normalizeAgentInstructions(value: string | null): string | null {
|
|
130
|
+
if (value === null) return null;
|
|
131
|
+
return value.trim() || null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function registerOrganizationMembershipRoutes(app: Hono, deps: ApiRouteDeps): void {
|
|
135
|
+
app.post("/v1/organizations", async (context) => {
|
|
136
|
+
const { session, subjectId } = await requireManagedHuman(context, deps);
|
|
137
|
+
const payload = await parseBody(context, CreateOrganizationRequest);
|
|
138
|
+
try {
|
|
139
|
+
return context.json(
|
|
140
|
+
CreateOrganizationResponse.parse(
|
|
141
|
+
await createManagedOrganization(deps.db, {
|
|
142
|
+
subjectId,
|
|
143
|
+
subjectLabel: session.user.email || session.user.name,
|
|
144
|
+
...payload,
|
|
145
|
+
}),
|
|
146
|
+
),
|
|
147
|
+
201,
|
|
148
|
+
);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
rethrowMembershipError(error);
|
|
30
151
|
}
|
|
152
|
+
});
|
|
31
153
|
|
|
32
|
-
|
|
154
|
+
app.get("/v1/organization-memberships", async (context) => {
|
|
155
|
+
const { session } = await requireManagedHuman(context, deps);
|
|
33
156
|
try {
|
|
34
|
-
result = await ensureManagedAccessForUserWithOrganizationMemberships(deps.db, {
|
|
157
|
+
const result = await ensureManagedAccessForUserWithOrganizationMemberships(deps.db, {
|
|
35
158
|
userId: session.user.id,
|
|
36
159
|
email: session.user.email,
|
|
37
160
|
name: session.user.name,
|
|
161
|
+
emailVerified: session.user.emailVerified,
|
|
38
162
|
});
|
|
163
|
+
return context.json(
|
|
164
|
+
ListManagedOrganizationMembershipsResponse.parse({
|
|
165
|
+
memberships: result.organizationMemberships,
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
39
168
|
} catch (error) {
|
|
40
169
|
if (nestedPostgresSqlState(error) === "42501") {
|
|
41
170
|
throw new HTTPException(403, {
|
|
42
171
|
message: "organization membership is not active",
|
|
43
172
|
});
|
|
44
173
|
}
|
|
45
|
-
|
|
174
|
+
rethrowMembershipError(error);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
app.get("/v1/organization-invitations", async (context) => {
|
|
179
|
+
const { session, subjectId } = await requireManagedHuman(context, deps);
|
|
180
|
+
const query = ListOrganizationInvitationsPageQuery.safeParse(context.req.query());
|
|
181
|
+
if (!query.success) {
|
|
182
|
+
throw new HTTPException(422, {
|
|
183
|
+
message: "invalid organization invitation page",
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
if (session.user.emailVerified) {
|
|
188
|
+
await bindPendingOrganizationInvitationsForVerifiedEmail(deps.db, {
|
|
189
|
+
subjectId,
|
|
190
|
+
email: session.user.email,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return context.json(
|
|
194
|
+
ListOrganizationInvitationsPageResponse.parse(
|
|
195
|
+
await listSelfOrganizationInvitations(deps.db, {
|
|
196
|
+
subjectId,
|
|
197
|
+
...(query.data.cursor === undefined ? {} : { cursor: query.data.cursor }),
|
|
198
|
+
limit: query.data.limit,
|
|
199
|
+
}),
|
|
200
|
+
),
|
|
201
|
+
);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
rethrowMembershipError(error);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
app.get("/v1/organizations/:organizationId/overview", async (context) => {
|
|
208
|
+
const { subjectId } = await requireManagedHuman(context, deps);
|
|
209
|
+
const organizationId = parseId(
|
|
210
|
+
OrganizationId,
|
|
211
|
+
context.req.param("organizationId"),
|
|
212
|
+
"organization id",
|
|
213
|
+
);
|
|
214
|
+
try {
|
|
215
|
+
return context.json(
|
|
216
|
+
OrganizationAdministrationOverview.parse(
|
|
217
|
+
await getOrganizationAdministrationOverview(deps.db, {
|
|
218
|
+
organizationId,
|
|
219
|
+
actorSubjectId: subjectId,
|
|
220
|
+
}),
|
|
221
|
+
),
|
|
222
|
+
);
|
|
223
|
+
} catch (error) {
|
|
224
|
+
rethrowMembershipError(error);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
app.patch("/v1/organizations/:organizationId", async (context) => {
|
|
229
|
+
const { subjectId } = await requireManagedHuman(context, deps);
|
|
230
|
+
const organizationId = parseId(
|
|
231
|
+
OrganizationId,
|
|
232
|
+
context.req.param("organizationId"),
|
|
233
|
+
"organization id",
|
|
234
|
+
);
|
|
235
|
+
const payload = await parseBody(context, UpdateOrganizationNameRequest);
|
|
236
|
+
try {
|
|
237
|
+
return context.json(
|
|
238
|
+
OrganizationSummary.parse(
|
|
239
|
+
await updateOrganizationName(deps.db, {
|
|
240
|
+
organizationId,
|
|
241
|
+
actorSubjectId: subjectId,
|
|
242
|
+
...payload,
|
|
243
|
+
}),
|
|
244
|
+
),
|
|
245
|
+
);
|
|
246
|
+
} catch (error) {
|
|
247
|
+
rethrowMembershipError(error);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
app.patch("/v1/organizations/:organizationId/workspaces/:workspaceId", async (context) => {
|
|
252
|
+
const { subjectId } = await requireManagedHuman(context, deps);
|
|
253
|
+
const organizationId = parseId(
|
|
254
|
+
OrganizationId,
|
|
255
|
+
context.req.param("organizationId"),
|
|
256
|
+
"organization id",
|
|
257
|
+
);
|
|
258
|
+
const workspaceId = parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id");
|
|
259
|
+
const payload = await parseBody(context, UpdateWorkspaceRequest);
|
|
260
|
+
try {
|
|
261
|
+
return context.json(
|
|
262
|
+
Workspace.parse(
|
|
263
|
+
await updateOrganizationSharedWorkspace(deps.db, {
|
|
264
|
+
organizationId,
|
|
265
|
+
workspaceId,
|
|
266
|
+
actorSubjectId: subjectId,
|
|
267
|
+
...(payload.name !== undefined ? { name: payload.name.trim() } : {}),
|
|
268
|
+
...(payload.slug !== undefined ? { slug: payload.slug?.trim() || null } : {}),
|
|
269
|
+
...(payload.agentInstructions !== undefined
|
|
270
|
+
? {
|
|
271
|
+
agentInstructions: normalizeAgentInstructions(payload.agentInstructions),
|
|
272
|
+
}
|
|
273
|
+
: {}),
|
|
274
|
+
}),
|
|
275
|
+
),
|
|
276
|
+
);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
rethrowMembershipError(error);
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
app.post("/v1/organizations/:organizationId/workspaces", async (context) => {
|
|
283
|
+
const { subjectId } = await requireManagedHuman(context, deps);
|
|
284
|
+
const organizationId = parseId(
|
|
285
|
+
OrganizationId,
|
|
286
|
+
context.req.param("organizationId"),
|
|
287
|
+
"organization id",
|
|
288
|
+
);
|
|
289
|
+
const payload = await parseBody(context, CreateOrganizationWorkspaceRequest);
|
|
290
|
+
try {
|
|
291
|
+
return context.json(
|
|
292
|
+
Workspace.parse(
|
|
293
|
+
await createOrganizationSharedWorkspace(deps.db, {
|
|
294
|
+
organizationId,
|
|
295
|
+
actorSubjectId: subjectId,
|
|
296
|
+
name: payload.name.trim(),
|
|
297
|
+
...(payload.slug !== undefined ? { slug: payload.slug?.trim() || null } : {}),
|
|
298
|
+
...(payload.agentInstructions !== undefined
|
|
299
|
+
? { agentInstructions: normalizeAgentInstructions(payload.agentInstructions) }
|
|
300
|
+
: {}),
|
|
301
|
+
operationId: payload.operationId,
|
|
302
|
+
}),
|
|
303
|
+
),
|
|
304
|
+
201,
|
|
305
|
+
);
|
|
306
|
+
} catch (error) {
|
|
307
|
+
rethrowMembershipError(error);
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
app.patch(
|
|
312
|
+
"/v1/organizations/:organizationId/workspaces/:workspaceId/settings",
|
|
313
|
+
async (context) => {
|
|
314
|
+
const { subjectId } = await requireManagedHuman(context, deps);
|
|
315
|
+
const organizationId = parseId(
|
|
316
|
+
OrganizationId,
|
|
317
|
+
context.req.param("organizationId"),
|
|
318
|
+
"organization id",
|
|
319
|
+
);
|
|
320
|
+
const workspaceId = parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id");
|
|
321
|
+
const payload = await parseBody(context, UpdateWorkspaceSettingsRequest);
|
|
322
|
+
try {
|
|
323
|
+
return context.json(
|
|
324
|
+
Workspace.parse(
|
|
325
|
+
await updateOrganizationSharedWorkspaceSettings(deps.db, {
|
|
326
|
+
organizationId,
|
|
327
|
+
workspaceId,
|
|
328
|
+
actorSubjectId: subjectId,
|
|
329
|
+
patch: payload,
|
|
330
|
+
}),
|
|
331
|
+
),
|
|
332
|
+
);
|
|
333
|
+
} catch (error) {
|
|
334
|
+
rethrowMembershipError(error);
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
app.post("/v1/organizations/:organizationId/workspaces/:workspaceId/members", async (context) => {
|
|
340
|
+
const { subjectId } = await requireManagedHuman(context, deps);
|
|
341
|
+
const organizationId = parseId(
|
|
342
|
+
OrganizationId,
|
|
343
|
+
context.req.param("organizationId"),
|
|
344
|
+
"organization id",
|
|
345
|
+
);
|
|
346
|
+
const workspaceId = parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id");
|
|
347
|
+
const payload = await parseBody(context, AddOrganizationWorkspaceMemberRequest);
|
|
348
|
+
try {
|
|
349
|
+
const members = await listOrganizationMembers(deps.db, {
|
|
350
|
+
organizationId,
|
|
351
|
+
actorSubjectId: subjectId,
|
|
352
|
+
});
|
|
353
|
+
const target = members.find(
|
|
354
|
+
(member) => member.id === payload.organizationMembershipId && member.status === "active",
|
|
355
|
+
);
|
|
356
|
+
if (!target) {
|
|
357
|
+
throw new HTTPException(404, {
|
|
358
|
+
message: "active organization member not found",
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
const [profile] = await getManagedUserProfilesByIds(deps.db, [
|
|
362
|
+
target.subjectId.slice("user:".length),
|
|
363
|
+
]);
|
|
364
|
+
return context.json(
|
|
365
|
+
WorkspaceMember.parse(
|
|
366
|
+
await upsertOrganizationSharedWorkspaceMember(deps.db, {
|
|
367
|
+
organizationId,
|
|
368
|
+
workspaceId,
|
|
369
|
+
actorSubjectId: subjectId,
|
|
370
|
+
targetOrganizationMembershipId: target.id,
|
|
371
|
+
subjectLabel: profile?.email ?? profile?.name ?? null,
|
|
372
|
+
role: payload.role ?? "member",
|
|
373
|
+
permissions: parseWorkspacePermissions(payload.permissions),
|
|
374
|
+
requireExisting: false,
|
|
375
|
+
}),
|
|
376
|
+
),
|
|
377
|
+
201,
|
|
378
|
+
);
|
|
379
|
+
} catch (error) {
|
|
380
|
+
if (error instanceof HTTPException) throw error;
|
|
381
|
+
rethrowMembershipError(error);
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
app.patch(
|
|
386
|
+
"/v1/organizations/:organizationId/workspaces/:workspaceId/members/:subjectId",
|
|
387
|
+
async (context) => {
|
|
388
|
+
const { subjectId: actorSubjectId } = await requireManagedHuman(context, deps);
|
|
389
|
+
const organizationId = parseId(
|
|
390
|
+
OrganizationId,
|
|
391
|
+
context.req.param("organizationId"),
|
|
392
|
+
"organization id",
|
|
393
|
+
);
|
|
394
|
+
const workspaceId = parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id");
|
|
395
|
+
const targetSubjectId = decodeURIComponent(context.req.param("subjectId"));
|
|
396
|
+
const payload = await parseBody(context, UpdateWorkspaceMemberRequest);
|
|
397
|
+
try {
|
|
398
|
+
const members = await listOrganizationMembers(deps.db, {
|
|
399
|
+
organizationId,
|
|
400
|
+
actorSubjectId,
|
|
401
|
+
});
|
|
402
|
+
const target = members.find(
|
|
403
|
+
(member) => member.subjectId === targetSubjectId && member.status === "active",
|
|
404
|
+
);
|
|
405
|
+
if (!target) {
|
|
406
|
+
throw new HTTPException(404, {
|
|
407
|
+
message: "active organization member not found",
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
const [profile] = await getManagedUserProfilesByIds(deps.db, [
|
|
411
|
+
target.subjectId.slice("user:".length),
|
|
412
|
+
]);
|
|
413
|
+
return context.json(
|
|
414
|
+
WorkspaceMember.parse(
|
|
415
|
+
await upsertOrganizationSharedWorkspaceMember(deps.db, {
|
|
416
|
+
organizationId,
|
|
417
|
+
workspaceId,
|
|
418
|
+
actorSubjectId,
|
|
419
|
+
targetOrganizationMembershipId: target.id,
|
|
420
|
+
subjectLabel: profile?.email ?? profile?.name ?? null,
|
|
421
|
+
...(payload.role === undefined ? {} : { role: payload.role }),
|
|
422
|
+
permissions: payload.permissions,
|
|
423
|
+
requireExisting: true,
|
|
424
|
+
}),
|
|
425
|
+
),
|
|
426
|
+
);
|
|
427
|
+
} catch (error) {
|
|
428
|
+
if (error instanceof HTTPException) throw error;
|
|
429
|
+
rethrowMembershipError(error);
|
|
430
|
+
}
|
|
431
|
+
},
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
app.delete(
|
|
435
|
+
"/v1/organizations/:organizationId/workspaces/:workspaceId/members/:subjectId",
|
|
436
|
+
async (context) => {
|
|
437
|
+
const { subjectId: actorSubjectId } = await requireManagedHuman(context, deps);
|
|
438
|
+
const organizationId = parseId(
|
|
439
|
+
OrganizationId,
|
|
440
|
+
context.req.param("organizationId"),
|
|
441
|
+
"organization id",
|
|
442
|
+
);
|
|
443
|
+
const workspaceId = parseId(WorkspaceId, context.req.param("workspaceId"), "workspace id");
|
|
444
|
+
const targetSubjectId = decodeURIComponent(context.req.param("subjectId"));
|
|
445
|
+
try {
|
|
446
|
+
await removeWorkspaceMember(deps.db, {
|
|
447
|
+
accountId: organizationId,
|
|
448
|
+
workspaceId,
|
|
449
|
+
actorSubjectId,
|
|
450
|
+
targetSubjectId,
|
|
451
|
+
requireOrganizationSharedWorkspaceAdministration: true,
|
|
452
|
+
});
|
|
453
|
+
return context.body(null, 204);
|
|
454
|
+
} catch (error) {
|
|
455
|
+
rethrowMembershipError(error);
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
app.get("/v1/organizations/:organizationId/private-session-settings", async (context) => {
|
|
461
|
+
const { subjectId } = await requireManagedHuman(context, deps);
|
|
462
|
+
const organizationId = parseId(
|
|
463
|
+
OrganizationId,
|
|
464
|
+
context.req.param("organizationId"),
|
|
465
|
+
"organization id",
|
|
466
|
+
);
|
|
467
|
+
try {
|
|
468
|
+
return context.json(
|
|
469
|
+
OrganizationPrivateSessionSettings.parse(
|
|
470
|
+
await getOrganizationPrivateSessionSettings(deps.db, {
|
|
471
|
+
organizationId,
|
|
472
|
+
actorSubjectId: subjectId,
|
|
473
|
+
}),
|
|
474
|
+
),
|
|
475
|
+
);
|
|
476
|
+
} catch (error) {
|
|
477
|
+
rethrowMembershipError(error);
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
app.patch("/v1/organizations/:organizationId/private-session-settings", async (context) => {
|
|
482
|
+
const { subjectId } = await requireManagedHuman(context, deps);
|
|
483
|
+
const organizationId = parseId(
|
|
484
|
+
OrganizationId,
|
|
485
|
+
context.req.param("organizationId"),
|
|
486
|
+
"organization id",
|
|
487
|
+
);
|
|
488
|
+
const payload = await parseBody(context, UpdateOrganizationPrivateSessionSettingsRequest);
|
|
489
|
+
try {
|
|
490
|
+
return context.json(
|
|
491
|
+
OrganizationPrivateSessionSettings.parse(
|
|
492
|
+
await updateOrganizationPrivateSessionSettings(deps.db, {
|
|
493
|
+
organizationId,
|
|
494
|
+
actorSubjectId: subjectId,
|
|
495
|
+
...payload,
|
|
496
|
+
}),
|
|
497
|
+
),
|
|
498
|
+
);
|
|
499
|
+
} catch (error) {
|
|
500
|
+
if (nestedPostgresSqlState(error) === "55000") {
|
|
501
|
+
throw new HTTPException(409, {
|
|
502
|
+
message: "private-session readiness is not activated for this organization",
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
rethrowMembershipError(error);
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
app.post("/v1/organizations/:organizationId/invitations", async (context) => {
|
|
510
|
+
const { subjectId } = await requireManagedHuman(context, deps);
|
|
511
|
+
const organizationId = parseId(
|
|
512
|
+
OrganizationId,
|
|
513
|
+
context.req.param("organizationId"),
|
|
514
|
+
"organization id",
|
|
515
|
+
);
|
|
516
|
+
const payload = await parseBody(context, CreateOrganizationInvitationRequest);
|
|
517
|
+
try {
|
|
518
|
+
return context.json(
|
|
519
|
+
OrganizationInvitation.parse(
|
|
520
|
+
await createOrganizationInvitation(deps.db, {
|
|
521
|
+
organizationId,
|
|
522
|
+
actorSubjectId: subjectId,
|
|
523
|
+
operationId: payload.operationId,
|
|
524
|
+
targetSubjectId: null,
|
|
525
|
+
targetEmail: payload.email.trim().toLowerCase(),
|
|
526
|
+
...(payload.name === undefined ? {} : { targetName: payload.name }),
|
|
527
|
+
initialWorkspaceIds: payload.initialWorkspaceIds,
|
|
528
|
+
role: payload.role,
|
|
529
|
+
expiresAt: payload.expiresAt,
|
|
530
|
+
}),
|
|
531
|
+
),
|
|
532
|
+
201,
|
|
533
|
+
);
|
|
534
|
+
} catch (error) {
|
|
535
|
+
if (error instanceof HTTPException) throw error;
|
|
536
|
+
rethrowMembershipError(error);
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
app.get("/v1/organizations/:organizationId/invitations", async (context) => {
|
|
541
|
+
const { subjectId } = await requireManagedHuman(context, deps);
|
|
542
|
+
const organizationId = parseId(
|
|
543
|
+
OrganizationId,
|
|
544
|
+
context.req.param("organizationId"),
|
|
545
|
+
"organization id",
|
|
546
|
+
);
|
|
547
|
+
const query = ListOrganizationInvitationsPageQuery.safeParse(context.req.query());
|
|
548
|
+
if (!query.success) {
|
|
549
|
+
throw new HTTPException(422, {
|
|
550
|
+
message: "invalid organization invitation page",
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
try {
|
|
554
|
+
return context.json(
|
|
555
|
+
ListOrganizationInvitationsPageResponse.parse(
|
|
556
|
+
await listOrganizationInvitations(deps.db, {
|
|
557
|
+
organizationId,
|
|
558
|
+
actorSubjectId: subjectId,
|
|
559
|
+
...(query.data.cursor === undefined ? {} : { cursor: query.data.cursor }),
|
|
560
|
+
limit: query.data.limit,
|
|
561
|
+
}),
|
|
562
|
+
),
|
|
563
|
+
);
|
|
564
|
+
} catch (error) {
|
|
565
|
+
rethrowMembershipError(error);
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
app.post("/v1/organization-invitations/:invitationId/accept", async (context) => {
|
|
570
|
+
const { session, subjectId } = await requireManagedHuman(context, deps);
|
|
571
|
+
const invitationId = parseId(InvitationId, context.req.param("invitationId"), "invitation id");
|
|
572
|
+
const payload = await parseBody(context, AcceptOrganizationInvitationRequest);
|
|
573
|
+
try {
|
|
574
|
+
if (session.user.emailVerified) {
|
|
575
|
+
await bindPendingOrganizationInvitationsForVerifiedEmail(deps.db, {
|
|
576
|
+
subjectId,
|
|
577
|
+
email: session.user.email,
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
const invitation = await getSelfOrganizationInvitation(deps.db, {
|
|
581
|
+
subjectId,
|
|
582
|
+
invitationId,
|
|
583
|
+
});
|
|
584
|
+
const result = await acceptOrganizationInvitation(deps.db, {
|
|
585
|
+
organizationId: invitation.organizationId,
|
|
586
|
+
actorSubjectId: subjectId,
|
|
587
|
+
operationId: payload.operationId,
|
|
588
|
+
invitationId,
|
|
589
|
+
expectedRevision: payload.expectedRevision,
|
|
590
|
+
});
|
|
591
|
+
return context.json({
|
|
592
|
+
invitation: OrganizationInvitation.parse(result.invitation),
|
|
593
|
+
membership: OrganizationMember.parse(result.membership),
|
|
594
|
+
});
|
|
595
|
+
} catch (error) {
|
|
596
|
+
if (error instanceof HTTPException) throw error;
|
|
597
|
+
rethrowMembershipError(error);
|
|
46
598
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
app.post(
|
|
602
|
+
"/v1/organizations/:organizationId/invitations/:invitationId/revoke",
|
|
603
|
+
async (context) => {
|
|
604
|
+
const { subjectId } = await requireManagedHuman(context, deps);
|
|
605
|
+
const organizationId = parseId(
|
|
606
|
+
OrganizationId,
|
|
607
|
+
context.req.param("organizationId"),
|
|
608
|
+
"organization id",
|
|
609
|
+
);
|
|
610
|
+
const invitationId = parseId(
|
|
611
|
+
InvitationId,
|
|
612
|
+
context.req.param("invitationId"),
|
|
613
|
+
"invitation id",
|
|
614
|
+
);
|
|
615
|
+
const payload = await parseBody(context, RevokeOrganizationInvitationRequest);
|
|
616
|
+
try {
|
|
617
|
+
return context.json(
|
|
618
|
+
OrganizationInvitation.parse(
|
|
619
|
+
await revokeOrganizationInvitation(deps.db, {
|
|
620
|
+
organizationId,
|
|
621
|
+
actorSubjectId: subjectId,
|
|
622
|
+
operationId: payload.operationId,
|
|
623
|
+
invitationId,
|
|
624
|
+
expectedRevision: payload.expectedRevision,
|
|
625
|
+
}),
|
|
626
|
+
),
|
|
627
|
+
);
|
|
628
|
+
} catch (error) {
|
|
629
|
+
rethrowMembershipError(error);
|
|
630
|
+
}
|
|
631
|
+
},
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
app.get("/v1/organizations/:organizationId/members", async (context) => {
|
|
635
|
+
const { subjectId } = await requireManagedHuman(context, deps);
|
|
636
|
+
const organizationId = parseId(
|
|
637
|
+
OrganizationId,
|
|
638
|
+
context.req.param("organizationId"),
|
|
639
|
+
"organization id",
|
|
51
640
|
);
|
|
641
|
+
try {
|
|
642
|
+
const members = await listOrganizationMembers(deps.db, {
|
|
643
|
+
organizationId,
|
|
644
|
+
actorSubjectId: subjectId,
|
|
645
|
+
});
|
|
646
|
+
const profiles = await getManagedUserProfilesByIds(
|
|
647
|
+
deps.db,
|
|
648
|
+
members.flatMap((member) =>
|
|
649
|
+
member.subjectId.startsWith("user:") ? [member.subjectId.slice("user:".length)] : [],
|
|
650
|
+
),
|
|
651
|
+
);
|
|
652
|
+
const profileById = new Map(profiles.map((profile) => [profile.id, profile]));
|
|
653
|
+
return context.json(
|
|
654
|
+
ListOrganizationMembersResponse.parse({
|
|
655
|
+
members: members.map((member) => {
|
|
656
|
+
const profile = profileById.get(member.subjectId.slice("user:".length));
|
|
657
|
+
return {
|
|
658
|
+
...member,
|
|
659
|
+
name: profile?.name?.trim() || null,
|
|
660
|
+
email: profile?.email ?? null,
|
|
661
|
+
};
|
|
662
|
+
}),
|
|
663
|
+
}),
|
|
664
|
+
);
|
|
665
|
+
} catch (error) {
|
|
666
|
+
rethrowMembershipError(error);
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
app.patch("/v1/organizations/:organizationId/members/:membershipId", async (context) => {
|
|
671
|
+
const { subjectId } = await requireManagedHuman(context, deps);
|
|
672
|
+
const organizationId = parseId(
|
|
673
|
+
OrganizationId,
|
|
674
|
+
context.req.param("organizationId"),
|
|
675
|
+
"organization id",
|
|
676
|
+
);
|
|
677
|
+
const membershipId = parseId(MembershipId, context.req.param("membershipId"), "membership id");
|
|
678
|
+
const payload = await parseBody(context, UpdateOrganizationMemberRequest);
|
|
679
|
+
try {
|
|
680
|
+
return context.json(
|
|
681
|
+
OrganizationMember.parse(
|
|
682
|
+
await updateOrganizationMember(deps.db, {
|
|
683
|
+
organizationId,
|
|
684
|
+
actorSubjectId: subjectId,
|
|
685
|
+
operationId: payload.operationId,
|
|
686
|
+
membershipId,
|
|
687
|
+
transition: payload,
|
|
688
|
+
}),
|
|
689
|
+
),
|
|
690
|
+
);
|
|
691
|
+
} catch (error) {
|
|
692
|
+
rethrowMembershipError(error);
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
app.get("/v1/organizations/:organizationId/retention-policy", async (context) => {
|
|
697
|
+
const { subjectId } = await requireManagedHuman(context, deps);
|
|
698
|
+
const organizationId = parseId(
|
|
699
|
+
OrganizationId,
|
|
700
|
+
context.req.param("organizationId"),
|
|
701
|
+
"organization id",
|
|
702
|
+
);
|
|
703
|
+
try {
|
|
704
|
+
return context.json(
|
|
705
|
+
OrganizationRetentionPolicy.parse(
|
|
706
|
+
await getOrganizationRetentionPolicy(deps.db, {
|
|
707
|
+
organizationId,
|
|
708
|
+
actorSubjectId: subjectId,
|
|
709
|
+
}),
|
|
710
|
+
),
|
|
711
|
+
);
|
|
712
|
+
} catch (error) {
|
|
713
|
+
rethrowMembershipError(error);
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
app.patch("/v1/organizations/:organizationId/retention-policy", async (context) => {
|
|
718
|
+
const { subjectId } = await requireManagedHuman(context, deps);
|
|
719
|
+
const organizationId = parseId(
|
|
720
|
+
OrganizationId,
|
|
721
|
+
context.req.param("organizationId"),
|
|
722
|
+
"organization id",
|
|
723
|
+
);
|
|
724
|
+
const payload = await parseBody(context, UpdateOrganizationRetentionPolicyRequest);
|
|
725
|
+
try {
|
|
726
|
+
return context.json(
|
|
727
|
+
OrganizationRetentionPolicy.parse(
|
|
728
|
+
await updateOrganizationRetentionPolicy(deps.db, {
|
|
729
|
+
organizationId,
|
|
730
|
+
actorSubjectId: subjectId,
|
|
731
|
+
operationId: payload.operationId,
|
|
732
|
+
mode: payload.mode,
|
|
733
|
+
retentionDays: payload.retentionDays,
|
|
734
|
+
expectedVersion: payload.expectedVersion,
|
|
735
|
+
}),
|
|
736
|
+
),
|
|
737
|
+
);
|
|
738
|
+
} catch (error) {
|
|
739
|
+
rethrowMembershipError(error);
|
|
740
|
+
}
|
|
52
741
|
});
|
|
53
742
|
}
|