@shipfox/api-workspaces 10.1.0 → 10.2.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.
Files changed (56) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +19 -0
  3. package/README.md +7 -1
  4. package/dist/core/admin-workspaces.d.ts +16 -0
  5. package/dist/core/admin-workspaces.d.ts.map +1 -1
  6. package/dist/core/admin-workspaces.js +52 -0
  7. package/dist/core/admin-workspaces.js.map +1 -1
  8. package/dist/core/errors.d.ts +12 -0
  9. package/dist/core/errors.d.ts.map +1 -1
  10. package/dist/core/errors.js +24 -0
  11. package/dist/core/errors.js.map +1 -1
  12. package/dist/core/index.d.ts +2 -2
  13. package/dist/core/index.d.ts.map +1 -1
  14. package/dist/core/index.js +2 -2
  15. package/dist/core/index.js.map +1 -1
  16. package/dist/db/admin-workspace-commands.d.ts +17 -0
  17. package/dist/db/admin-workspace-commands.d.ts.map +1 -0
  18. package/dist/db/admin-workspace-commands.js +86 -0
  19. package/dist/db/admin-workspace-commands.js.map +1 -0
  20. package/dist/db/db.d.ts +256 -0
  21. package/dist/db/db.d.ts.map +1 -1
  22. package/dist/db/db.js +2 -0
  23. package/dist/db/db.js.map +1 -1
  24. package/dist/db/index.d.ts +1 -0
  25. package/dist/db/index.d.ts.map +1 -1
  26. package/dist/db/index.js +1 -0
  27. package/dist/db/index.js.map +1 -1
  28. package/dist/db/schema/admin-command-results.d.ts +135 -0
  29. package/dist/db/schema/admin-command-results.d.ts.map +1 -0
  30. package/dist/db/schema/admin-command-results.js +18 -0
  31. package/dist/db/schema/admin-command-results.js.map +1 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +7 -2
  34. package/dist/index.js.map +1 -1
  35. package/dist/presentation/routes/admin-workspaces.d.ts.map +1 -1
  36. package/dist/presentation/routes/admin-workspaces.js +119 -14
  37. package/dist/presentation/routes/admin-workspaces.js.map +1 -1
  38. package/dist/tsconfig.test.tsbuildinfo +1 -1
  39. package/drizzle/0002_wide_enchantress.sql +11 -0
  40. package/drizzle/meta/0002_snapshot.json +555 -0
  41. package/drizzle/meta/_journal.json +7 -0
  42. package/package.json +5 -5
  43. package/src/core/admin-workspaces.ts +102 -0
  44. package/src/core/errors.ts +28 -0
  45. package/src/core/index.ts +8 -0
  46. package/src/db/admin-workspace-commands.ts +169 -0
  47. package/src/db/db.ts +2 -0
  48. package/src/db/index.ts +1 -0
  49. package/src/db/schema/admin-command-results.ts +30 -0
  50. package/src/index.test.ts +3 -1
  51. package/src/index.ts +17 -2
  52. package/src/presentation/routes/admin-workspaces.test.ts +273 -3
  53. package/src/presentation/routes/admin-workspaces.ts +143 -13
  54. package/test/globalSetup.ts +3 -1
  55. package/tsconfig.build.tsbuildinfo +1 -1
  56. package/vitest.config.ts +3 -0
@@ -1,2 +1,2 @@
1
1
  $ shipfox-swc
2
- Successfully compiled: 50 files with swc (524.08ms)
2
+ Successfully compiled: 52 files with swc (363.1ms)
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @shipfox/api-workspaces
2
2
 
3
+ ## 10.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 07e7371: Add idempotent administrator workspace suspension and reactivation commands with atomic redacted administration events.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [c9a188d]
12
+ - Updated dependencies [8678943]
13
+ - Updated dependencies [95d1456]
14
+ - Updated dependencies [0773b85]
15
+ - Updated dependencies [6be5a54]
16
+ - Updated dependencies [07e7371]
17
+ - @shipfox/api-runners-dto@10.2.0
18
+ - @shipfox/api-auth-dto@10.2.0
19
+ - @shipfox/api-workspaces-dto@10.2.0
20
+ - @shipfox/api-auth-context@10.2.0
21
+
3
22
  ## 10.1.0
4
23
 
5
24
  ### Patch Changes
package/README.md CHANGED
@@ -47,7 +47,12 @@ Routes mount under `/workspaces`. They make and list workspaces, manage members,
47
47
  and make, list, view, accept, or revoke invites. The composed module also mounts
48
48
  `GET /admin/workspaces`, which requires the Auth `admin-observer` role and returns
49
49
  bounded workspace identity, lifecycle, member, project, and best-effort job-count
50
- summaries. Supporting count failures are represented as `unknown`.
50
+ summaries. Supporting count failures are represented as `unknown`. `POST
51
+ /admin/workspaces/:workspaceId/suspend` and `POST
52
+ /admin/workspaces/:workspaceId/reactivate` require the `admin-operator` role, an
53
+ `Idempotency-Key` header, and a `{reason}` body, and return `{workspace_id,
54
+ status, correlation_id}`. Both write a redacted `administration.action.performed`
55
+ outbox event and record their result for idempotent retries.
51
56
 
52
57
  The module creates these tables:
53
58
 
@@ -55,6 +60,7 @@ The module creates these tables:
55
60
  - `workspaces_memberships`
56
61
  - `workspaces_invitations`
57
62
  - `workspaces_outbox`
63
+ - `workspaces_admin_command_results`
58
64
 
59
65
  ## Behavior Notes
60
66
 
@@ -1,6 +1,22 @@
1
+ import { type AdministrationRole } from '@shipfox/api-common-dto';
1
2
  import type { ProjectsModuleClient } from '@shipfox/api-projects-dto/inter-module';
2
3
  import type { RunnersInterModuleClient } from '@shipfox/api-runners-dto/inter-module';
3
4
  import type { StringIdCursor } from '@shipfox/node-drizzle';
5
+ export interface WorkspaceAdministrationMutationContext {
6
+ actorId: string;
7
+ actorRole: AdministrationRole;
8
+ idempotencyKey: string;
9
+ correlationId: string;
10
+ workspaceId: string;
11
+ reason: string;
12
+ }
13
+ export interface WorkspaceAdministrationMutationResult {
14
+ workspaceId: string;
15
+ status: 'active' | 'suspended';
16
+ correlationId: string;
17
+ }
18
+ export declare function suspendWorkspace(params: WorkspaceAdministrationMutationContext): Promise<WorkspaceAdministrationMutationResult>;
19
+ export declare function reactivateWorkspace(params: WorkspaceAdministrationMutationContext): Promise<WorkspaceAdministrationMutationResult>;
4
20
  export interface WorkspaceAdministratorSummary {
5
21
  id: string;
6
22
  name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"admin-workspaces.d.ts","sourceRoot":"","sources":["../../src/core/admin-workspaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,wCAAwC,CAAC;AACjF,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,uCAAuC,CAAC;AACpF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAI1D,MAAM,WAAW,6BAA6B;IAC5C,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC;IAC3C,aAAa,EAAE;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,CAAC;IAC/B,cAAc,EAAE;QAAC,KAAK,EAAE,WAAW,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,GAAG;QAAC,KAAK,EAAE,SAAS,CAAA;KAAC,CAAC;IACzE,SAAS,EAAE;QAAC,KAAK,EAAE,WAAW,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC,GAAG;QAAC,KAAK,EAAE,SAAS,CAAA;KAAC,CAAC;IACtF,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,yCAAyC;IACxD,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACpC,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,OAAO,EAAE,wBAAwB,CAAC;CACnC;AAED,MAAM,WAAW,yCAAyC;IACxD,UAAU,EAAE,6BAA6B,EAAE,CAAC;IAC5C,UAAU,EAAE,cAAc,GAAG,IAAI,CAAC;CACnC;AAED,wBAAsB,mCAAmC,CACvD,MAAM,EAAE,yCAAyC,GAChD,OAAO,CAAC,yCAAyC,CAAC,CAqBpD"}
1
+ {"version":3,"file":"admin-workspaces.d.ts","sourceRoot":"","sources":["../../src/core/admin-workspaces.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,kBAAkB,EAAkC,MAAM,yBAAyB,CAAC;AACjG,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,wCAAwC,CAAC;AACjF,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,uCAAuC,CAAC;AACpF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAa1D,MAAM,WAAW,sCAAsC;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,kBAAkB,CAAC;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,qCAAqC;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC/B,aAAa,EAAE,MAAM,CAAC;CACvB;AA2DD,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,sCAAsC,GAC7C,OAAO,CAAC,qCAAqC,CAAC,CAMhD;AAED,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,sCAAsC,GAC7C,OAAO,CAAC,qCAAqC,CAAC,CAMhD;AAED,MAAM,WAAW,6BAA6B;IAC5C,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC;IAC3C,aAAa,EAAE;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,CAAC;IAC/B,cAAc,EAAE;QAAC,KAAK,EAAE,WAAW,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,GAAG;QAAC,KAAK,EAAE,SAAS,CAAA;KAAC,CAAC;IACzE,SAAS,EAAE;QAAC,KAAK,EAAE,WAAW,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC,GAAG;QAAC,KAAK,EAAE,SAAS,CAAA;KAAC,CAAC;IACtF,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,yCAAyC;IACxD,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,CAAC;IACxD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC;IACpC,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,OAAO,EAAE,wBAAwB,CAAC;CACnC;AAED,MAAM,WAAW,yCAAyC;IACxD,UAAU,EAAE,6BAA6B,EAAE,CAAC;IAC5C,UAAU,EAAE,cAAc,GAAG,IAAI,CAAC;CACnC;AAED,wBAAsB,mCAAmC,CACvD,MAAM,EAAE,yCAAyC,GAChD,OAAO,CAAC,yCAAyC,CAAC,CAqBpD"}
@@ -1,5 +1,57 @@
1
+ import { createAdministrationActionEvent } from '@shipfox/api-common-dto';
1
2
  import { logger } from '@shipfox/node-opentelemetry';
3
+ import { hashOpaqueToken } from '@shipfox/node-tokens';
4
+ import { reactivateWorkspaceWithAudit, suspendWorkspaceWithAudit } from '#db/admin-workspace-commands.js';
2
5
  import { listAdminWorkspaces as listAdminWorkspaceRows } from '#db/workspaces.js';
6
+ const ADMIN_OPERATOR_ROLE = 'admin-operator';
7
+ const SUSPEND_COMMAND = 'workspace.suspend';
8
+ const REACTIVATE_COMMAND = 'workspace.reactivate';
9
+ function commandFingerprint(command, params) {
10
+ return hashOpaqueToken(`${command}:${JSON.stringify({
11
+ workspaceId: params.workspaceId,
12
+ reason: params.reason
13
+ })}`);
14
+ }
15
+ function administrationEvent(params) {
16
+ return createAdministrationActionEvent({
17
+ actorId: params.actorId,
18
+ actorRole: params.actorRole,
19
+ requiredRole: ADMIN_OPERATOR_ROLE,
20
+ command: params.command,
21
+ targetType: 'workspace',
22
+ targetId: params.workspaceId,
23
+ reason: params.reason,
24
+ result: 'succeeded',
25
+ correlationId: params.correlationId,
26
+ idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,
27
+ occurredAt: new Date().toISOString()
28
+ });
29
+ }
30
+ async function runWorkspaceAdministrationMutation(params, command, update) {
31
+ const idempotencyKeyFingerprint = hashOpaqueToken(params.idempotencyKey);
32
+ const result = await update({
33
+ actorId: params.actorId,
34
+ workspaceId: params.workspaceId,
35
+ idempotencyKeyFingerprint,
36
+ requestFingerprint: commandFingerprint(command, params),
37
+ event: administrationEvent({
38
+ actorId: params.actorId,
39
+ actorRole: params.actorRole,
40
+ command,
41
+ workspaceId: params.workspaceId,
42
+ reason: params.reason,
43
+ correlationId: params.correlationId,
44
+ idempotencyKeyFingerprint
45
+ })
46
+ });
47
+ return result;
48
+ }
49
+ export async function suspendWorkspace(params) {
50
+ return await runWorkspaceAdministrationMutation(params, SUSPEND_COMMAND, suspendWorkspaceWithAudit);
51
+ }
52
+ export async function reactivateWorkspace(params) {
53
+ return await runWorkspaceAdministrationMutation(params, REACTIVATE_COMMAND, reactivateWorkspaceWithAudit);
54
+ }
3
55
  export async function listWorkspaceAdministratorSummaries(params) {
4
56
  const result = await listAdminWorkspaceRows(params);
5
57
  const workspaceIds = result.workspaces.map(({ id })=>id);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/admin-workspaces.ts"],"sourcesContent":["import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';\nimport type {RunnersInterModuleClient} from '@shipfox/api-runners-dto/inter-module';\nimport type {StringIdCursor} from '@shipfox/node-drizzle';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {listAdminWorkspaces as listAdminWorkspaceRows} from '#db/workspaces.js';\n\nexport interface WorkspaceAdministratorSummary {\n id: string;\n name: string;\n status: 'active' | 'suspended' | 'deleted';\n memberSummary: {count: number};\n projectSummary: {state: 'available'; count: number} | {state: 'unknown'};\n jobCounts: {state: 'available'; queued: number; running: number} | {state: 'unknown'};\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface ListWorkspaceAdministratorSummariesParams {\n workspaceId?: string | undefined;\n search?: string | undefined;\n status?: 'active' | 'suspended' | 'deleted' | undefined;\n limit: number;\n cursor?: StringIdCursor | undefined;\n projects: ProjectsModuleClient;\n runners: RunnersInterModuleClient;\n}\n\nexport interface ListWorkspaceAdministratorSummariesResult {\n workspaces: WorkspaceAdministratorSummary[];\n nextCursor: StringIdCursor | null;\n}\n\nexport async function listWorkspaceAdministratorSummaries(\n params: ListWorkspaceAdministratorSummariesParams,\n): Promise<ListWorkspaceAdministratorSummariesResult> {\n const result = await listAdminWorkspaceRows(params);\n const workspaceIds = result.workspaces.map(({id}) => id);\n const [projectCounts, jobCounts] = await Promise.all([\n getProjectCounts(params.projects, workspaceIds),\n getJobCounts(params.runners, workspaceIds),\n ]);\n\n return {\n workspaces: result.workspaces.map((workspace) => ({\n id: workspace.id,\n name: workspace.name,\n status: workspace.status,\n memberSummary: {count: workspace.memberCount},\n projectSummary: projectCounts?.get(workspace.id) ?? {state: 'unknown'},\n jobCounts: jobCounts?.get(workspace.id) ?? {state: 'unknown'},\n createdAt: workspace.createdAt,\n updatedAt: workspace.updatedAt,\n })),\n nextCursor: result.nextCursor,\n };\n}\n\nasync function getProjectCounts(\n projects: ProjectsModuleClient,\n workspaceIds: string[],\n): Promise<Map<string, {state: 'available'; count: number} | {state: 'unknown'}> | null> {\n if (workspaceIds.length === 0) return new Map();\n\n try {\n const result = await projects.getWorkspaceProjectCounts({workspaceIds});\n const counts = new Map(\n result.counts.map(({workspaceId, count}) => [\n workspaceId,\n {state: 'available', count} as const,\n ]),\n );\n return new Map(\n workspaceIds.map((workspaceId) => [\n workspaceId,\n counts.get(workspaceId) ?? {state: 'unknown'},\n ]),\n );\n } catch (error) {\n logger().warn(\n {operation: 'admin-workspace-project-summary', err: error},\n 'Project summary unavailable',\n );\n return null;\n }\n}\n\nasync function getJobCounts(\n runners: RunnersInterModuleClient,\n workspaceIds: string[],\n): Promise<Map<\n string,\n {state: 'available'; queued: number; running: number} | {state: 'unknown'}\n> | null> {\n if (workspaceIds.length === 0) return new Map();\n\n try {\n const result = await runners.getWorkspaceJobCounts({workspaceIds});\n const counts = new Map(\n result.counts.map(({workspaceId, queued, running}) => [\n workspaceId,\n {state: 'available', queued, running} as const,\n ]),\n );\n return new Map(\n workspaceIds.map((workspaceId) => [\n workspaceId,\n counts.get(workspaceId) ?? {state: 'unknown'},\n ]),\n );\n } catch (error) {\n logger().warn({operation: 'admin-workspace-job-counts', err: error}, 'Job counts unavailable');\n return null;\n }\n}\n"],"names":["logger","listAdminWorkspaces","listAdminWorkspaceRows","listWorkspaceAdministratorSummaries","params","result","workspaceIds","workspaces","map","id","projectCounts","jobCounts","Promise","all","getProjectCounts","projects","getJobCounts","runners","workspace","name","status","memberSummary","count","memberCount","projectSummary","get","state","createdAt","updatedAt","nextCursor","length","Map","getWorkspaceProjectCounts","counts","workspaceId","error","warn","operation","err","getWorkspaceJobCounts","queued","running"],"mappings":"AAGA,SAAQA,MAAM,QAAO,8BAA8B;AACnD,SAAQC,uBAAuBC,sBAAsB,QAAO,oBAAoB;AA4BhF,OAAO,eAAeC,oCACpBC,MAAiD;IAEjD,MAAMC,SAAS,MAAMH,uBAAuBE;IAC5C,MAAME,eAAeD,OAAOE,UAAU,CAACC,GAAG,CAAC,CAAC,EAACC,EAAE,EAAC,GAAKA;IACrD,MAAM,CAACC,eAAeC,UAAU,GAAG,MAAMC,QAAQC,GAAG,CAAC;QACnDC,iBAAiBV,OAAOW,QAAQ,EAAET;QAClCU,aAAaZ,OAAOa,OAAO,EAAEX;KAC9B;IAED,OAAO;QACLC,YAAYF,OAAOE,UAAU,CAACC,GAAG,CAAC,CAACU,YAAe,CAAA;gBAChDT,IAAIS,UAAUT,EAAE;gBAChBU,MAAMD,UAAUC,IAAI;gBACpBC,QAAQF,UAAUE,MAAM;gBACxBC,eAAe;oBAACC,OAAOJ,UAAUK,WAAW;gBAAA;gBAC5CC,gBAAgBd,eAAee,IAAIP,UAAUT,EAAE,KAAK;oBAACiB,OAAO;gBAAS;gBACrEf,WAAWA,WAAWc,IAAIP,UAAUT,EAAE,KAAK;oBAACiB,OAAO;gBAAS;gBAC5DC,WAAWT,UAAUS,SAAS;gBAC9BC,WAAWV,UAAUU,SAAS;YAChC,CAAA;QACAC,YAAYxB,OAAOwB,UAAU;IAC/B;AACF;AAEA,eAAef,iBACbC,QAA8B,EAC9BT,YAAsB;IAEtB,IAAIA,aAAawB,MAAM,KAAK,GAAG,OAAO,IAAIC;IAE1C,IAAI;QACF,MAAM1B,SAAS,MAAMU,SAASiB,yBAAyB,CAAC;YAAC1B;QAAY;QACrE,MAAM2B,SAAS,IAAIF,IACjB1B,OAAO4B,MAAM,CAACzB,GAAG,CAAC,CAAC,EAAC0B,WAAW,EAAEZ,KAAK,EAAC,GAAK;gBAC1CY;gBACA;oBAACR,OAAO;oBAAaJ;gBAAK;aAC3B;QAEH,OAAO,IAAIS,IACTzB,aAAaE,GAAG,CAAC,CAAC0B,cAAgB;gBAChCA;gBACAD,OAAOR,GAAG,CAACS,gBAAgB;oBAACR,OAAO;gBAAS;aAC7C;IAEL,EAAE,OAAOS,OAAO;QACdnC,SAASoC,IAAI,CACX;YAACC,WAAW;YAAmCC,KAAKH;QAAK,GACzD;QAEF,OAAO;IACT;AACF;AAEA,eAAenB,aACbC,OAAiC,EACjCX,YAAsB;IAKtB,IAAIA,aAAawB,MAAM,KAAK,GAAG,OAAO,IAAIC;IAE1C,IAAI;QACF,MAAM1B,SAAS,MAAMY,QAAQsB,qBAAqB,CAAC;YAACjC;QAAY;QAChE,MAAM2B,SAAS,IAAIF,IACjB1B,OAAO4B,MAAM,CAACzB,GAAG,CAAC,CAAC,EAAC0B,WAAW,EAAEM,MAAM,EAAEC,OAAO,EAAC,GAAK;gBACpDP;gBACA;oBAACR,OAAO;oBAAac;oBAAQC;gBAAO;aACrC;QAEH,OAAO,IAAIV,IACTzB,aAAaE,GAAG,CAAC,CAAC0B,cAAgB;gBAChCA;gBACAD,OAAOR,GAAG,CAACS,gBAAgB;oBAACR,OAAO;gBAAS;aAC7C;IAEL,EAAE,OAAOS,OAAO;QACdnC,SAASoC,IAAI,CAAC;YAACC,WAAW;YAA8BC,KAAKH;QAAK,GAAG;QACrE,OAAO;IACT;AACF"}
1
+ {"version":3,"sources":["../../src/core/admin-workspaces.ts"],"sourcesContent":["import {type AdministrationRole, createAdministrationActionEvent} from '@shipfox/api-common-dto';\nimport type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';\nimport type {RunnersInterModuleClient} from '@shipfox/api-runners-dto/inter-module';\nimport type {StringIdCursor} from '@shipfox/node-drizzle';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {hashOpaqueToken} from '@shipfox/node-tokens';\nimport {\n reactivateWorkspaceWithAudit,\n suspendWorkspaceWithAudit,\n} from '#db/admin-workspace-commands.js';\nimport {listAdminWorkspaces as listAdminWorkspaceRows} from '#db/workspaces.js';\n\nconst ADMIN_OPERATOR_ROLE: AdministrationRole = 'admin-operator';\nconst SUSPEND_COMMAND = 'workspace.suspend';\nconst REACTIVATE_COMMAND = 'workspace.reactivate';\n\nexport interface WorkspaceAdministrationMutationContext {\n actorId: string;\n actorRole: AdministrationRole;\n idempotencyKey: string;\n correlationId: string;\n workspaceId: string;\n reason: string;\n}\n\nexport interface WorkspaceAdministrationMutationResult {\n workspaceId: string;\n status: 'active' | 'suspended';\n correlationId: string;\n}\n\nfunction commandFingerprint(\n command: string,\n params: WorkspaceAdministrationMutationContext,\n): string {\n return hashOpaqueToken(\n `${command}:${JSON.stringify({workspaceId: params.workspaceId, reason: params.reason})}`,\n );\n}\n\nfunction administrationEvent(params: {\n actorId: string;\n actorRole: AdministrationRole;\n command: string;\n workspaceId: string;\n reason: string;\n correlationId: string;\n idempotencyKeyFingerprint: string;\n}) {\n return createAdministrationActionEvent({\n actorId: params.actorId,\n actorRole: params.actorRole,\n requiredRole: ADMIN_OPERATOR_ROLE,\n command: params.command,\n targetType: 'workspace',\n targetId: params.workspaceId,\n reason: params.reason,\n result: 'succeeded',\n correlationId: params.correlationId,\n idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,\n occurredAt: new Date().toISOString(),\n });\n}\n\nasync function runWorkspaceAdministrationMutation(\n params: WorkspaceAdministrationMutationContext,\n command: string,\n update: typeof suspendWorkspaceWithAudit,\n): Promise<WorkspaceAdministrationMutationResult> {\n const idempotencyKeyFingerprint = hashOpaqueToken(params.idempotencyKey);\n const result = await update({\n actorId: params.actorId,\n workspaceId: params.workspaceId,\n idempotencyKeyFingerprint,\n requestFingerprint: commandFingerprint(command, params),\n event: administrationEvent({\n actorId: params.actorId,\n actorRole: params.actorRole,\n command,\n workspaceId: params.workspaceId,\n reason: params.reason,\n correlationId: params.correlationId,\n idempotencyKeyFingerprint,\n }),\n });\n return result;\n}\n\nexport async function suspendWorkspace(\n params: WorkspaceAdministrationMutationContext,\n): Promise<WorkspaceAdministrationMutationResult> {\n return await runWorkspaceAdministrationMutation(\n params,\n SUSPEND_COMMAND,\n suspendWorkspaceWithAudit,\n );\n}\n\nexport async function reactivateWorkspace(\n params: WorkspaceAdministrationMutationContext,\n): Promise<WorkspaceAdministrationMutationResult> {\n return await runWorkspaceAdministrationMutation(\n params,\n REACTIVATE_COMMAND,\n reactivateWorkspaceWithAudit,\n );\n}\n\nexport interface WorkspaceAdministratorSummary {\n id: string;\n name: string;\n status: 'active' | 'suspended' | 'deleted';\n memberSummary: {count: number};\n projectSummary: {state: 'available'; count: number} | {state: 'unknown'};\n jobCounts: {state: 'available'; queued: number; running: number} | {state: 'unknown'};\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface ListWorkspaceAdministratorSummariesParams {\n workspaceId?: string | undefined;\n search?: string | undefined;\n status?: 'active' | 'suspended' | 'deleted' | undefined;\n limit: number;\n cursor?: StringIdCursor | undefined;\n projects: ProjectsModuleClient;\n runners: RunnersInterModuleClient;\n}\n\nexport interface ListWorkspaceAdministratorSummariesResult {\n workspaces: WorkspaceAdministratorSummary[];\n nextCursor: StringIdCursor | null;\n}\n\nexport async function listWorkspaceAdministratorSummaries(\n params: ListWorkspaceAdministratorSummariesParams,\n): Promise<ListWorkspaceAdministratorSummariesResult> {\n const result = await listAdminWorkspaceRows(params);\n const workspaceIds = result.workspaces.map(({id}) => id);\n const [projectCounts, jobCounts] = await Promise.all([\n getProjectCounts(params.projects, workspaceIds),\n getJobCounts(params.runners, workspaceIds),\n ]);\n\n return {\n workspaces: result.workspaces.map((workspace) => ({\n id: workspace.id,\n name: workspace.name,\n status: workspace.status,\n memberSummary: {count: workspace.memberCount},\n projectSummary: projectCounts?.get(workspace.id) ?? {state: 'unknown'},\n jobCounts: jobCounts?.get(workspace.id) ?? {state: 'unknown'},\n createdAt: workspace.createdAt,\n updatedAt: workspace.updatedAt,\n })),\n nextCursor: result.nextCursor,\n };\n}\n\nasync function getProjectCounts(\n projects: ProjectsModuleClient,\n workspaceIds: string[],\n): Promise<Map<string, {state: 'available'; count: number} | {state: 'unknown'}> | null> {\n if (workspaceIds.length === 0) return new Map();\n\n try {\n const result = await projects.getWorkspaceProjectCounts({workspaceIds});\n const counts = new Map(\n result.counts.map(({workspaceId, count}) => [\n workspaceId,\n {state: 'available', count} as const,\n ]),\n );\n return new Map(\n workspaceIds.map((workspaceId) => [\n workspaceId,\n counts.get(workspaceId) ?? {state: 'unknown'},\n ]),\n );\n } catch (error) {\n logger().warn(\n {operation: 'admin-workspace-project-summary', err: error},\n 'Project summary unavailable',\n );\n return null;\n }\n}\n\nasync function getJobCounts(\n runners: RunnersInterModuleClient,\n workspaceIds: string[],\n): Promise<Map<\n string,\n {state: 'available'; queued: number; running: number} | {state: 'unknown'}\n> | null> {\n if (workspaceIds.length === 0) return new Map();\n\n try {\n const result = await runners.getWorkspaceJobCounts({workspaceIds});\n const counts = new Map(\n result.counts.map(({workspaceId, queued, running}) => [\n workspaceId,\n {state: 'available', queued, running} as const,\n ]),\n );\n return new Map(\n workspaceIds.map((workspaceId) => [\n workspaceId,\n counts.get(workspaceId) ?? {state: 'unknown'},\n ]),\n );\n } catch (error) {\n logger().warn({operation: 'admin-workspace-job-counts', err: error}, 'Job counts unavailable');\n return null;\n }\n}\n"],"names":["createAdministrationActionEvent","logger","hashOpaqueToken","reactivateWorkspaceWithAudit","suspendWorkspaceWithAudit","listAdminWorkspaces","listAdminWorkspaceRows","ADMIN_OPERATOR_ROLE","SUSPEND_COMMAND","REACTIVATE_COMMAND","commandFingerprint","command","params","JSON","stringify","workspaceId","reason","administrationEvent","actorId","actorRole","requiredRole","targetType","targetId","result","correlationId","idempotencyKeyFingerprint","occurredAt","Date","toISOString","runWorkspaceAdministrationMutation","update","idempotencyKey","requestFingerprint","event","suspendWorkspace","reactivateWorkspace","listWorkspaceAdministratorSummaries","workspaceIds","workspaces","map","id","projectCounts","jobCounts","Promise","all","getProjectCounts","projects","getJobCounts","runners","workspace","name","status","memberSummary","count","memberCount","projectSummary","get","state","createdAt","updatedAt","nextCursor","length","Map","getWorkspaceProjectCounts","counts","error","warn","operation","err","getWorkspaceJobCounts","queued","running"],"mappings":"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"}
@@ -4,6 +4,18 @@ export declare class WorkspaceNotFoundError extends Error {
4
4
  export declare class WorkspaceInactiveError extends Error {
5
5
  constructor(workspaceId: string);
6
6
  }
7
+ export declare class WorkspaceAlreadySuspendedError extends Error {
8
+ constructor(workspaceId: string);
9
+ }
10
+ export declare class WorkspaceNotSuspendedError extends Error {
11
+ constructor(workspaceId: string);
12
+ }
13
+ export declare class WorkspaceDeletedError extends Error {
14
+ constructor(workspaceId: string);
15
+ }
16
+ export declare class WorkspaceAdminIdempotencyKeyReuseError extends Error {
17
+ constructor();
18
+ }
7
19
  export declare class InvitationNotFoundError extends Error {
8
20
  constructor(id: string);
9
21
  }
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,WAAW,EAAE,MAAM;CAIhC;AAED,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,WAAW,EAAE,MAAM;CAIhC;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,EAAE,EAAE,MAAM;CAIvB;AAED,qBAAa,gCAAiC,SAAQ,KAAK;;CAK1D;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;CAIhD;AAED,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,SAAS,EAAE,MAAM;CAI9B;AAED,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,KAAK,EAAE,MAAM;CAI1B;AAED,qBAAa,uBAAwB,SAAQ,KAAK;;CAKjD;AAED,qBAAa,qBAAsB,SAAQ,KAAK;;CAK/C;AAED,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,MAAM,CAAC,EAAE,MAAM;CAI5B;AAED,qBAAa,iBAAkB,SAAQ,KAAK;;CAK3C;AAED,qBAAa,qBAAsB,SAAQ,KAAK;;CAK/C;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,WAAW,EAAE,MAAM;CAIhC;AAED,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,WAAW,EAAE,MAAM;CAIhC;AAED,qBAAa,0BAA2B,SAAQ,KAAK;;CAKpD;AAED,qBAAa,4BAA6B,SAAQ,KAAK;;CAKtD;AAED,qBAAa,yBAA0B,SAAQ,KAAK;gBACtC,KAAK,EAAE,MAAM;CAI1B"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,WAAW,EAAE,MAAM;CAIhC;AAED,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,WAAW,EAAE,MAAM;CAIhC;AAED,qBAAa,8BAA+B,SAAQ,KAAK;gBAC3C,WAAW,EAAE,MAAM;CAIhC;AAED,qBAAa,0BAA2B,SAAQ,KAAK;gBACvC,WAAW,EAAE,MAAM;CAIhC;AAED,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,WAAW,EAAE,MAAM;CAIhC;AAED,qBAAa,sCAAuC,SAAQ,KAAK;;CAKhE;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,EAAE,EAAE,MAAM;CAIvB;AAED,qBAAa,gCAAiC,SAAQ,KAAK;;CAK1D;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;CAIhD;AAED,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,SAAS,EAAE,MAAM;CAI9B;AAED,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,KAAK,EAAE,MAAM;CAI1B;AAED,qBAAa,uBAAwB,SAAQ,KAAK;;CAKjD;AAED,qBAAa,qBAAsB,SAAQ,KAAK;;CAK/C;AAED,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,MAAM,CAAC,EAAE,MAAM;CAI5B;AAED,qBAAa,iBAAkB,SAAQ,KAAK;;CAK3C;AAED,qBAAa,qBAAsB,SAAQ,KAAK;;CAK/C;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,WAAW,EAAE,MAAM;CAIhC;AAED,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,WAAW,EAAE,MAAM;CAIhC;AAED,qBAAa,0BAA2B,SAAQ,KAAK;;CAKpD;AAED,qBAAa,4BAA6B,SAAQ,KAAK;;CAKtD;AAED,qBAAa,yBAA0B,SAAQ,KAAK;gBACtC,KAAK,EAAE,MAAM;CAI1B"}
@@ -10,6 +10,30 @@ export class WorkspaceInactiveError extends Error {
10
10
  this.name = 'WorkspaceInactiveError';
11
11
  }
12
12
  }
13
+ export class WorkspaceAlreadySuspendedError extends Error {
14
+ constructor(workspaceId){
15
+ super(`Workspace is already suspended: ${workspaceId}`);
16
+ this.name = 'WorkspaceAlreadySuspendedError';
17
+ }
18
+ }
19
+ export class WorkspaceNotSuspendedError extends Error {
20
+ constructor(workspaceId){
21
+ super(`Workspace is not suspended: ${workspaceId}`);
22
+ this.name = 'WorkspaceNotSuspendedError';
23
+ }
24
+ }
25
+ export class WorkspaceDeletedError extends Error {
26
+ constructor(workspaceId){
27
+ super(`Workspace is deleted: ${workspaceId}`);
28
+ this.name = 'WorkspaceDeletedError';
29
+ }
30
+ }
31
+ export class WorkspaceAdminIdempotencyKeyReuseError extends Error {
32
+ constructor(){
33
+ super('Idempotency key was already used for a different workspace command');
34
+ this.name = 'WorkspaceAdminIdempotencyKeyReuseError';
35
+ }
36
+ }
13
37
  export class InvitationNotFoundError extends Error {
14
38
  constructor(id){
15
39
  super(`Invitation not found: ${id}`);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/errors.ts"],"sourcesContent":["export class WorkspaceNotFoundError extends Error {\n constructor(workspaceId: string) {\n super(`Workspace not found: ${workspaceId}`);\n this.name = 'WorkspaceNotFoundError';\n }\n}\n\nexport class WorkspaceInactiveError extends Error {\n constructor(workspaceId: string) {\n super(`Workspace is not active: ${workspaceId}`);\n this.name = 'WorkspaceInactiveError';\n }\n}\n\nexport class InvitationNotFoundError extends Error {\n constructor(id: string) {\n super(`Invitation not found: ${id}`);\n this.name = 'InvitationNotFoundError';\n }\n}\n\nexport class InvitationWorkspaceMismatchError extends Error {\n constructor() {\n super('Invitation does not belong to this workspace');\n this.name = 'InvitationWorkspaceMismatchError';\n }\n}\n\nexport class MembershipNotFoundError extends Error {\n constructor(userId: string, workspaceId: string) {\n super(`Membership not found for user ${userId} in workspace ${workspaceId}`);\n this.name = 'MembershipNotFoundError';\n }\n}\n\nexport class UserNotFoundError extends Error {\n constructor(idOrEmail: string) {\n super(`User not found: ${idOrEmail}`);\n this.name = 'UserNotFoundError';\n }\n}\n\nexport class EmailTakenError extends Error {\n constructor(email: string) {\n super(`Email already registered: ${email}`);\n this.name = 'EmailTakenError';\n }\n}\n\nexport class InvalidCredentialsError extends Error {\n constructor() {\n super('Invalid credentials');\n this.name = 'InvalidCredentialsError';\n }\n}\n\nexport class EmailNotVerifiedError extends Error {\n constructor() {\n super('Email not verified');\n this.name = 'EmailNotVerifiedError';\n }\n}\n\nexport class TokenInvalidError extends Error {\n constructor(reason?: string) {\n super(reason ? `Invalid token: ${reason}` : 'Invalid token');\n this.name = 'TokenInvalidError';\n }\n}\n\nexport class TokenExpiredError extends Error {\n constructor() {\n super('Token has expired');\n this.name = 'TokenExpiredError';\n }\n}\n\nexport class TokenAlreadyUsedError extends Error {\n constructor() {\n super('Token has already been used');\n this.name = 'TokenAlreadyUsedError';\n }\n}\n\nexport class MembershipRequiredError extends Error {\n constructor(workspaceId: string) {\n super(`Membership required for workspace: ${workspaceId}`);\n this.name = 'MembershipRequiredError';\n }\n}\n\nexport class LastMemberError extends Error {\n constructor(workspaceId: string) {\n super(`Cannot remove the last member of workspace: ${workspaceId}`);\n this.name = 'LastMemberError';\n }\n}\n\nexport class SelfRemovalNotAllowedError extends Error {\n constructor() {\n super('Cannot remove yourself from a workspace');\n this.name = 'SelfRemovalNotAllowedError';\n }\n}\n\nexport class InvitationEmailMismatchError extends Error {\n constructor() {\n super('Invitation email does not match authenticated user');\n this.name = 'InvitationEmailMismatchError';\n }\n}\n\nexport class OpenInvitationExistsError extends Error {\n constructor(email: string) {\n super(`An open invitation already exists for: ${email}`);\n this.name = 'OpenInvitationExistsError';\n }\n}\n"],"names":["WorkspaceNotFoundError","Error","workspaceId","name","WorkspaceInactiveError","InvitationNotFoundError","id","InvitationWorkspaceMismatchError","MembershipNotFoundError","userId","UserNotFoundError","idOrEmail","EmailTakenError","email","InvalidCredentialsError","EmailNotVerifiedError","TokenInvalidError","reason","TokenExpiredError","TokenAlreadyUsedError","MembershipRequiredError","LastMemberError","SelfRemovalNotAllowedError","InvitationEmailMismatchError","OpenInvitationExistsError"],"mappings":"AAAA,OAAO,MAAMA,+BAA+BC;IAC1C,YAAYC,WAAmB,CAAE;QAC/B,KAAK,CAAC,CAAC,qBAAqB,EAAEA,aAAa;QAC3C,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMC,+BAA+BH;IAC1C,YAAYC,WAAmB,CAAE;QAC/B,KAAK,CAAC,CAAC,yBAAyB,EAAEA,aAAa;QAC/C,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAME,gCAAgCJ;IAC3C,YAAYK,EAAU,CAAE;QACtB,KAAK,CAAC,CAAC,sBAAsB,EAAEA,IAAI;QACnC,IAAI,CAACH,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMI,yCAAyCN;IACpD,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACE,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMK,gCAAgCP;IAC3C,YAAYQ,MAAc,EAAEP,WAAmB,CAAE;QAC/C,KAAK,CAAC,CAAC,8BAA8B,EAAEO,OAAO,cAAc,EAAEP,aAAa;QAC3E,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMO,0BAA0BT;IACrC,YAAYU,SAAiB,CAAE;QAC7B,KAAK,CAAC,CAAC,gBAAgB,EAAEA,WAAW;QACpC,IAAI,CAACR,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMS,wBAAwBX;IACnC,YAAYY,KAAa,CAAE;QACzB,KAAK,CAAC,CAAC,0BAA0B,EAAEA,OAAO;QAC1C,IAAI,CAACV,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMW,gCAAgCb;IAC3C,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACE,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMY,8BAA8Bd;IACzC,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACE,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMa,0BAA0Bf;IACrC,YAAYgB,MAAe,CAAE;QAC3B,KAAK,CAACA,SAAS,CAAC,eAAe,EAAEA,QAAQ,GAAG;QAC5C,IAAI,CAACd,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMe,0BAA0BjB;IACrC,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACE,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMgB,8BAA8BlB;IACzC,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACE,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMiB,gCAAgCnB;IAC3C,YAAYC,WAAmB,CAAE;QAC/B,KAAK,CAAC,CAAC,mCAAmC,EAAEA,aAAa;QACzD,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMkB,wBAAwBpB;IACnC,YAAYC,WAAmB,CAAE;QAC/B,KAAK,CAAC,CAAC,4CAA4C,EAAEA,aAAa;QAClE,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMmB,mCAAmCrB;IAC9C,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACE,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMoB,qCAAqCtB;IAChD,aAAc;QACZ,KAAK,CAAC;QACN,IAAI,CAACE,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMqB,kCAAkCvB;IAC7C,YAAYY,KAAa,CAAE;QACzB,KAAK,CAAC,CAAC,uCAAuC,EAAEA,OAAO;QACvD,IAAI,CAACV,IAAI,GAAG;IACd;AACF"}
1
+ {"version":3,"sources":["../../src/core/errors.ts"],"sourcesContent":["export class WorkspaceNotFoundError extends Error {\n constructor(workspaceId: string) {\n super(`Workspace not found: ${workspaceId}`);\n this.name = 'WorkspaceNotFoundError';\n }\n}\n\nexport class WorkspaceInactiveError extends Error {\n constructor(workspaceId: string) {\n super(`Workspace is not active: ${workspaceId}`);\n this.name = 'WorkspaceInactiveError';\n }\n}\n\nexport class WorkspaceAlreadySuspendedError extends Error {\n constructor(workspaceId: string) {\n super(`Workspace is already suspended: ${workspaceId}`);\n this.name = 'WorkspaceAlreadySuspendedError';\n }\n}\n\nexport class WorkspaceNotSuspendedError extends Error {\n constructor(workspaceId: string) {\n super(`Workspace is not suspended: ${workspaceId}`);\n this.name = 'WorkspaceNotSuspendedError';\n }\n}\n\nexport class WorkspaceDeletedError extends Error {\n constructor(workspaceId: string) {\n super(`Workspace is deleted: ${workspaceId}`);\n this.name = 'WorkspaceDeletedError';\n }\n}\n\nexport class WorkspaceAdminIdempotencyKeyReuseError extends Error {\n constructor() {\n super('Idempotency key was already used for a different workspace command');\n this.name = 'WorkspaceAdminIdempotencyKeyReuseError';\n }\n}\n\nexport class InvitationNotFoundError extends Error {\n constructor(id: string) {\n super(`Invitation not found: ${id}`);\n this.name = 'InvitationNotFoundError';\n }\n}\n\nexport class InvitationWorkspaceMismatchError extends Error {\n constructor() {\n super('Invitation does not belong to this workspace');\n this.name = 'InvitationWorkspaceMismatchError';\n }\n}\n\nexport class MembershipNotFoundError extends Error {\n constructor(userId: string, workspaceId: string) {\n super(`Membership not found for user ${userId} in workspace ${workspaceId}`);\n this.name = 'MembershipNotFoundError';\n }\n}\n\nexport class UserNotFoundError extends Error {\n constructor(idOrEmail: string) {\n super(`User not found: ${idOrEmail}`);\n this.name = 'UserNotFoundError';\n }\n}\n\nexport class EmailTakenError extends Error {\n constructor(email: string) {\n super(`Email already registered: ${email}`);\n this.name = 'EmailTakenError';\n }\n}\n\nexport class InvalidCredentialsError extends Error {\n constructor() {\n super('Invalid credentials');\n this.name = 'InvalidCredentialsError';\n }\n}\n\nexport class EmailNotVerifiedError extends Error {\n constructor() {\n super('Email not verified');\n this.name = 'EmailNotVerifiedError';\n }\n}\n\nexport class TokenInvalidError extends Error {\n constructor(reason?: string) {\n super(reason ? `Invalid token: ${reason}` : 'Invalid token');\n this.name = 'TokenInvalidError';\n }\n}\n\nexport class TokenExpiredError extends Error {\n constructor() {\n super('Token has expired');\n this.name = 'TokenExpiredError';\n }\n}\n\nexport class TokenAlreadyUsedError extends Error {\n constructor() {\n super('Token has already been used');\n this.name = 'TokenAlreadyUsedError';\n }\n}\n\nexport class MembershipRequiredError extends Error {\n constructor(workspaceId: string) {\n super(`Membership required for workspace: ${workspaceId}`);\n this.name = 'MembershipRequiredError';\n }\n}\n\nexport class LastMemberError extends Error {\n constructor(workspaceId: string) {\n super(`Cannot remove the last member of workspace: ${workspaceId}`);\n this.name = 'LastMemberError';\n }\n}\n\nexport class SelfRemovalNotAllowedError extends Error {\n constructor() {\n super('Cannot remove yourself from a workspace');\n this.name = 'SelfRemovalNotAllowedError';\n }\n}\n\nexport class InvitationEmailMismatchError extends Error {\n constructor() {\n super('Invitation email does not match authenticated user');\n this.name = 'InvitationEmailMismatchError';\n }\n}\n\nexport class OpenInvitationExistsError extends Error {\n constructor(email: string) {\n super(`An open invitation already exists for: ${email}`);\n this.name = 'OpenInvitationExistsError';\n }\n}\n"],"names":["WorkspaceNotFoundError","Error","workspaceId","name","WorkspaceInactiveError","WorkspaceAlreadySuspendedError","WorkspaceNotSuspendedError","WorkspaceDeletedError","WorkspaceAdminIdempotencyKeyReuseError","InvitationNotFoundError","id","InvitationWorkspaceMismatchError","MembershipNotFoundError","userId","UserNotFoundError","idOrEmail","EmailTakenError","email","InvalidCredentialsError","EmailNotVerifiedError","TokenInvalidError","reason","TokenExpiredError","TokenAlreadyUsedError","MembershipRequiredError","LastMemberError","SelfRemovalNotAllowedError","InvitationEmailMismatchError","OpenInvitationExistsError"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
- export { type ListWorkspaceAdministratorSummariesParams, type ListWorkspaceAdministratorSummariesResult, listWorkspaceAdministratorSummaries, type WorkspaceAdministratorSummary, } from './admin-workspaces.js';
1
+ export { type ListWorkspaceAdministratorSummariesParams, type ListWorkspaceAdministratorSummariesResult, listWorkspaceAdministratorSummaries, reactivateWorkspace, suspendWorkspace, type WorkspaceAdministrationMutationContext, type WorkspaceAdministrationMutationResult, type WorkspaceAdministratorSummary, } from './admin-workspaces.js';
2
2
  export type { Invitation } from './entities/invitation.js';
3
3
  export type { Membership } from './entities/membership.js';
4
4
  export type { Workspace, WorkspaceStatus } from './entities/workspace.js';
5
- export { InvitationEmailMismatchError, InvitationNotFoundError, InvitationWorkspaceMismatchError, LastMemberError, MembershipNotFoundError, MembershipRequiredError, OpenInvitationExistsError, SelfRemovalNotAllowedError, TokenAlreadyUsedError, TokenExpiredError, WorkspaceNotFoundError, } from './errors.js';
5
+ export { InvitationEmailMismatchError, InvitationNotFoundError, InvitationWorkspaceMismatchError, LastMemberError, MembershipNotFoundError, MembershipRequiredError, OpenInvitationExistsError, SelfRemovalNotAllowedError, TokenAlreadyUsedError, TokenExpiredError, WorkspaceAdminIdempotencyKeyReuseError, WorkspaceAlreadySuspendedError, WorkspaceDeletedError, WorkspaceNotFoundError, WorkspaceNotSuspendedError, } from './errors.js';
6
6
  export { acceptWorkspaceInvitation, createWorkspaceInvitation, listWorkspaceInvitations, type PreviewInvitationResult, previewInvitation, revokeWorkspaceInvitation, } from './invitations.js';
7
7
  export { type EnsureMembershipParams, ensureMembership } from './memberships.js';
8
8
  export { createWorkspaceForUser, getWorkspace, listUserWorkspaceMemberships, listWorkspaceMembers, type RequireWorkspaceMembershipParams, type RequireWorkspaceMembershipResult, removeWorkspaceMember, requireWorkspaceMembership, } from './workspaces.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,yCAAyC,EAC9C,KAAK,yCAAyC,EAC9C,mCAAmC,EACnC,KAAK,6BAA6B,GACnC,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAC,UAAU,EAAC,MAAM,0BAA0B,CAAC;AACzD,YAAY,EAAC,UAAU,EAAC,MAAM,0BAA0B,CAAC;AACzD,YAAY,EAAC,SAAS,EAAE,eAAe,EAAC,MAAM,yBAAyB,CAAC;AACxE,OAAO,EACL,4BAA4B,EAC5B,uBAAuB,EACvB,gCAAgC,EAChC,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,yBAAyB,EACzB,0BAA0B,EAC1B,qBAAqB,EACrB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,KAAK,uBAAuB,EAC5B,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAC,KAAK,sBAAsB,EAAE,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAC/E,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,4BAA4B,EAC5B,oBAAoB,EACpB,KAAK,gCAAgC,EACrC,KAAK,gCAAgC,EACrC,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,yCAAyC,EAC9C,KAAK,yCAAyC,EAC9C,mCAAmC,EACnC,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,sCAAsC,EAC3C,KAAK,qCAAqC,EAC1C,KAAK,6BAA6B,GACnC,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EAAC,UAAU,EAAC,MAAM,0BAA0B,CAAC;AACzD,YAAY,EAAC,UAAU,EAAC,MAAM,0BAA0B,CAAC;AACzD,YAAY,EAAC,SAAS,EAAE,eAAe,EAAC,MAAM,yBAAyB,CAAC;AACxE,OAAO,EACL,4BAA4B,EAC5B,uBAAuB,EACvB,gCAAgC,EAChC,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,yBAAyB,EACzB,0BAA0B,EAC1B,qBAAqB,EACrB,iBAAiB,EACjB,sCAAsC,EACtC,8BAA8B,EAC9B,qBAAqB,EACrB,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,EACxB,KAAK,uBAAuB,EAC5B,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAC,KAAK,sBAAsB,EAAE,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAC/E,OAAO,EACL,sBAAsB,EACtB,YAAY,EACZ,4BAA4B,EAC5B,oBAAoB,EACpB,KAAK,gCAAgC,EACrC,KAAK,gCAAgC,EACrC,qBAAqB,EACrB,0BAA0B,GAC3B,MAAM,iBAAiB,CAAC"}
@@ -1,5 +1,5 @@
1
- export { listWorkspaceAdministratorSummaries } from './admin-workspaces.js';
2
- export { InvitationEmailMismatchError, InvitationNotFoundError, InvitationWorkspaceMismatchError, LastMemberError, MembershipNotFoundError, MembershipRequiredError, OpenInvitationExistsError, SelfRemovalNotAllowedError, TokenAlreadyUsedError, TokenExpiredError, WorkspaceNotFoundError } from './errors.js';
1
+ export { listWorkspaceAdministratorSummaries, reactivateWorkspace, suspendWorkspace } from './admin-workspaces.js';
2
+ export { InvitationEmailMismatchError, InvitationNotFoundError, InvitationWorkspaceMismatchError, LastMemberError, MembershipNotFoundError, MembershipRequiredError, OpenInvitationExistsError, SelfRemovalNotAllowedError, TokenAlreadyUsedError, TokenExpiredError, WorkspaceAdminIdempotencyKeyReuseError, WorkspaceAlreadySuspendedError, WorkspaceDeletedError, WorkspaceNotFoundError, WorkspaceNotSuspendedError } from './errors.js';
3
3
  export { acceptWorkspaceInvitation, createWorkspaceInvitation, listWorkspaceInvitations, previewInvitation, revokeWorkspaceInvitation } from './invitations.js';
4
4
  export { ensureMembership } from './memberships.js';
5
5
  export { createWorkspaceForUser, getWorkspace, listUserWorkspaceMemberships, listWorkspaceMembers, removeWorkspaceMember, requireWorkspaceMembership } from './workspaces.js';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/core/index.ts"],"sourcesContent":["export {\n type ListWorkspaceAdministratorSummariesParams,\n type ListWorkspaceAdministratorSummariesResult,\n listWorkspaceAdministratorSummaries,\n type WorkspaceAdministratorSummary,\n} from './admin-workspaces.js';\nexport type {Invitation} from './entities/invitation.js';\nexport type {Membership} from './entities/membership.js';\nexport type {Workspace, WorkspaceStatus} from './entities/workspace.js';\nexport {\n InvitationEmailMismatchError,\n InvitationNotFoundError,\n InvitationWorkspaceMismatchError,\n LastMemberError,\n MembershipNotFoundError,\n MembershipRequiredError,\n OpenInvitationExistsError,\n SelfRemovalNotAllowedError,\n TokenAlreadyUsedError,\n TokenExpiredError,\n WorkspaceNotFoundError,\n} from './errors.js';\nexport {\n acceptWorkspaceInvitation,\n createWorkspaceInvitation,\n listWorkspaceInvitations,\n type PreviewInvitationResult,\n previewInvitation,\n revokeWorkspaceInvitation,\n} from './invitations.js';\nexport {type EnsureMembershipParams, ensureMembership} from './memberships.js';\nexport {\n createWorkspaceForUser,\n getWorkspace,\n listUserWorkspaceMemberships,\n listWorkspaceMembers,\n type RequireWorkspaceMembershipParams,\n type RequireWorkspaceMembershipResult,\n removeWorkspaceMember,\n requireWorkspaceMembership,\n} from './workspaces.js';\n"],"names":["listWorkspaceAdministratorSummaries","InvitationEmailMismatchError","InvitationNotFoundError","InvitationWorkspaceMismatchError","LastMemberError","MembershipNotFoundError","MembershipRequiredError","OpenInvitationExistsError","SelfRemovalNotAllowedError","TokenAlreadyUsedError","TokenExpiredError","WorkspaceNotFoundError","acceptWorkspaceInvitation","createWorkspaceInvitation","listWorkspaceInvitations","previewInvitation","revokeWorkspaceInvitation","ensureMembership","createWorkspaceForUser","getWorkspace","listUserWorkspaceMemberships","listWorkspaceMembers","removeWorkspaceMember","requireWorkspaceMembership"],"mappings":"AAAA,SAGEA,mCAAmC,QAE9B,wBAAwB;AAI/B,SACEC,4BAA4B,EAC5BC,uBAAuB,EACvBC,gCAAgC,EAChCC,eAAe,EACfC,uBAAuB,EACvBC,uBAAuB,EACvBC,yBAAyB,EACzBC,0BAA0B,EAC1BC,qBAAqB,EACrBC,iBAAiB,EACjBC,sBAAsB,QACjB,cAAc;AACrB,SACEC,yBAAyB,EACzBC,yBAAyB,EACzBC,wBAAwB,EAExBC,iBAAiB,EACjBC,yBAAyB,QACpB,mBAAmB;AAC1B,SAAqCC,gBAAgB,QAAO,mBAAmB;AAC/E,SACEC,sBAAsB,EACtBC,YAAY,EACZC,4BAA4B,EAC5BC,oBAAoB,EAGpBC,qBAAqB,EACrBC,0BAA0B,QACrB,kBAAkB"}
1
+ {"version":3,"sources":["../../src/core/index.ts"],"sourcesContent":["export {\n type ListWorkspaceAdministratorSummariesParams,\n type ListWorkspaceAdministratorSummariesResult,\n listWorkspaceAdministratorSummaries,\n reactivateWorkspace,\n suspendWorkspace,\n type WorkspaceAdministrationMutationContext,\n type WorkspaceAdministrationMutationResult,\n type WorkspaceAdministratorSummary,\n} from './admin-workspaces.js';\nexport type {Invitation} from './entities/invitation.js';\nexport type {Membership} from './entities/membership.js';\nexport type {Workspace, WorkspaceStatus} from './entities/workspace.js';\nexport {\n InvitationEmailMismatchError,\n InvitationNotFoundError,\n InvitationWorkspaceMismatchError,\n LastMemberError,\n MembershipNotFoundError,\n MembershipRequiredError,\n OpenInvitationExistsError,\n SelfRemovalNotAllowedError,\n TokenAlreadyUsedError,\n TokenExpiredError,\n WorkspaceAdminIdempotencyKeyReuseError,\n WorkspaceAlreadySuspendedError,\n WorkspaceDeletedError,\n WorkspaceNotFoundError,\n WorkspaceNotSuspendedError,\n} from './errors.js';\nexport {\n acceptWorkspaceInvitation,\n createWorkspaceInvitation,\n listWorkspaceInvitations,\n type PreviewInvitationResult,\n previewInvitation,\n revokeWorkspaceInvitation,\n} from './invitations.js';\nexport {type EnsureMembershipParams, ensureMembership} from './memberships.js';\nexport {\n createWorkspaceForUser,\n getWorkspace,\n listUserWorkspaceMemberships,\n listWorkspaceMembers,\n type RequireWorkspaceMembershipParams,\n type RequireWorkspaceMembershipResult,\n removeWorkspaceMember,\n requireWorkspaceMembership,\n} from './workspaces.js';\n"],"names":["listWorkspaceAdministratorSummaries","reactivateWorkspace","suspendWorkspace","InvitationEmailMismatchError","InvitationNotFoundError","InvitationWorkspaceMismatchError","LastMemberError","MembershipNotFoundError","MembershipRequiredError","OpenInvitationExistsError","SelfRemovalNotAllowedError","TokenAlreadyUsedError","TokenExpiredError","WorkspaceAdminIdempotencyKeyReuseError","WorkspaceAlreadySuspendedError","WorkspaceDeletedError","WorkspaceNotFoundError","WorkspaceNotSuspendedError","acceptWorkspaceInvitation","createWorkspaceInvitation","listWorkspaceInvitations","previewInvitation","revokeWorkspaceInvitation","ensureMembership","createWorkspaceForUser","getWorkspace","listUserWorkspaceMemberships","listWorkspaceMembers","removeWorkspaceMember","requireWorkspaceMembership"],"mappings":"AAAA,SAGEA,mCAAmC,EACnCC,mBAAmB,EACnBC,gBAAgB,QAIX,wBAAwB;AAI/B,SACEC,4BAA4B,EAC5BC,uBAAuB,EACvBC,gCAAgC,EAChCC,eAAe,EACfC,uBAAuB,EACvBC,uBAAuB,EACvBC,yBAAyB,EACzBC,0BAA0B,EAC1BC,qBAAqB,EACrBC,iBAAiB,EACjBC,sCAAsC,EACtCC,8BAA8B,EAC9BC,qBAAqB,EACrBC,sBAAsB,EACtBC,0BAA0B,QACrB,cAAc;AACrB,SACEC,yBAAyB,EACzBC,yBAAyB,EACzBC,wBAAwB,EAExBC,iBAAiB,EACjBC,yBAAyB,QACpB,mBAAmB;AAC1B,SAAqCC,gBAAgB,QAAO,mBAAmB;AAC/E,SACEC,sBAAsB,EACtBC,YAAY,EACZC,4BAA4B,EAC5BC,oBAAoB,EAGpBC,qBAAqB,EACrBC,0BAA0B,QACrB,kBAAkB"}
@@ -0,0 +1,17 @@
1
+ import type { AdministrationActionEvent } from '@shipfox/api-common-dto';
2
+ export interface WorkspaceAdministrationResult {
3
+ workspaceId: string;
4
+ status: 'active' | 'suspended';
5
+ correlationId: string;
6
+ }
7
+ interface AuditedWorkspaceCommandParams {
8
+ actorId: string;
9
+ idempotencyKeyFingerprint: string;
10
+ requestFingerprint: string;
11
+ event: AdministrationActionEvent;
12
+ workspaceId: string;
13
+ }
14
+ export declare function suspendWorkspaceWithAudit(params: AuditedWorkspaceCommandParams): Promise<WorkspaceAdministrationResult>;
15
+ export declare function reactivateWorkspaceWithAudit(params: AuditedWorkspaceCommandParams): Promise<WorkspaceAdministrationResult>;
16
+ export {};
17
+ //# sourceMappingURL=admin-workspace-commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-workspace-commands.d.ts","sourceRoot":"","sources":["../../src/db/admin-workspace-commands.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EAE1B,MAAM,yBAAyB,CAAC;AAoBjC,MAAM,WAAW,6BAA6B;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC/B,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,UAAU,6BAA6B;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,yBAAyB,EAAE,MAAM,CAAC;IAClC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,KAAK,EAAE,yBAAyB,CAAC;IACjC,WAAW,EAAE,MAAM,CAAC;CACrB;AAmHD,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,6BAA6B,GACpC,OAAO,CAAC,6BAA6B,CAAC,CAMxC;AAED,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,6BAA6B,GACpC,OAAO,CAAC,6BAA6B,CAAC,CAMxC"}
@@ -0,0 +1,86 @@
1
+ import { writeOutboxEvent } from '@shipfox/node-outbox';
2
+ import { and, eq, sql } from 'drizzle-orm';
3
+ import { WorkspaceAdminIdempotencyKeyReuseError, WorkspaceAlreadySuspendedError, WorkspaceDeletedError, WorkspaceNotFoundError, WorkspaceNotSuspendedError } from '#core/errors.js';
4
+ import { db } from './db.js';
5
+ import { workspacesAdminCommandResults } from './schema/admin-command-results.js';
6
+ import { workspacesOutbox } from './schema/outbox.js';
7
+ import { workspaces } from './schema/workspaces.js';
8
+ async function loadCommandResult(tx, params) {
9
+ const rows = await tx.select().from(workspacesAdminCommandResults).where(and(eq(workspacesAdminCommandResults.actorId, params.actorId), eq(workspacesAdminCommandResults.idempotencyKeyFingerprint, params.idempotencyKeyFingerprint))).limit(1);
10
+ const result = rows[0];
11
+ if (!result) return undefined;
12
+ if (result.command !== params.command || result.requestFingerprint !== params.requestFingerprint) {
13
+ throw new WorkspaceAdminIdempotencyKeyReuseError();
14
+ }
15
+ return result.result;
16
+ }
17
+ async function storeCommandResult(tx, params, result) {
18
+ await tx.insert(workspacesAdminCommandResults).values({
19
+ actorId: params.actorId,
20
+ idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,
21
+ command: params.event.command,
22
+ requestFingerprint: params.requestFingerprint,
23
+ result
24
+ });
25
+ }
26
+ async function writeAdministrationAction(tx, event) {
27
+ await writeOutboxEvent(tx, workspacesOutbox, {
28
+ type: 'administration.action.performed',
29
+ payload: event
30
+ });
31
+ }
32
+ async function lockAdminCommand(tx, params) {
33
+ await tx.execute(sql`select pg_advisory_xact_lock(hashtext(${`workspaces_admin_command:${params.actorId}:${params.idempotencyKeyFingerprint}`}))`);
34
+ }
35
+ async function updateWorkspaceStatusWithAudit(params) {
36
+ return await db().transaction(async (tx)=>{
37
+ await lockAdminCommand(tx, params);
38
+ const existing = await loadCommandResult(tx, {
39
+ actorId: params.actorId,
40
+ idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,
41
+ command: params.event.command,
42
+ requestFingerprint: params.requestFingerprint
43
+ });
44
+ if (existing) return existing;
45
+ const rows = await tx.select().from(workspaces).where(eq(workspaces.id, params.workspaceId)).for('update').limit(1);
46
+ const workspaceRow = rows[0];
47
+ if (!workspaceRow) throw new WorkspaceNotFoundError(params.workspaceId);
48
+ if (workspaceRow.status === 'deleted') throw new WorkspaceDeletedError(params.workspaceId);
49
+ if (workspaceRow.status !== params.expectedStatus) {
50
+ if (params.nextStatus === 'suspended') {
51
+ throw new WorkspaceAlreadySuspendedError(params.workspaceId);
52
+ }
53
+ throw new WorkspaceNotSuspendedError(params.workspaceId);
54
+ }
55
+ const updatedRows = await tx.update(workspaces).set({
56
+ status: params.nextStatus,
57
+ updatedAt: new Date()
58
+ }).where(eq(workspaces.id, params.workspaceId)).returning();
59
+ const updatedRow = updatedRows[0];
60
+ if (!updatedRow) throw new WorkspaceNotFoundError(params.workspaceId);
61
+ const result = {
62
+ workspaceId: updatedRow.id,
63
+ status: updatedRow.status,
64
+ correlationId: params.event.correlationId
65
+ };
66
+ await writeAdministrationAction(tx, params.event);
67
+ await storeCommandResult(tx, params, result);
68
+ return result;
69
+ });
70
+ }
71
+ export async function suspendWorkspaceWithAudit(params) {
72
+ return await updateWorkspaceStatusWithAudit({
73
+ ...params,
74
+ expectedStatus: 'active',
75
+ nextStatus: 'suspended'
76
+ });
77
+ }
78
+ export async function reactivateWorkspaceWithAudit(params) {
79
+ return await updateWorkspaceStatusWithAudit({
80
+ ...params,
81
+ expectedStatus: 'suspended',
82
+ nextStatus: 'active'
83
+ });
84
+ }
85
+
86
+ //# sourceMappingURL=admin-workspace-commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/db/admin-workspace-commands.ts"],"sourcesContent":["import type {\n AdministrationActionEvent,\n AdministrationActionEventMap,\n} from '@shipfox/api-common-dto';\nimport {writeOutboxEvent} from '@shipfox/node-outbox';\nimport {and, eq, sql} from 'drizzle-orm';\nimport {\n WorkspaceAdminIdempotencyKeyReuseError,\n WorkspaceAlreadySuspendedError,\n WorkspaceDeletedError,\n WorkspaceNotFoundError,\n WorkspaceNotSuspendedError,\n} from '#core/errors.js';\nimport {db} from './db.js';\nimport {\n type WorkspaceAdminCommandResultDb,\n workspacesAdminCommandResults,\n} from './schema/admin-command-results.js';\nimport {workspacesOutbox} from './schema/outbox.js';\nimport {workspaces} from './schema/workspaces.js';\n\ntype Tx = Parameters<Parameters<ReturnType<typeof db>['transaction']>[0]>[0];\n\nexport interface WorkspaceAdministrationResult {\n workspaceId: string;\n status: 'active' | 'suspended';\n correlationId: string;\n}\n\ninterface AuditedWorkspaceCommandParams {\n actorId: string;\n idempotencyKeyFingerprint: string;\n requestFingerprint: string;\n event: AdministrationActionEvent;\n workspaceId: string;\n}\n\nasync function loadCommandResult(\n tx: Tx,\n params: Pick<AuditedWorkspaceCommandParams, 'actorId' | 'idempotencyKeyFingerprint'> & {\n command: string;\n requestFingerprint: string;\n },\n): Promise<WorkspaceAdministrationResult | undefined> {\n const rows = await tx\n .select()\n .from(workspacesAdminCommandResults)\n .where(\n and(\n eq(workspacesAdminCommandResults.actorId, params.actorId),\n eq(\n workspacesAdminCommandResults.idempotencyKeyFingerprint,\n params.idempotencyKeyFingerprint,\n ),\n ),\n )\n .limit(1);\n const result: WorkspaceAdminCommandResultDb | undefined = rows[0];\n if (!result) return undefined;\n if (\n result.command !== params.command ||\n result.requestFingerprint !== params.requestFingerprint\n ) {\n throw new WorkspaceAdminIdempotencyKeyReuseError();\n }\n return result.result;\n}\n\nasync function storeCommandResult(\n tx: Tx,\n params: AuditedWorkspaceCommandParams,\n result: WorkspaceAdministrationResult,\n): Promise<void> {\n await tx.insert(workspacesAdminCommandResults).values({\n actorId: params.actorId,\n idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,\n command: params.event.command,\n requestFingerprint: params.requestFingerprint,\n result,\n });\n}\n\nasync function writeAdministrationAction(tx: Tx, event: AdministrationActionEvent): Promise<void> {\n await writeOutboxEvent<AdministrationActionEventMap>(tx, workspacesOutbox, {\n type: 'administration.action.performed',\n payload: event,\n });\n}\n\nasync function lockAdminCommand(\n tx: Tx,\n params: {actorId: string; idempotencyKeyFingerprint: string},\n): Promise<void> {\n await tx.execute(\n sql`select pg_advisory_xact_lock(hashtext(${`workspaces_admin_command:${params.actorId}:${params.idempotencyKeyFingerprint}`}))`,\n );\n}\n\nasync function updateWorkspaceStatusWithAudit(\n params: AuditedWorkspaceCommandParams & {\n expectedStatus: 'active' | 'suspended';\n nextStatus: 'active' | 'suspended';\n },\n): Promise<WorkspaceAdministrationResult> {\n return await db().transaction(async (tx) => {\n await lockAdminCommand(tx, params);\n\n const existing = await loadCommandResult(tx, {\n actorId: params.actorId,\n idempotencyKeyFingerprint: params.idempotencyKeyFingerprint,\n command: params.event.command,\n requestFingerprint: params.requestFingerprint,\n });\n if (existing) return existing;\n\n const rows = await tx\n .select()\n .from(workspaces)\n .where(eq(workspaces.id, params.workspaceId))\n .for('update')\n .limit(1);\n const workspaceRow = rows[0];\n if (!workspaceRow) throw new WorkspaceNotFoundError(params.workspaceId);\n if (workspaceRow.status === 'deleted') throw new WorkspaceDeletedError(params.workspaceId);\n if (workspaceRow.status !== params.expectedStatus) {\n if (params.nextStatus === 'suspended') {\n throw new WorkspaceAlreadySuspendedError(params.workspaceId);\n }\n throw new WorkspaceNotSuspendedError(params.workspaceId);\n }\n\n const updatedRows = await tx\n .update(workspaces)\n .set({status: params.nextStatus, updatedAt: new Date()})\n .where(eq(workspaces.id, params.workspaceId))\n .returning();\n const updatedRow = updatedRows[0];\n if (!updatedRow) throw new WorkspaceNotFoundError(params.workspaceId);\n\n const result = {\n workspaceId: updatedRow.id,\n status: updatedRow.status as 'active' | 'suspended',\n correlationId: params.event.correlationId,\n } satisfies WorkspaceAdministrationResult;\n await writeAdministrationAction(tx, params.event);\n await storeCommandResult(tx, params, result);\n return result;\n });\n}\n\nexport async function suspendWorkspaceWithAudit(\n params: AuditedWorkspaceCommandParams,\n): Promise<WorkspaceAdministrationResult> {\n return await updateWorkspaceStatusWithAudit({\n ...params,\n expectedStatus: 'active',\n nextStatus: 'suspended',\n });\n}\n\nexport async function reactivateWorkspaceWithAudit(\n params: AuditedWorkspaceCommandParams,\n): Promise<WorkspaceAdministrationResult> {\n return await updateWorkspaceStatusWithAudit({\n ...params,\n expectedStatus: 'suspended',\n nextStatus: 'active',\n });\n}\n"],"names":["writeOutboxEvent","and","eq","sql","WorkspaceAdminIdempotencyKeyReuseError","WorkspaceAlreadySuspendedError","WorkspaceDeletedError","WorkspaceNotFoundError","WorkspaceNotSuspendedError","db","workspacesAdminCommandResults","workspacesOutbox","workspaces","loadCommandResult","tx","params","rows","select","from","where","actorId","idempotencyKeyFingerprint","limit","result","undefined","command","requestFingerprint","storeCommandResult","insert","values","event","writeAdministrationAction","type","payload","lockAdminCommand","execute","updateWorkspaceStatusWithAudit","transaction","existing","id","workspaceId","for","workspaceRow","status","expectedStatus","nextStatus","updatedRows","update","set","updatedAt","Date","returning","updatedRow","correlationId","suspendWorkspaceWithAudit","reactivateWorkspaceWithAudit"],"mappings":"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"}