@shipfox/api-workspaces 11.0.0 → 12.0.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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +34 -0
- package/README.md +27 -4
- package/dist/core/entities/workspace.d.ts +1 -0
- package/dist/core/entities/workspace.d.ts.map +1 -1
- package/dist/core/entities/workspace.js.map +1 -1
- package/dist/core/errors.d.ts +3 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +6 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/invitations.js +1 -1
- package/dist/core/invitations.js.map +1 -1
- package/dist/core/rate-limit.d.ts +39 -0
- package/dist/core/rate-limit.d.ts.map +1 -0
- package/dist/core/rate-limit.js +70 -0
- package/dist/core/rate-limit.js.map +1 -0
- package/dist/core/workspaces.d.ts +7 -0
- package/dist/core/workspaces.d.ts.map +1 -1
- package/dist/core/workspaces.js +42 -4
- package/dist/core/workspaces.js.map +1 -1
- package/dist/db/db.d.ts +320 -0
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/db.js +3 -1
- package/dist/db/db.js.map +1 -1
- package/dist/db/index.d.ts +4 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +3 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/memberships.d.ts +1 -0
- package/dist/db/memberships.d.ts.map +1 -1
- package/dist/db/memberships.js +2 -0
- package/dist/db/memberships.js.map +1 -1
- package/dist/db/rate-limits.d.ts +9 -0
- package/dist/db/rate-limits.d.ts.map +1 -0
- package/dist/db/rate-limits.js +43 -0
- package/dist/db/rate-limits.js.map +1 -0
- package/dist/db/schema/rate-limits.d.ts +145 -0
- package/dist/db/schema/rate-limits.d.ts.map +1 -0
- package/dist/db/schema/rate-limits.js +25 -0
- package/dist/db/schema/rate-limits.js.map +1 -0
- package/dist/db/schema/workspaces.d.ts +17 -0
- package/dist/db/schema/workspaces.d.ts.map +1 -1
- package/dist/db/schema/workspaces.js +5 -2
- package/dist/db/schema/workspaces.js.map +1 -1
- package/dist/db/workspaces.d.ts +11 -1
- package/dist/db/workspaces.d.ts.map +1 -1
- package/dist/db/workspaces.js +12 -3
- package/dist/db/workspaces.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/metrics/instance.d.ts +6 -0
- package/dist/metrics/instance.d.ts.map +1 -1
- package/dist/metrics/instance.js +12 -0
- package/dist/metrics/instance.js.map +1 -1
- package/dist/presentation/dto/membership.d.ts.map +1 -1
- package/dist/presentation/dto/membership.js +1 -0
- package/dist/presentation/dto/membership.js.map +1 -1
- package/dist/presentation/dto/workspace.d.ts.map +1 -1
- package/dist/presentation/dto/workspace.js +1 -0
- package/dist/presentation/dto/workspace.js.map +1 -1
- package/dist/presentation/e2eRoutes/create-workspace.d.ts.map +1 -1
- package/dist/presentation/e2eRoutes/create-workspace.js +11 -1
- package/dist/presentation/e2eRoutes/create-workspace.js.map +1 -1
- package/dist/presentation/routes/index.d.ts.map +1 -1
- package/dist/presentation/routes/index.js +3 -1
- package/dist/presentation/routes/index.js.map +1 -1
- package/dist/presentation/routes/rate-limit.d.ts +3 -0
- package/dist/presentation/routes/rate-limit.d.ts.map +1 -0
- package/dist/presentation/routes/rate-limit.js +42 -0
- package/dist/presentation/routes/rate-limit.js.map +1 -0
- package/dist/presentation/routes/workspaces/create.d.ts.map +1 -1
- package/dist/presentation/routes/workspaces/create.js +11 -1
- package/dist/presentation/routes/workspaces/create.js.map +1 -1
- package/dist/presentation/routes/workspaces/index.d.ts +2 -0
- package/dist/presentation/routes/workspaces/index.d.ts.map +1 -1
- package/dist/presentation/routes/workspaces/index.js +2 -0
- package/dist/presentation/routes/workspaces/index.js.map +1 -1
- package/dist/presentation/routes/workspaces/slug-availability.d.ts +2 -0
- package/dist/presentation/routes/workspaces/slug-availability.d.ts.map +1 -0
- package/dist/presentation/routes/workspaces/slug-availability.js +31 -0
- package/dist/presentation/routes/workspaces/slug-availability.js.map +1 -0
- package/dist/presentation/routes/workspaces/update.d.ts +2 -0
- package/dist/presentation/routes/workspaces/update.d.ts.map +1 -0
- package/dist/presentation/routes/workspaces/update.js +49 -0
- package/dist/presentation/routes/workspaces/update.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0000_initial.sql +2 -0
- package/drizzle/0003_dapper_talos.sql +13 -0
- package/drizzle/meta/0000_snapshot.json +23 -1
- package/drizzle/meta/0003_snapshot.json +689 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +14 -12
- package/src/core/entities/workspace.ts +1 -0
- package/src/core/errors.ts +7 -0
- package/src/core/index.ts +3 -0
- package/src/core/invitations.ts +1 -1
- package/src/core/rate-limit.test.ts +113 -0
- package/src/core/rate-limit.ts +119 -0
- package/src/core/workspaces.test.ts +9 -0
- package/src/core/workspaces.ts +47 -3
- package/src/db/db.ts +2 -0
- package/src/db/index.ts +10 -0
- package/src/db/memberships.ts +3 -0
- package/src/db/rate-limits.test.ts +74 -0
- package/src/db/rate-limits.ts +59 -0
- package/src/db/schema/rate-limits.ts +27 -0
- package/src/db/schema/workspaces.ts +7 -2
- package/src/db/workspaces.ts +21 -1
- package/src/index.test.ts +2 -0
- package/src/index.ts +1 -0
- package/src/metrics/instance.ts +26 -0
- package/src/presentation/dto/membership.ts +1 -0
- package/src/presentation/dto/workspace.ts +1 -0
- package/src/presentation/e2eRoutes/create-workspace.ts +9 -1
- package/src/presentation/inter-module.test.ts +22 -4
- package/src/presentation/routes/index.ts +8 -1
- package/src/presentation/routes/rate-limit.ts +45 -0
- package/src/presentation/routes/workspaces/create.test.ts +33 -3
- package/src/presentation/routes/workspaces/create.ts +9 -1
- package/src/presentation/routes/workspaces/index.ts +2 -0
- package/src/presentation/routes/workspaces/list.test.ts +1 -0
- package/src/presentation/routes/workspaces/slug-availability.test.ts +159 -0
- package/src/presentation/routes/workspaces/slug-availability.ts +30 -0
- package/src/presentation/routes/workspaces/update.test.ts +114 -0
- package/src/presentation/routes/workspaces/update.ts +41 -0
- package/test/env.ts +1 -0
- package/test/factories/workspace.ts +3 -1
- package/test/globalSetup.ts +1 -1
- package/test/routes.ts +18 -4
- package/tsconfig.build.tsbuildinfo +1 -1
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
$ shipfox-swc
|
|
2
|
-
Successfully compiled:
|
|
2
|
+
Successfully compiled: 58 files with swc (398.24ms)
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
# @shipfox/api-workspaces
|
|
2
2
|
|
|
3
|
+
## 12.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 9ebc5b4: Add an authenticated, rate-limited workspace slug availability endpoint.
|
|
8
|
+
- e1efaee: Add workspace slugs across the API and client workspace contracts.
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- f78740d: Remove Unicode dash punctuation from package prose and source comments.
|
|
13
|
+
- Updated dependencies [f78740d]
|
|
14
|
+
- Updated dependencies [94aba88]
|
|
15
|
+
- Updated dependencies [4eb18b8]
|
|
16
|
+
- Updated dependencies [f13e8bb]
|
|
17
|
+
- Updated dependencies [9ebc5b4]
|
|
18
|
+
- Updated dependencies [8cc5a36]
|
|
19
|
+
- Updated dependencies [34a5639]
|
|
20
|
+
- Updated dependencies [032d316]
|
|
21
|
+
- Updated dependencies [cb0abfa]
|
|
22
|
+
- Updated dependencies [e1efaee]
|
|
23
|
+
- @shipfox/api-auth-dto@12.0.0
|
|
24
|
+
- @shipfox/api-common-dto@12.0.0
|
|
25
|
+
- @shipfox/inter-module@0.2.3
|
|
26
|
+
- @shipfox/node-fastify@0.4.1
|
|
27
|
+
- @shipfox/node-module@1.0.5
|
|
28
|
+
- @shipfox/api-workspaces-dto@12.0.0
|
|
29
|
+
- @shipfox/api-projects-dto@12.0.0
|
|
30
|
+
- @shipfox/node-postgres@0.5.0
|
|
31
|
+
- @shipfox/node-rate-limit@0.4.0
|
|
32
|
+
- @shipfox/api-auth-context@12.0.0
|
|
33
|
+
- @shipfox/api-runners-dto@12.0.0
|
|
34
|
+
- @shipfox/node-drizzle@0.3.5
|
|
35
|
+
- @shipfox/node-outbox@0.2.6
|
|
36
|
+
|
|
3
37
|
## 11.0.0
|
|
4
38
|
|
|
5
39
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Shipfox API Workspaces manages who can use a workspace and send or accept invite
|
|
|
9
9
|
- **Invitation helpers**: Make, view, accept, list, and revoke invites.
|
|
10
10
|
- **Workspace helpers**: Read workspaces and check a signed-in user's access.
|
|
11
11
|
|
|
12
|
-
## Installation
|
|
12
|
+
## Installation and setup
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
15
|
pnpm add @shipfox/api-workspaces
|
|
@@ -38,13 +38,19 @@ const membership = await ensureMembership({
|
|
|
38
38
|
| Variable | Default | Purpose |
|
|
39
39
|
| --- | --- | --- |
|
|
40
40
|
| `CLIENT_BASE_URL` | `http://localhost:5173` | Base URL used in workspace invitation links. |
|
|
41
|
+
| `AUTH_ROOT_KEY` | none | Root key used to HMAC source IPs for the workspace slug availability rate limit. |
|
|
41
42
|
|
|
42
43
|
Invitation email uses the shared `@shipfox/node-mailer` configuration.
|
|
43
44
|
|
|
44
45
|
## Routes / API / Data Model
|
|
45
46
|
|
|
46
|
-
Routes mount under `/workspaces`. They make and
|
|
47
|
-
and make, list, view, accept, or
|
|
47
|
+
Routes mount under `/workspaces`. They make, list, and update workspaces, check
|
|
48
|
+
workspace slug availability, manage members, and make, list, view, accept, or
|
|
49
|
+
revoke invites. Every workspace has a unique `slug` used in client URLs;
|
|
50
|
+
creating a workspace requires one, and `PATCH /workspaces/:workspaceId` can
|
|
51
|
+
rename it. A taken slug returns `slug-conflict`. Updates write a
|
|
52
|
+
`workspaces.workspace.updated` outbox event.
|
|
53
|
+
The composed module also mounts
|
|
48
54
|
`GET /admin/workspaces`, which requires the Auth `admin-observer` role and returns
|
|
49
55
|
bounded workspace identity, lifecycle, member, project, and best-effort job-count
|
|
50
56
|
summaries. Supporting count failures are represented as `unknown`. `POST
|
|
@@ -54,6 +60,18 @@ summaries. Supporting count failures are represented as `unknown`. `POST
|
|
|
54
60
|
status, correlation_id}`. Both write a redacted `administration.action.performed`
|
|
55
61
|
outbox event and record their result for idempotent retries.
|
|
56
62
|
|
|
63
|
+
Workspace identity fields are:
|
|
64
|
+
|
|
65
|
+
| Field | Constraint | Use |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| `id` | UUID, stable | API resource identity and foreign references |
|
|
68
|
+
| `name` | Required display text | Workspace label |
|
|
69
|
+
| `slug` | Required, globally unique resource slug | Client navigation and display |
|
|
70
|
+
|
|
71
|
+
The API keeps `id` in resource path parameters. `slug` is accepted on create and
|
|
72
|
+
update, and a duplicate returns `409 slug-conflict`. A slug change frees the old
|
|
73
|
+
value immediately. No API resource route resolves a workspace from its slug.
|
|
74
|
+
|
|
57
75
|
The module creates these tables:
|
|
58
76
|
|
|
59
77
|
- `workspaces`
|
|
@@ -61,8 +79,9 @@ The module creates these tables:
|
|
|
61
79
|
- `workspaces_invitations`
|
|
62
80
|
- `workspaces_outbox`
|
|
63
81
|
- `workspaces_admin_command_results`
|
|
82
|
+
- `workspaces_rate_limits`
|
|
64
83
|
|
|
65
|
-
## Behavior
|
|
84
|
+
## Behavior notes
|
|
66
85
|
|
|
67
86
|
`ensureMembership` uses a unique database rule to keep one row for `user_id`
|
|
68
87
|
and `workspace_id`. Calls at the same time get the same row. It saves the email
|
|
@@ -72,6 +91,10 @@ counts only new rows.
|
|
|
72
91
|
Invite acceptance keeps its current transaction. It does not change a row that
|
|
73
92
|
is already there.
|
|
74
93
|
|
|
94
|
+
`GET /workspaces/slug-availability?slug=acme` requires an authenticated user and
|
|
95
|
+
returns only `{available: boolean}`. A malformed slug returns 400. Requests are
|
|
96
|
+
limited to 60 per source IP in a five-minute window.
|
|
97
|
+
|
|
75
98
|
`ensureMembership` fails when its workspace does not exist. Workspace checks
|
|
76
99
|
throw `WorkspaceNotFoundError` or `MembershipRequiredError`. Invite helpers
|
|
77
100
|
throw their exported token and email mismatch errors when an invite cannot be
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../../src/core/entities/workspace.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC;AAEjE,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,eAAe,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB"}
|
|
1
|
+
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../../src/core/entities/workspace.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,WAAW,GAAG,SAAS,CAAC;AAEjE,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,eAAe,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/entities/workspace.ts"],"sourcesContent":["export type WorkspaceStatus = 'active' | 'suspended' | 'deleted';\n\nexport interface Workspace {\n id: string;\n name: string;\n status: WorkspaceStatus;\n settings: Record<string, unknown>;\n createdBy: string | null;\n createdAt: Date;\n updatedAt: Date;\n}\n"],"names":[],"mappings":"AAEA,
|
|
1
|
+
{"version":3,"sources":["../../../src/core/entities/workspace.ts"],"sourcesContent":["export type WorkspaceStatus = 'active' | 'suspended' | 'deleted';\n\nexport interface Workspace {\n id: string;\n name: string;\n slug: string;\n status: WorkspaceStatus;\n settings: Record<string, unknown>;\n createdBy: string | null;\n createdAt: Date;\n updatedAt: Date;\n}\n"],"names":[],"mappings":"AAEA,WASC"}
|
package/dist/core/errors.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export declare class WorkspaceNotFoundError extends Error {
|
|
2
2
|
constructor(workspaceId: string);
|
|
3
3
|
}
|
|
4
|
+
export declare class WorkspaceSlugConflictError extends Error {
|
|
5
|
+
constructor(slug: string);
|
|
6
|
+
}
|
|
4
7
|
export declare class WorkspaceInactiveError extends Error {
|
|
5
8
|
constructor(workspaceId: string);
|
|
6
9
|
}
|
|
@@ -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,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"}
|
|
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,0BAA2B,SAAQ,KAAK;gBACvC,IAAI,EAAE,MAAM;CAIzB;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"}
|
package/dist/core/errors.js
CHANGED
|
@@ -4,6 +4,12 @@ export class WorkspaceNotFoundError extends Error {
|
|
|
4
4
|
this.name = 'WorkspaceNotFoundError';
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
|
+
export class WorkspaceSlugConflictError extends Error {
|
|
8
|
+
constructor(slug){
|
|
9
|
+
super(`Workspace slug is already taken: ${slug}`);
|
|
10
|
+
this.name = 'WorkspaceSlugConflictError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
7
13
|
export class WorkspaceInactiveError extends Error {
|
|
8
14
|
constructor(workspaceId){
|
|
9
15
|
super(`Workspace is not active: ${workspaceId}`);
|
package/dist/core/errors.js.map
CHANGED
|
@@ -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 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":"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+
|
|
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 WorkspaceSlugConflictError extends Error {\n constructor(slug: string) {\n super(`Workspace slug is already taken: ${slug}`);\n this.name = 'WorkspaceSlugConflictError';\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","WorkspaceSlugConflictError","slug","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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"}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ export { type ListWorkspaceAdministratorSummariesParams, type ListWorkspaceAdmin
|
|
|
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, WorkspaceAdminIdempotencyKeyReuseError, WorkspaceAlreadySuspendedError, WorkspaceDeletedError, WorkspaceNotFoundError, WorkspaceNotSuspendedError, } from './errors.js';
|
|
5
|
+
export { InvitationEmailMismatchError, InvitationNotFoundError, InvitationWorkspaceMismatchError, LastMemberError, MembershipNotFoundError, MembershipRequiredError, OpenInvitationExistsError, SelfRemovalNotAllowedError, TokenAlreadyUsedError, TokenExpiredError, WorkspaceAdminIdempotencyKeyReuseError, WorkspaceAlreadySuspendedError, WorkspaceDeletedError, WorkspaceNotFoundError, WorkspaceNotSuspendedError, WorkspaceSlugConflictError, } 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
|
-
export { createWorkspaceForUser, getWorkspace, listUserWorkspaceMemberships, listWorkspaceMembers, type RequireWorkspaceMembershipParams, type RequireWorkspaceMembershipResult, removeWorkspaceMember, requireWorkspaceMembership, } from './workspaces.js';
|
|
8
|
+
export { checkWorkspaceSlugAvailability, createWorkspaceForUser, getWorkspace, listUserWorkspaceMemberships, listWorkspaceMembers, type RequireWorkspaceMembershipParams, type RequireWorkspaceMembershipResult, removeWorkspaceMember, requireWorkspaceMembership, updateWorkspaceDetails, } from './workspaces.js';
|
|
9
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/core/index.d.ts.map
CHANGED
|
@@ -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,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,
|
|
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,EAC1B,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,8BAA8B,EAC9B,sBAAsB,EACtB,YAAY,EACZ,4BAA4B,EAC5B,oBAAoB,EACpB,KAAK,gCAAgC,EACrC,KAAK,gCAAgC,EACrC,qBAAqB,EACrB,0BAA0B,EAC1B,sBAAsB,GACvB,MAAM,iBAAiB,CAAC"}
|
package/dist/core/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
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';
|
|
2
|
+
export { InvitationEmailMismatchError, InvitationNotFoundError, InvitationWorkspaceMismatchError, LastMemberError, MembershipNotFoundError, MembershipRequiredError, OpenInvitationExistsError, SelfRemovalNotAllowedError, TokenAlreadyUsedError, TokenExpiredError, WorkspaceAdminIdempotencyKeyReuseError, WorkspaceAlreadySuspendedError, WorkspaceDeletedError, WorkspaceNotFoundError, WorkspaceNotSuspendedError, WorkspaceSlugConflictError } from './errors.js';
|
|
3
3
|
export { acceptWorkspaceInvitation, createWorkspaceInvitation, listWorkspaceInvitations, previewInvitation, revokeWorkspaceInvitation } from './invitations.js';
|
|
4
4
|
export { ensureMembership } from './memberships.js';
|
|
5
|
-
export { createWorkspaceForUser, getWorkspace, listUserWorkspaceMemberships, listWorkspaceMembers, removeWorkspaceMember, requireWorkspaceMembership } from './workspaces.js';
|
|
5
|
+
export { checkWorkspaceSlugAvailability, createWorkspaceForUser, getWorkspace, listUserWorkspaceMemberships, listWorkspaceMembers, removeWorkspaceMember, requireWorkspaceMembership, updateWorkspaceDetails } from './workspaces.js';
|
|
6
6
|
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
package/dist/core/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
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 WorkspaceSlugConflictError,\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 checkWorkspaceSlugAvailability,\n createWorkspaceForUser,\n getWorkspace,\n listUserWorkspaceMemberships,\n listWorkspaceMembers,\n type RequireWorkspaceMembershipParams,\n type RequireWorkspaceMembershipResult,\n removeWorkspaceMember,\n requireWorkspaceMembership,\n updateWorkspaceDetails,\n} from './workspaces.js';\n"],"names":["listWorkspaceAdministratorSummaries","reactivateWorkspace","suspendWorkspace","InvitationEmailMismatchError","InvitationNotFoundError","InvitationWorkspaceMismatchError","LastMemberError","MembershipNotFoundError","MembershipRequiredError","OpenInvitationExistsError","SelfRemovalNotAllowedError","TokenAlreadyUsedError","TokenExpiredError","WorkspaceAdminIdempotencyKeyReuseError","WorkspaceAlreadySuspendedError","WorkspaceDeletedError","WorkspaceNotFoundError","WorkspaceNotSuspendedError","WorkspaceSlugConflictError","acceptWorkspaceInvitation","createWorkspaceInvitation","listWorkspaceInvitations","previewInvitation","revokeWorkspaceInvitation","ensureMembership","checkWorkspaceSlugAvailability","createWorkspaceForUser","getWorkspace","listUserWorkspaceMemberships","listWorkspaceMembers","removeWorkspaceMember","requireWorkspaceMembership","updateWorkspaceDetails"],"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,EAC1BC,0BAA0B,QACrB,cAAc;AACrB,SACEC,yBAAyB,EACzBC,yBAAyB,EACzBC,wBAAwB,EAExBC,iBAAiB,EACjBC,yBAAyB,QACpB,mBAAmB;AAC1B,SAAqCC,gBAAgB,QAAO,mBAAmB;AAC/E,SACEC,8BAA8B,EAC9BC,sBAAsB,EACtBC,YAAY,EACZC,4BAA4B,EAC5BC,oBAAoB,EAGpBC,qBAAqB,EACrBC,0BAA0B,EAC1BC,sBAAsB,QACjB,kBAAkB"}
|
package/dist/core/invitations.js
CHANGED
|
@@ -53,7 +53,7 @@ export async function previewInvitation(params) {
|
|
|
53
53
|
}
|
|
54
54
|
const workspace = await getWorkspaceById(invitation.workspaceId);
|
|
55
55
|
if (!workspace) {
|
|
56
|
-
// Workspace cascade-deleted
|
|
56
|
+
// Workspace cascade-deleted: the invitation row is orphaned. Treat as invalid.
|
|
57
57
|
return {
|
|
58
58
|
status: 'invalid'
|
|
59
59
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/invitations.ts"],"sourcesContent":["import type {UserContextMembership} from '@shipfox/api-auth-context';\nimport {emailSchema} from '@shipfox/api-common-dto';\nimport {generateOpaqueToken, hashOpaqueToken} from '@shipfox/node-tokens';\nimport {config} from '#config.js';\nimport {\n createInvitation,\n findInvitationById,\n findInvitationByToken,\n listOpenInvitationsByWorkspace,\n reconcileInvitationAcceptance,\n revokeInvitation,\n} from '#db/invitations.js';\nimport {getWorkspaceById} from '#db/workspaces.js';\nimport type {Invitation} from './entities/invitation.js';\nimport type {Membership} from './entities/membership.js';\nimport {\n InvitationEmailMismatchError,\n InvitationNotFoundError,\n InvitationWorkspaceMismatchError,\n TokenAlreadyUsedError,\n TokenExpiredError,\n TokenInvalidError,\n} from './errors.js';\nimport {requireWorkspaceMembership} from './workspaces.js';\n\nconst INVITATION_TTL_DAYS = 7;\n\nfunction daysFromNow(days: number): Date {\n return new Date(Date.now() + days * 24 * 60 * 60 * 1000);\n}\n\nfunction nonBlankOrFallback(value: string | null | undefined, fallback: string): string {\n const trimmed = value?.trim();\n return trimmed ? trimmed : fallback;\n}\n\nexport async function createWorkspaceInvitation(params: {\n workspaceId: string;\n email: string;\n invitedByUserId: string;\n invitedByDisplay?: string | null;\n invitedByMemberships: ReadonlyArray<UserContextMembership>;\n}): Promise<Invitation> {\n const email = emailSchema.parse(params.email);\n const {workspace} = await requireWorkspaceMembership({\n workspaceId: params.workspaceId,\n userId: params.invitedByUserId,\n memberships: params.invitedByMemberships,\n enforceWorkspaceStatus: false,\n });\n\n const rawToken = generateOpaqueToken('invitation');\n const invitation = await createInvitation({\n workspaceId: params.workspaceId,\n email,\n hashedToken: hashOpaqueToken(rawToken),\n expiresAt: daysFromNow(INVITATION_TTL_DAYS),\n invitedByUserId: params.invitedByUserId,\n invitedByDisplay: params.invitedByDisplay ?? null,\n sendEmail: {\n workspaceName: nonBlankOrFallback(workspace.name, 'your workspace'),\n inviterName: nonBlankOrFallback(params.invitedByDisplay, 'A teammate'),\n inviteLink: `${config.CLIENT_BASE_URL}/invitations/accept?token=${rawToken}`,\n },\n });\n\n return invitation;\n}\n\nexport async function peekInvitationByRawToken(params: {\n token: string;\n}): Promise<Invitation | undefined> {\n return await findInvitationByToken({hashedToken: hashOpaqueToken(params.token)});\n}\n\nexport type PreviewInvitationResult =\n | {\n status: 'pending';\n workspaceId: string;\n workspaceName: string;\n email: string;\n invitedByDisplay: string | null;\n expiresAt: Date;\n }\n | {status: 'expired'; workspaceName: string; expiresAt: Date}\n | {status: 'already_used'; workspaceName: string}\n | {status: 'invalid'};\n\nexport interface AcceptedWorkspaceInvitation {\n invitation: Invitation;\n membership: Membership;\n alreadyMember: boolean;\n}\n\nexport async function previewInvitation(params: {token: string}): Promise<PreviewInvitationResult> {\n const invitation = await peekInvitationByRawToken({token: params.token});\n if (!invitation) {\n return {status: 'invalid'};\n }\n\n const workspace = await getWorkspaceById(invitation.workspaceId);\n if (!workspace) {\n // Workspace cascade-deleted — the invitation row is orphaned. Treat as invalid.\n return {status: 'invalid'};\n }\n\n if (invitation.revokedAt !== null) {\n return {status: 'invalid'};\n }\n\n if (invitation.acceptedAt !== null) {\n return {status: 'already_used', workspaceName: workspace.name};\n }\n\n if (invitation.expiresAt.getTime() <= Date.now()) {\n return {\n status: 'expired',\n workspaceName: workspace.name,\n expiresAt: invitation.expiresAt,\n };\n }\n\n return {\n status: 'pending',\n workspaceId: workspace.id,\n workspaceName: workspace.name,\n email: invitation.email,\n invitedByDisplay: invitation.invitedByDisplay,\n expiresAt: invitation.expiresAt,\n };\n}\n\nexport async function acceptWorkspaceInvitation(params: {\n token: string;\n userId: string;\n email: string;\n name?: string | null | undefined;\n}): Promise<AcceptedWorkspaceInvitation> {\n const result = await reconcileWorkspaceInvitationAcceptance({\n token: params.token,\n email: params.email,\n userId: params.userId,\n name: params.name,\n });\n if (result.status === 'accepted') {\n return {\n invitation: result.invitation,\n membership: result.membership,\n alreadyMember: result.alreadyMember,\n };\n }\n if (result.status === 'already_accepted' || result.status === 'consumed_by_another_user') {\n throw new TokenAlreadyUsedError();\n }\n if (result.status === 'expired') {\n throw new TokenExpiredError();\n }\n if (result.status === 'email_mismatch') {\n throw new InvitationEmailMismatchError();\n }\n throw new TokenInvalidError(\n result.status === 'revoked' ? 'Invitation token has been revoked' : 'Invitation is invalid',\n );\n}\n\nexport type WorkspaceInvitationReconciliation =\n | {\n status: 'accepted' | 'already_accepted';\n invitation: Invitation;\n membership: Membership;\n alreadyMember: boolean;\n }\n | {\n status: 'invalid' | 'expired' | 'revoked' | 'consumed_by_another_user' | 'email_mismatch';\n };\n\nexport async function reconcileWorkspaceInvitationAcceptance(params: {\n token: string;\n userId: string;\n email: string;\n name?: string | null | undefined;\n}): Promise<WorkspaceInvitationReconciliation> {\n const invitation = await peekInvitationByRawToken({token: params.token});\n if (!invitation) return {status: 'invalid'};\n\n const result = await reconcileInvitationAcceptance({\n invitationId: invitation.id,\n acceptedByUserId: params.userId,\n email: emailSchema.parse(params.email),\n acceptedByUserName: params.name,\n });\n if (result.status === 'accepted') {\n return {\n status: result.status,\n invitation: result.invitation,\n membership: result.membership,\n alreadyMember: result.alreadyMember,\n };\n }\n if (result.status === 'already_accepted') {\n return {\n status: result.status,\n invitation: result.invitation,\n membership: result.membership,\n alreadyMember: true,\n };\n }\n return result;\n}\n\nexport async function listWorkspaceInvitations(params: {\n workspaceId: string;\n requesterUserId: string;\n requesterMemberships: ReadonlyArray<UserContextMembership>;\n}): Promise<Invitation[]> {\n await requireWorkspaceMembership({\n workspaceId: params.workspaceId,\n userId: params.requesterUserId,\n memberships: params.requesterMemberships,\n enforceWorkspaceStatus: false,\n });\n\n return listOpenInvitationsByWorkspace({workspaceId: params.workspaceId});\n}\n\nexport async function revokeWorkspaceInvitation(params: {\n workspaceId: string;\n requesterUserId: string;\n requesterMemberships: ReadonlyArray<UserContextMembership>;\n invitationId: string;\n}): Promise<void> {\n await requireWorkspaceMembership({\n workspaceId: params.workspaceId,\n userId: params.requesterUserId,\n memberships: params.requesterMemberships,\n enforceWorkspaceStatus: false,\n });\n\n const invitation = await findInvitationById({id: params.invitationId});\n if (!invitation) {\n throw new InvitationNotFoundError(params.invitationId);\n }\n\n if (invitation.workspaceId !== params.workspaceId) {\n throw new InvitationWorkspaceMismatchError();\n }\n\n await revokeInvitation({invitationId: params.invitationId});\n}\n"],"names":["emailSchema","generateOpaqueToken","hashOpaqueToken","config","createInvitation","findInvitationById","findInvitationByToken","listOpenInvitationsByWorkspace","reconcileInvitationAcceptance","revokeInvitation","getWorkspaceById","InvitationEmailMismatchError","InvitationNotFoundError","InvitationWorkspaceMismatchError","TokenAlreadyUsedError","TokenExpiredError","TokenInvalidError","requireWorkspaceMembership","INVITATION_TTL_DAYS","daysFromNow","days","Date","now","nonBlankOrFallback","value","fallback","trimmed","trim","createWorkspaceInvitation","params","email","parse","workspace","workspaceId","userId","invitedByUserId","memberships","invitedByMemberships","enforceWorkspaceStatus","rawToken","invitation","hashedToken","expiresAt","invitedByDisplay","sendEmail","workspaceName","name","inviterName","inviteLink","CLIENT_BASE_URL","peekInvitationByRawToken","token","previewInvitation","status","revokedAt","acceptedAt","getTime","id","acceptWorkspaceInvitation","result","reconcileWorkspaceInvitationAcceptance","membership","alreadyMember","invitationId","acceptedByUserId","acceptedByUserName","listWorkspaceInvitations","requesterUserId","requesterMemberships","revokeWorkspaceInvitation"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/core/invitations.ts"],"sourcesContent":["import type {UserContextMembership} from '@shipfox/api-auth-context';\nimport {emailSchema} from '@shipfox/api-common-dto';\nimport {generateOpaqueToken, hashOpaqueToken} from '@shipfox/node-tokens';\nimport {config} from '#config.js';\nimport {\n createInvitation,\n findInvitationById,\n findInvitationByToken,\n listOpenInvitationsByWorkspace,\n reconcileInvitationAcceptance,\n revokeInvitation,\n} from '#db/invitations.js';\nimport {getWorkspaceById} from '#db/workspaces.js';\nimport type {Invitation} from './entities/invitation.js';\nimport type {Membership} from './entities/membership.js';\nimport {\n InvitationEmailMismatchError,\n InvitationNotFoundError,\n InvitationWorkspaceMismatchError,\n TokenAlreadyUsedError,\n TokenExpiredError,\n TokenInvalidError,\n} from './errors.js';\nimport {requireWorkspaceMembership} from './workspaces.js';\n\nconst INVITATION_TTL_DAYS = 7;\n\nfunction daysFromNow(days: number): Date {\n return new Date(Date.now() + days * 24 * 60 * 60 * 1000);\n}\n\nfunction nonBlankOrFallback(value: string | null | undefined, fallback: string): string {\n const trimmed = value?.trim();\n return trimmed ? trimmed : fallback;\n}\n\nexport async function createWorkspaceInvitation(params: {\n workspaceId: string;\n email: string;\n invitedByUserId: string;\n invitedByDisplay?: string | null;\n invitedByMemberships: ReadonlyArray<UserContextMembership>;\n}): Promise<Invitation> {\n const email = emailSchema.parse(params.email);\n const {workspace} = await requireWorkspaceMembership({\n workspaceId: params.workspaceId,\n userId: params.invitedByUserId,\n memberships: params.invitedByMemberships,\n enforceWorkspaceStatus: false,\n });\n\n const rawToken = generateOpaqueToken('invitation');\n const invitation = await createInvitation({\n workspaceId: params.workspaceId,\n email,\n hashedToken: hashOpaqueToken(rawToken),\n expiresAt: daysFromNow(INVITATION_TTL_DAYS),\n invitedByUserId: params.invitedByUserId,\n invitedByDisplay: params.invitedByDisplay ?? null,\n sendEmail: {\n workspaceName: nonBlankOrFallback(workspace.name, 'your workspace'),\n inviterName: nonBlankOrFallback(params.invitedByDisplay, 'A teammate'),\n inviteLink: `${config.CLIENT_BASE_URL}/invitations/accept?token=${rawToken}`,\n },\n });\n\n return invitation;\n}\n\nexport async function peekInvitationByRawToken(params: {\n token: string;\n}): Promise<Invitation | undefined> {\n return await findInvitationByToken({hashedToken: hashOpaqueToken(params.token)});\n}\n\nexport type PreviewInvitationResult =\n | {\n status: 'pending';\n workspaceId: string;\n workspaceName: string;\n email: string;\n invitedByDisplay: string | null;\n expiresAt: Date;\n }\n | {status: 'expired'; workspaceName: string; expiresAt: Date}\n | {status: 'already_used'; workspaceName: string}\n | {status: 'invalid'};\n\nexport interface AcceptedWorkspaceInvitation {\n invitation: Invitation;\n membership: Membership;\n alreadyMember: boolean;\n}\n\nexport async function previewInvitation(params: {token: string}): Promise<PreviewInvitationResult> {\n const invitation = await peekInvitationByRawToken({token: params.token});\n if (!invitation) {\n return {status: 'invalid'};\n }\n\n const workspace = await getWorkspaceById(invitation.workspaceId);\n if (!workspace) {\n // Workspace cascade-deleted: the invitation row is orphaned. Treat as invalid.\n return {status: 'invalid'};\n }\n\n if (invitation.revokedAt !== null) {\n return {status: 'invalid'};\n }\n\n if (invitation.acceptedAt !== null) {\n return {status: 'already_used', workspaceName: workspace.name};\n }\n\n if (invitation.expiresAt.getTime() <= Date.now()) {\n return {\n status: 'expired',\n workspaceName: workspace.name,\n expiresAt: invitation.expiresAt,\n };\n }\n\n return {\n status: 'pending',\n workspaceId: workspace.id,\n workspaceName: workspace.name,\n email: invitation.email,\n invitedByDisplay: invitation.invitedByDisplay,\n expiresAt: invitation.expiresAt,\n };\n}\n\nexport async function acceptWorkspaceInvitation(params: {\n token: string;\n userId: string;\n email: string;\n name?: string | null | undefined;\n}): Promise<AcceptedWorkspaceInvitation> {\n const result = await reconcileWorkspaceInvitationAcceptance({\n token: params.token,\n email: params.email,\n userId: params.userId,\n name: params.name,\n });\n if (result.status === 'accepted') {\n return {\n invitation: result.invitation,\n membership: result.membership,\n alreadyMember: result.alreadyMember,\n };\n }\n if (result.status === 'already_accepted' || result.status === 'consumed_by_another_user') {\n throw new TokenAlreadyUsedError();\n }\n if (result.status === 'expired') {\n throw new TokenExpiredError();\n }\n if (result.status === 'email_mismatch') {\n throw new InvitationEmailMismatchError();\n }\n throw new TokenInvalidError(\n result.status === 'revoked' ? 'Invitation token has been revoked' : 'Invitation is invalid',\n );\n}\n\nexport type WorkspaceInvitationReconciliation =\n | {\n status: 'accepted' | 'already_accepted';\n invitation: Invitation;\n membership: Membership;\n alreadyMember: boolean;\n }\n | {\n status: 'invalid' | 'expired' | 'revoked' | 'consumed_by_another_user' | 'email_mismatch';\n };\n\nexport async function reconcileWorkspaceInvitationAcceptance(params: {\n token: string;\n userId: string;\n email: string;\n name?: string | null | undefined;\n}): Promise<WorkspaceInvitationReconciliation> {\n const invitation = await peekInvitationByRawToken({token: params.token});\n if (!invitation) return {status: 'invalid'};\n\n const result = await reconcileInvitationAcceptance({\n invitationId: invitation.id,\n acceptedByUserId: params.userId,\n email: emailSchema.parse(params.email),\n acceptedByUserName: params.name,\n });\n if (result.status === 'accepted') {\n return {\n status: result.status,\n invitation: result.invitation,\n membership: result.membership,\n alreadyMember: result.alreadyMember,\n };\n }\n if (result.status === 'already_accepted') {\n return {\n status: result.status,\n invitation: result.invitation,\n membership: result.membership,\n alreadyMember: true,\n };\n }\n return result;\n}\n\nexport async function listWorkspaceInvitations(params: {\n workspaceId: string;\n requesterUserId: string;\n requesterMemberships: ReadonlyArray<UserContextMembership>;\n}): Promise<Invitation[]> {\n await requireWorkspaceMembership({\n workspaceId: params.workspaceId,\n userId: params.requesterUserId,\n memberships: params.requesterMemberships,\n enforceWorkspaceStatus: false,\n });\n\n return listOpenInvitationsByWorkspace({workspaceId: params.workspaceId});\n}\n\nexport async function revokeWorkspaceInvitation(params: {\n workspaceId: string;\n requesterUserId: string;\n requesterMemberships: ReadonlyArray<UserContextMembership>;\n invitationId: string;\n}): Promise<void> {\n await requireWorkspaceMembership({\n workspaceId: params.workspaceId,\n userId: params.requesterUserId,\n memberships: params.requesterMemberships,\n enforceWorkspaceStatus: false,\n });\n\n const invitation = await findInvitationById({id: params.invitationId});\n if (!invitation) {\n throw new InvitationNotFoundError(params.invitationId);\n }\n\n if (invitation.workspaceId !== params.workspaceId) {\n throw new InvitationWorkspaceMismatchError();\n }\n\n await revokeInvitation({invitationId: params.invitationId});\n}\n"],"names":["emailSchema","generateOpaqueToken","hashOpaqueToken","config","createInvitation","findInvitationById","findInvitationByToken","listOpenInvitationsByWorkspace","reconcileInvitationAcceptance","revokeInvitation","getWorkspaceById","InvitationEmailMismatchError","InvitationNotFoundError","InvitationWorkspaceMismatchError","TokenAlreadyUsedError","TokenExpiredError","TokenInvalidError","requireWorkspaceMembership","INVITATION_TTL_DAYS","daysFromNow","days","Date","now","nonBlankOrFallback","value","fallback","trimmed","trim","createWorkspaceInvitation","params","email","parse","workspace","workspaceId","userId","invitedByUserId","memberships","invitedByMemberships","enforceWorkspaceStatus","rawToken","invitation","hashedToken","expiresAt","invitedByDisplay","sendEmail","workspaceName","name","inviterName","inviteLink","CLIENT_BASE_URL","peekInvitationByRawToken","token","previewInvitation","status","revokedAt","acceptedAt","getTime","id","acceptWorkspaceInvitation","result","reconcileWorkspaceInvitationAcceptance","membership","alreadyMember","invitationId","acceptedByUserId","acceptedByUserName","listWorkspaceInvitations","requesterUserId","requesterMemberships","revokeWorkspaceInvitation"],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { RateLimitExceededError, type RateLimitOutcome, type RateLimitPolicy, RateLimitUnavailableError } from '@shipfox/node-rate-limit';
|
|
2
|
+
export type WorkspacesRateLimitAction = 'slug-availability';
|
|
3
|
+
export type WorkspacesRateLimitScope = 'ip';
|
|
4
|
+
export type WorkspacesRateLimitOutcome = RateLimitOutcome;
|
|
5
|
+
export type WorkspacesRateLimitPolicy = RateLimitPolicy;
|
|
6
|
+
export interface CheckWorkspacesRateLimitParams extends WorkspacesRateLimitPolicy {
|
|
7
|
+
action: WorkspacesRateLimitAction;
|
|
8
|
+
scope: WorkspacesRateLimitScope;
|
|
9
|
+
identifier: string;
|
|
10
|
+
now?: Date | undefined;
|
|
11
|
+
timeoutMs?: number | undefined;
|
|
12
|
+
}
|
|
13
|
+
export declare class WorkspacesRateLimitExceededError extends RateLimitExceededError<WorkspacesRateLimitAction, WorkspacesRateLimitScope> {
|
|
14
|
+
constructor(params: {
|
|
15
|
+
action: WorkspacesRateLimitAction;
|
|
16
|
+
scope: WorkspacesRateLimitScope;
|
|
17
|
+
retryAfterSeconds: number;
|
|
18
|
+
identifierHmacPrefix: string;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export declare class WorkspacesRateLimitUnavailableError extends RateLimitUnavailableError<WorkspacesRateLimitAction, WorkspacesRateLimitScope> {
|
|
22
|
+
constructor(params: {
|
|
23
|
+
action: WorkspacesRateLimitAction;
|
|
24
|
+
scope: WorkspacesRateLimitScope;
|
|
25
|
+
identifierHmacPrefix: string;
|
|
26
|
+
cause: unknown;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export declare const WORKSPACE_SLUG_AVAILABILITY_RATE_LIMIT: {
|
|
30
|
+
readonly limit: 60;
|
|
31
|
+
readonly windowSeconds: number;
|
|
32
|
+
};
|
|
33
|
+
export declare function hashWorkspacesRateLimitIdentifier(params: {
|
|
34
|
+
action: WorkspacesRateLimitAction;
|
|
35
|
+
scope: WorkspacesRateLimitScope;
|
|
36
|
+
identifier: string;
|
|
37
|
+
}): string;
|
|
38
|
+
export declare function checkWorkspacesRateLimit(params: CheckWorkspacesRateLimitParams): Promise<void>;
|
|
39
|
+
//# sourceMappingURL=rate-limit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/core/rate-limit.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,sBAAsB,EACtB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,yBAAyB,EAC1B,MAAM,0BAA0B,CAAC;AAOlC,MAAM,MAAM,yBAAyB,GAAG,mBAAmB,CAAC;AAC5D,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC;AAC5C,MAAM,MAAM,0BAA0B,GAAG,gBAAgB,CAAC;AAC1D,MAAM,MAAM,yBAAyB,GAAG,eAAe,CAAC;AAExD,MAAM,WAAW,8BAA+B,SAAQ,yBAAyB;IAC/E,MAAM,EAAE,yBAAyB,CAAC;IAClC,KAAK,EAAE,wBAAwB,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,IAAI,GAAG,SAAS,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED,qBAAa,gCAAiC,SAAQ,sBAAsB,CAC1E,yBAAyB,EACzB,wBAAwB,CACzB;gBACa,MAAM,EAAE;QAClB,MAAM,EAAE,yBAAyB,CAAC;QAClC,KAAK,EAAE,wBAAwB,CAAC;QAChC,iBAAiB,EAAE,MAAM,CAAC;QAC1B,oBAAoB,EAAE,MAAM,CAAC;KAC9B;CAIF;AAED,qBAAa,mCAAoC,SAAQ,yBAAyB,CAChF,yBAAyB,EACzB,wBAAwB,CACzB;gBACa,MAAM,EAAE;QAClB,MAAM,EAAE,yBAAyB,CAAC;QAClC,KAAK,EAAE,wBAAwB,CAAC;QAChC,oBAAoB,EAAE,MAAM,CAAC;QAC7B,KAAK,EAAE,OAAO,CAAC;KAChB;CAIF;AAED,eAAO,MAAM,sCAAsC;;;CAGzC,CAAC;AAKX,wBAAgB,iCAAiC,CAAC,MAAM,EAAE;IACxD,MAAM,EAAE,yBAAyB,CAAC;IAClC,KAAK,EAAE,wBAAwB,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,MAAM,CAQT;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,8BAA8B,GACrC,OAAO,CAAC,IAAI,CAAC,CAoCf"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { rateLimitIdentifierKey } from '@shipfox/node-auth-root-key';
|
|
2
|
+
import { checkRateLimit, hashRateLimitIdentifier, RateLimitExceededError, RateLimitUnavailableError } from '@shipfox/node-rate-limit';
|
|
3
|
+
import { consumeWorkspacesRateLimit, pruneExpiredWorkspacesRateLimits } from '#db/rate-limits.js';
|
|
4
|
+
import { recordWorkspaceRateLimitCheck, recordWorkspaceRateLimitPruneFailure } from '#metrics/instance.js';
|
|
5
|
+
export class WorkspacesRateLimitExceededError extends RateLimitExceededError {
|
|
6
|
+
constructor(params){
|
|
7
|
+
super(params);
|
|
8
|
+
this.name = 'WorkspacesRateLimitExceededError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export class WorkspacesRateLimitUnavailableError extends RateLimitUnavailableError {
|
|
12
|
+
constructor(params){
|
|
13
|
+
super(params);
|
|
14
|
+
this.name = 'WorkspacesRateLimitUnavailableError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export const WORKSPACE_SLUG_AVAILABILITY_RATE_LIMIT = {
|
|
18
|
+
limit: 60,
|
|
19
|
+
windowSeconds: 5 * 60
|
|
20
|
+
};
|
|
21
|
+
const RATE_LIMIT_TIMEOUT_MS = 250;
|
|
22
|
+
const IDENTIFIER_HASH_DOMAIN = 'shipfox.workspaces.rate-limit.identifier.v1';
|
|
23
|
+
export function hashWorkspacesRateLimitIdentifier(params) {
|
|
24
|
+
return hashRateLimitIdentifier({
|
|
25
|
+
action: params.action,
|
|
26
|
+
scope: params.scope,
|
|
27
|
+
identifier: params.identifier,
|
|
28
|
+
secret: rateLimitIdentifierKey(),
|
|
29
|
+
domain: IDENTIFIER_HASH_DOMAIN
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export async function checkWorkspacesRateLimit(params) {
|
|
33
|
+
try {
|
|
34
|
+
await checkRateLimit({
|
|
35
|
+
action: params.action,
|
|
36
|
+
scope: params.scope,
|
|
37
|
+
identifier: params.identifier,
|
|
38
|
+
limit: params.limit,
|
|
39
|
+
windowSeconds: params.windowSeconds,
|
|
40
|
+
identifierSecret: rateLimitIdentifierKey(),
|
|
41
|
+
identifierHashDomain: IDENTIFIER_HASH_DOMAIN,
|
|
42
|
+
consume: consumeWorkspacesRateLimit,
|
|
43
|
+
prune: pruneExpiredWorkspacesRateLimits,
|
|
44
|
+
onCheck: recordWorkspaceRateLimitCheck,
|
|
45
|
+
onPruneFailure: recordWorkspaceRateLimitPruneFailure,
|
|
46
|
+
now: params.now,
|
|
47
|
+
timeoutMs: params.timeoutMs ?? RATE_LIMIT_TIMEOUT_MS
|
|
48
|
+
});
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error instanceof RateLimitExceededError) {
|
|
51
|
+
throw new WorkspacesRateLimitExceededError({
|
|
52
|
+
action: error.action,
|
|
53
|
+
scope: error.scope,
|
|
54
|
+
retryAfterSeconds: error.retryAfterSeconds,
|
|
55
|
+
identifierHmacPrefix: error.identifierHmacPrefix
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
if (error instanceof RateLimitUnavailableError) {
|
|
59
|
+
throw new WorkspacesRateLimitUnavailableError({
|
|
60
|
+
action: error.action,
|
|
61
|
+
scope: error.scope,
|
|
62
|
+
identifierHmacPrefix: error.identifierHmacPrefix,
|
|
63
|
+
cause: error.cause
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
//# sourceMappingURL=rate-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/rate-limit.ts"],"sourcesContent":["import {rateLimitIdentifierKey} from '@shipfox/node-auth-root-key';\nimport {\n checkRateLimit,\n hashRateLimitIdentifier,\n RateLimitExceededError,\n type RateLimitOutcome,\n type RateLimitPolicy,\n RateLimitUnavailableError,\n} from '@shipfox/node-rate-limit';\nimport {consumeWorkspacesRateLimit, pruneExpiredWorkspacesRateLimits} from '#db/rate-limits.js';\nimport {\n recordWorkspaceRateLimitCheck,\n recordWorkspaceRateLimitPruneFailure,\n} from '#metrics/instance.js';\n\nexport type WorkspacesRateLimitAction = 'slug-availability';\nexport type WorkspacesRateLimitScope = 'ip';\nexport type WorkspacesRateLimitOutcome = RateLimitOutcome;\nexport type WorkspacesRateLimitPolicy = RateLimitPolicy;\n\nexport interface CheckWorkspacesRateLimitParams extends WorkspacesRateLimitPolicy {\n action: WorkspacesRateLimitAction;\n scope: WorkspacesRateLimitScope;\n identifier: string;\n now?: Date | undefined;\n timeoutMs?: number | undefined;\n}\n\nexport class WorkspacesRateLimitExceededError extends RateLimitExceededError<\n WorkspacesRateLimitAction,\n WorkspacesRateLimitScope\n> {\n constructor(params: {\n action: WorkspacesRateLimitAction;\n scope: WorkspacesRateLimitScope;\n retryAfterSeconds: number;\n identifierHmacPrefix: string;\n }) {\n super(params);\n this.name = 'WorkspacesRateLimitExceededError';\n }\n}\n\nexport class WorkspacesRateLimitUnavailableError extends RateLimitUnavailableError<\n WorkspacesRateLimitAction,\n WorkspacesRateLimitScope\n> {\n constructor(params: {\n action: WorkspacesRateLimitAction;\n scope: WorkspacesRateLimitScope;\n identifierHmacPrefix: string;\n cause: unknown;\n }) {\n super(params);\n this.name = 'WorkspacesRateLimitUnavailableError';\n }\n}\n\nexport const WORKSPACE_SLUG_AVAILABILITY_RATE_LIMIT = {\n limit: 60,\n windowSeconds: 5 * 60,\n} as const;\n\nconst RATE_LIMIT_TIMEOUT_MS = 250;\nconst IDENTIFIER_HASH_DOMAIN = 'shipfox.workspaces.rate-limit.identifier.v1';\n\nexport function hashWorkspacesRateLimitIdentifier(params: {\n action: WorkspacesRateLimitAction;\n scope: WorkspacesRateLimitScope;\n identifier: string;\n}): string {\n return hashRateLimitIdentifier({\n action: params.action,\n scope: params.scope,\n identifier: params.identifier,\n secret: rateLimitIdentifierKey(),\n domain: IDENTIFIER_HASH_DOMAIN,\n });\n}\n\nexport async function checkWorkspacesRateLimit(\n params: CheckWorkspacesRateLimitParams,\n): Promise<void> {\n try {\n await checkRateLimit({\n action: params.action,\n scope: params.scope,\n identifier: params.identifier,\n limit: params.limit,\n windowSeconds: params.windowSeconds,\n identifierSecret: rateLimitIdentifierKey(),\n identifierHashDomain: IDENTIFIER_HASH_DOMAIN,\n consume: consumeWorkspacesRateLimit,\n prune: pruneExpiredWorkspacesRateLimits,\n onCheck: recordWorkspaceRateLimitCheck,\n onPruneFailure: recordWorkspaceRateLimitPruneFailure,\n now: params.now,\n timeoutMs: params.timeoutMs ?? RATE_LIMIT_TIMEOUT_MS,\n });\n } catch (error) {\n if (error instanceof RateLimitExceededError) {\n throw new WorkspacesRateLimitExceededError({\n action: error.action,\n scope: error.scope,\n retryAfterSeconds: error.retryAfterSeconds,\n identifierHmacPrefix: error.identifierHmacPrefix,\n });\n }\n if (error instanceof RateLimitUnavailableError) {\n throw new WorkspacesRateLimitUnavailableError({\n action: error.action,\n scope: error.scope,\n identifierHmacPrefix: error.identifierHmacPrefix,\n cause: error.cause,\n });\n }\n throw error;\n }\n}\n"],"names":["rateLimitIdentifierKey","checkRateLimit","hashRateLimitIdentifier","RateLimitExceededError","RateLimitUnavailableError","consumeWorkspacesRateLimit","pruneExpiredWorkspacesRateLimits","recordWorkspaceRateLimitCheck","recordWorkspaceRateLimitPruneFailure","WorkspacesRateLimitExceededError","params","name","WorkspacesRateLimitUnavailableError","WORKSPACE_SLUG_AVAILABILITY_RATE_LIMIT","limit","windowSeconds","RATE_LIMIT_TIMEOUT_MS","IDENTIFIER_HASH_DOMAIN","hashWorkspacesRateLimitIdentifier","action","scope","identifier","secret","domain","checkWorkspacesRateLimit","identifierSecret","identifierHashDomain","consume","prune","onCheck","onPruneFailure","now","timeoutMs","error","retryAfterSeconds","identifierHmacPrefix","cause"],"mappings":"AAAA,SAAQA,sBAAsB,QAAO,8BAA8B;AACnE,SACEC,cAAc,EACdC,uBAAuB,EACvBC,sBAAsB,EAGtBC,yBAAyB,QACpB,2BAA2B;AAClC,SAAQC,0BAA0B,EAAEC,gCAAgC,QAAO,qBAAqB;AAChG,SACEC,6BAA6B,EAC7BC,oCAAoC,QAC/B,uBAAuB;AAe9B,OAAO,MAAMC,yCAAyCN;IAIpD,YAAYO,MAKX,CAAE;QACD,KAAK,CAACA;QACN,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAMC,4CAA4CR;IAIvD,YAAYM,MAKX,CAAE;QACD,KAAK,CAACA;QACN,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,MAAME,yCAAyC;IACpDC,OAAO;IACPC,eAAe,IAAI;AACrB,EAAW;AAEX,MAAMC,wBAAwB;AAC9B,MAAMC,yBAAyB;AAE/B,OAAO,SAASC,kCAAkCR,MAIjD;IACC,OAAOR,wBAAwB;QAC7BiB,QAAQT,OAAOS,MAAM;QACrBC,OAAOV,OAAOU,KAAK;QACnBC,YAAYX,OAAOW,UAAU;QAC7BC,QAAQtB;QACRuB,QAAQN;IACV;AACF;AAEA,OAAO,eAAeO,yBACpBd,MAAsC;IAEtC,IAAI;QACF,MAAMT,eAAe;YACnBkB,QAAQT,OAAOS,MAAM;YACrBC,OAAOV,OAAOU,KAAK;YACnBC,YAAYX,OAAOW,UAAU;YAC7BP,OAAOJ,OAAOI,KAAK;YACnBC,eAAeL,OAAOK,aAAa;YACnCU,kBAAkBzB;YAClB0B,sBAAsBT;YACtBU,SAAStB;YACTuB,OAAOtB;YACPuB,SAAStB;YACTuB,gBAAgBtB;YAChBuB,KAAKrB,OAAOqB,GAAG;YACfC,WAAWtB,OAAOsB,SAAS,IAAIhB;QACjC;IACF,EAAE,OAAOiB,OAAO;QACd,IAAIA,iBAAiB9B,wBAAwB;YAC3C,MAAM,IAAIM,iCAAiC;gBACzCU,QAAQc,MAAMd,MAAM;gBACpBC,OAAOa,MAAMb,KAAK;gBAClBc,mBAAmBD,MAAMC,iBAAiB;gBAC1CC,sBAAsBF,MAAME,oBAAoB;YAClD;QACF;QACA,IAAIF,iBAAiB7B,2BAA2B;YAC9C,MAAM,IAAIQ,oCAAoC;gBAC5CO,QAAQc,MAAMd,MAAM;gBACpBC,OAAOa,MAAMb,KAAK;gBAClBe,sBAAsBF,MAAME,oBAAoB;gBAChDC,OAAOH,MAAMG,KAAK;YACpB;QACF;QACA,MAAMH;IACR;AACF"}
|
|
@@ -17,10 +17,17 @@ export interface RequireWorkspaceMembershipResult {
|
|
|
17
17
|
export declare function requireWorkspaceMembership(params: RequireWorkspaceMembershipParams): Promise<RequireWorkspaceMembershipResult>;
|
|
18
18
|
export declare function createWorkspaceForUser(params: {
|
|
19
19
|
name: string;
|
|
20
|
+
slug: string;
|
|
20
21
|
userId: string;
|
|
21
22
|
userEmail?: string | undefined;
|
|
22
23
|
userName?: string | null | undefined;
|
|
23
24
|
}): Promise<Workspace>;
|
|
25
|
+
export declare function checkWorkspaceSlugAvailability(slug: string): Promise<boolean>;
|
|
26
|
+
export declare function updateWorkspaceDetails(params: {
|
|
27
|
+
workspaceId: string;
|
|
28
|
+
name?: string | undefined;
|
|
29
|
+
slug?: string | undefined;
|
|
30
|
+
}): Promise<Workspace>;
|
|
24
31
|
export declare function getWorkspace(params: {
|
|
25
32
|
workspaceId: string;
|
|
26
33
|
}): Promise<Workspace>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../src/core/workspaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,2BAA2B,CAAC;AACrE,OAAO,
|
|
1
|
+
{"version":3,"file":"workspaces.d.ts","sourceRoot":"","sources":["../../src/core/workspaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAGL,KAAK,aAAa,EAEnB,MAAM,6BAA6B,CAAC;AAIrC,OAAO,EAIL,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAE7B,MAAM,oBAAoB,CAAC;AAK5B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yBAAyB,CAAC;AAUvD,MAAM,WAAW,gCAAgC;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;IAClD,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,gCAAgC;IAC/C,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,aAAa,CAAC;CACrB;AAED,wBAAsB,0BAA0B,CAC9C,MAAM,EAAE,gCAAgC,GACvC,OAAO,CAAC,gCAAgC,CAAC,CAe3C;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACtC,GAAG,OAAO,CAAC,SAAS,CAAC,CA8BrB;AAED,wBAAsB,8BAA8B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEnF;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B,GAAG,OAAO,CAAC,SAAS,CAAC,CA0BrB;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAOpF;AAED,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAG/F;AAED,wBAAsB,0BAA0B,CAAC,MAAM,EAAE;IACvD,WAAW,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAG/B;AAED,wBAAsB,4BAA4B,CAAC,MAAM,EAAE;IACzD,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAErC;AAED,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;CAC5D,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAShC;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;IAC3D,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBhB"}
|
package/dist/core/workspaces.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { WORKSPACES_WORKSPACE_CREATED } from '@shipfox/api-workspaces-dto';
|
|
1
|
+
import { WORKSPACES_WORKSPACE_CREATED, WORKSPACES_WORKSPACE_UPDATED } from '@shipfox/api-workspaces-dto';
|
|
2
|
+
import { isUniqueViolation } from '@shipfox/node-drizzle';
|
|
2
3
|
import { writeOutboxEvent } from '@shipfox/node-outbox';
|
|
3
4
|
import { db } from '#db/db.js';
|
|
4
5
|
import { findMembership, listMembershipsByUser, listMembershipsByWorkspace, removeMembership } from '#db/memberships.js';
|
|
5
6
|
import { memberships } from '#db/schema/memberships.js';
|
|
6
7
|
import { workspacesOutbox } from '#db/schema/outbox.js';
|
|
7
8
|
import { toWorkspace, workspaces } from '#db/schema/workspaces.js';
|
|
8
|
-
import { getWorkspaceById } from '#db/workspaces.js';
|
|
9
|
-
import { MembershipNotFoundError, MembershipRequiredError, SelfRemovalNotAllowedError, WorkspaceInactiveError, WorkspaceNotFoundError } from './errors.js';
|
|
9
|
+
import { getWorkspaceById, isWorkspaceSlugAvailable, updateWorkspace } from '#db/workspaces.js';
|
|
10
|
+
import { MembershipNotFoundError, MembershipRequiredError, SelfRemovalNotAllowedError, WorkspaceInactiveError, WorkspaceNotFoundError, WorkspaceSlugConflictError } from './errors.js';
|
|
10
11
|
export async function requireWorkspaceMembership(params) {
|
|
11
12
|
const membership = params.memberships.find((m)=>m.workspaceId === params.workspaceId);
|
|
12
13
|
if (!membership) {
|
|
@@ -30,9 +31,14 @@ export async function createWorkspaceForUser(params) {
|
|
|
30
31
|
return await db().transaction(async (tx)=>{
|
|
31
32
|
const [workspaceRow] = await tx.insert(workspaces).values({
|
|
32
33
|
name: params.name,
|
|
34
|
+
slug: params.slug,
|
|
33
35
|
createdBy: params.userId
|
|
36
|
+
}).onConflictDoNothing({
|
|
37
|
+
target: workspaces.slug
|
|
34
38
|
}).returning();
|
|
35
|
-
if (!workspaceRow)
|
|
39
|
+
if (!workspaceRow) {
|
|
40
|
+
throw new WorkspaceSlugConflictError(params.slug);
|
|
41
|
+
}
|
|
36
42
|
await tx.insert(memberships).values({
|
|
37
43
|
userId: params.userId,
|
|
38
44
|
userEmail: params.userEmail ?? `user-${params.userId}@example.local`,
|
|
@@ -45,12 +51,44 @@ export async function createWorkspaceForUser(params) {
|
|
|
45
51
|
payload: {
|
|
46
52
|
workspaceId: workspace.id,
|
|
47
53
|
name: workspace.name,
|
|
54
|
+
slug: workspace.slug,
|
|
48
55
|
creatorUserId: params.userId
|
|
49
56
|
}
|
|
50
57
|
});
|
|
51
58
|
return workspace;
|
|
52
59
|
});
|
|
53
60
|
}
|
|
61
|
+
export async function checkWorkspaceSlugAvailability(slug) {
|
|
62
|
+
return await isWorkspaceSlugAvailable(slug);
|
|
63
|
+
}
|
|
64
|
+
export async function updateWorkspaceDetails(params) {
|
|
65
|
+
try {
|
|
66
|
+
return await db().transaction(async (tx)=>{
|
|
67
|
+
const workspace = await updateWorkspace({
|
|
68
|
+
id: params.workspaceId,
|
|
69
|
+
name: params.name,
|
|
70
|
+
slug: params.slug
|
|
71
|
+
}, {
|
|
72
|
+
tx
|
|
73
|
+
});
|
|
74
|
+
if (!workspace) throw new WorkspaceNotFoundError(params.workspaceId);
|
|
75
|
+
await writeOutboxEvent(tx, workspacesOutbox, {
|
|
76
|
+
type: WORKSPACES_WORKSPACE_UPDATED,
|
|
77
|
+
payload: {
|
|
78
|
+
workspaceId: workspace.id,
|
|
79
|
+
name: workspace.name,
|
|
80
|
+
slug: workspace.slug
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
return workspace;
|
|
84
|
+
});
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (isUniqueViolation(error, 'workspaces_slug_unique') && params.slug) {
|
|
87
|
+
throw new WorkspaceSlugConflictError(params.slug);
|
|
88
|
+
}
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
54
92
|
export async function getWorkspace(params) {
|
|
55
93
|
const workspace = await getWorkspaceById(params.workspaceId);
|
|
56
94
|
if (!workspace) {
|