@shipfox/api-workspaces 4.0.0 → 6.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 +58 -0
- package/README.md +2 -2
- package/dist/config.d.ts +0 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -50
- package/dist/config.js.map +1 -1
- package/dist/core/entities/invitation.d.ts +1 -0
- package/dist/core/entities/invitation.d.ts.map +1 -1
- package/dist/core/entities/invitation.js.map +1 -1
- package/dist/core/invitations.d.ts +21 -2
- package/dist/core/invitations.d.ts.map +1 -1
- package/dist/core/invitations.js +49 -12
- package/dist/core/invitations.js.map +1 -1
- package/dist/core/workspaces.d.ts +1 -1
- package/dist/core/workspaces.d.ts.map +1 -1
- package/dist/core/workspaces.js +13 -1
- package/dist/core/workspaces.js.map +1 -1
- package/dist/db/db.d.ts +34 -0
- package/dist/db/db.d.ts.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/invitations.d.ts +15 -8
- package/dist/db/invitations.d.ts.map +1 -1
- package/dist/db/invitations.js +70 -22
- package/dist/db/invitations.js.map +1 -1
- package/dist/db/schema/invitations.d.ts +17 -0
- package/dist/db/schema/invitations.d.ts.map +1 -1
- package/dist/db/schema/invitations.js +4 -0
- package/dist/db/schema/invitations.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/presentation/inter-module.d.ts +4 -0
- package/dist/presentation/inter-module.d.ts.map +1 -0
- package/dist/presentation/inter-module.js +74 -0
- package/dist/presentation/inter-module.js.map +1 -0
- package/dist/presentation/subscribers/on-invitation-send-requested.js +1 -1
- package/dist/presentation/subscribers/on-invitation-send-requested.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/drizzle/0004_tired_talon.sql +1 -0
- package/drizzle/meta/0004_snapshot.json +448 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +28 -30
- package/src/config.ts +1 -44
- package/src/core/entities/invitation.ts +1 -0
- package/src/core/invitations.test.ts +35 -2
- package/src/core/invitations.ts +72 -17
- package/src/core/workspaces.test.ts +28 -0
- package/src/core/workspaces.ts +19 -2
- package/src/db/index.ts +1 -6
- package/src/db/invitations.test.ts +237 -15
- package/src/db/invitations.ts +78 -50
- package/src/db/schema/invitations.ts +2 -0
- package/src/db/workspaces.test.ts +3 -2
- package/src/index.test.ts +10 -1
- package/src/index.ts +8 -1
- package/src/presentation/inter-module.ts +94 -0
- package/src/presentation/routes/invitations/create.test.ts +19 -1
- package/src/presentation/subscribers/invitation-send-requested.test.ts +1 -1
- package/src/presentation/subscribers/on-invitation-send-requested.ts +1 -1
- 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: 48 files with swc (373.58ms)
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# @shipfox/api-workspaces
|
|
2
2
|
|
|
3
|
+
## 6.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6bdf24b: Adds idempotent workspace invitation reconciliation for retry-safe external acceptance flows.
|
|
8
|
+
- 326f4c0: Exposes Workspaces inter-module operations and moves Auth and OAuth providers onto injected clients.
|
|
9
|
+
- 1820feb: Adds Slack Settings installation and callback recovery while returning stable workspace access errors.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 7366f04: Adds a configured shared mailer that owns SMTP delivery settings. `@shipfox/api-auth` and `@shipfox/api-workspaces` drop their own mailer environment variables and factory logic and use the shared `mailer` from `@shipfox/node-mailer` instead.
|
|
14
|
+
- c2db8c3: Adds workspace member invitation and join lifecycle events to the workspaces outbox.
|
|
15
|
+
- f73da5d: Enforces bounded API context imports and routes inter-module consumers through producer contracts.
|
|
16
|
+
- Updated dependencies [b70f920]
|
|
17
|
+
- Updated dependencies [7366f04]
|
|
18
|
+
- Updated dependencies [54ce48b]
|
|
19
|
+
- Updated dependencies [c0162b0]
|
|
20
|
+
- Updated dependencies [7ac43a4]
|
|
21
|
+
- Updated dependencies [a01e917]
|
|
22
|
+
- Updated dependencies [1b79cda]
|
|
23
|
+
- Updated dependencies [c2db8c3]
|
|
24
|
+
- Updated dependencies [8bdc149]
|
|
25
|
+
- Updated dependencies [3810996]
|
|
26
|
+
- Updated dependencies [b00ed29]
|
|
27
|
+
- Updated dependencies [8aa7cd3]
|
|
28
|
+
- Updated dependencies [326f4c0]
|
|
29
|
+
- Updated dependencies [4a91956]
|
|
30
|
+
- Updated dependencies [81f9544]
|
|
31
|
+
- @shipfox/node-tokens@0.3.0
|
|
32
|
+
- @shipfox/node-mailer@0.2.0
|
|
33
|
+
- @shipfox/node-module@0.4.0
|
|
34
|
+
- @shipfox/node-drizzle@0.3.2
|
|
35
|
+
- @shipfox/node-outbox@0.2.4
|
|
36
|
+
- @shipfox/api-workspaces-dto@6.0.0
|
|
37
|
+
- @shipfox/api-auth-context@6.0.0
|
|
38
|
+
- @shipfox/node-fastify@0.2.4
|
|
39
|
+
- @shipfox/api-common-dto@6.0.0
|
|
40
|
+
- @shipfox/inter-module@0.2.0
|
|
41
|
+
|
|
42
|
+
## 5.0.0
|
|
43
|
+
|
|
44
|
+
### Patch Changes
|
|
45
|
+
|
|
46
|
+
- bb037af: Resolves workspace packages from source during development while published consumers continue to use compiled output.
|
|
47
|
+
- Updated dependencies [bb037af]
|
|
48
|
+
- @shipfox/api-auth-context@5.0.0
|
|
49
|
+
- @shipfox/api-workspaces-dto@5.0.0
|
|
50
|
+
- @shipfox/config@1.2.2
|
|
51
|
+
- @shipfox/node-drizzle@0.3.1
|
|
52
|
+
- @shipfox/node-email@0.2.2
|
|
53
|
+
- @shipfox/node-fastify@0.2.3
|
|
54
|
+
- @shipfox/node-mailer@0.1.4
|
|
55
|
+
- @shipfox/node-module@0.3.2
|
|
56
|
+
- @shipfox/node-opentelemetry@0.5.2
|
|
57
|
+
- @shipfox/node-outbox@0.2.3
|
|
58
|
+
- @shipfox/node-postgres@0.4.2
|
|
59
|
+
- @shipfox/node-tokens@0.2.1
|
|
60
|
+
|
|
3
61
|
## 4.0.0
|
|
4
62
|
|
|
5
63
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ pnpm add @shipfox/api-workspaces
|
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
Set `CLIENT_BASE_URL` to the public client URL. Invite emails use this URL for
|
|
19
|
-
their accept links.
|
|
19
|
+
their accept links. Configure `@shipfox/node-mailer` when invite email must be sent.
|
|
20
20
|
|
|
21
21
|
## Usage
|
|
22
22
|
|
|
@@ -39,7 +39,7 @@ const membership = await ensureMembership({
|
|
|
39
39
|
| --- | --- | --- |
|
|
40
40
|
| `CLIENT_BASE_URL` | `http://localhost:5173` | Base URL used in workspace invitation links. |
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
Invitation email uses the shared `@shipfox/node-mailer` configuration.
|
|
43
43
|
|
|
44
44
|
## Routes / API / Data Model
|
|
45
45
|
|
package/dist/config.d.ts
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import { type Mailer } from '@shipfox/node-mailer';
|
|
2
1
|
export declare const config: Readonly<{
|
|
3
2
|
CLIENT_BASE_URL: string;
|
|
4
|
-
MAILER_TRANSPORT: "console" | "smtp";
|
|
5
|
-
MAILER_FROM: string;
|
|
6
|
-
SMTP_HOST: string | undefined;
|
|
7
|
-
SMTP_PORT: number;
|
|
8
|
-
SMTP_USER: string | undefined;
|
|
9
|
-
SMTP_PASSWORD: string | undefined;
|
|
10
3
|
} & import("@shipfox/config").CleanedEnvAccessors>;
|
|
11
|
-
export declare const mailer: Mailer;
|
|
12
4
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,MAAM;;kDAKjB,CAAC"}
|
package/dist/config.js
CHANGED
|
@@ -1,58 +1,9 @@
|
|
|
1
|
-
import { createConfig,
|
|
2
|
-
import { createConsoleMailer, createSmtpMailer } from '@shipfox/node-mailer';
|
|
1
|
+
import { createConfig, str } from '@shipfox/config';
|
|
3
2
|
export const config = createConfig({
|
|
4
3
|
CLIENT_BASE_URL: str({
|
|
5
4
|
desc: 'Base URL of the client app. Used to build links in workspace invitation emails.',
|
|
6
5
|
default: 'http://localhost:5173'
|
|
7
|
-
}),
|
|
8
|
-
MAILER_TRANSPORT: str({
|
|
9
|
-
desc: 'How emails are delivered. Use console to print them to the log, or smtp to send them through an SMTP server.',
|
|
10
|
-
choices: [
|
|
11
|
-
'console',
|
|
12
|
-
'smtp'
|
|
13
|
-
],
|
|
14
|
-
default: 'console'
|
|
15
|
-
}),
|
|
16
|
-
MAILER_FROM: str({
|
|
17
|
-
desc: 'Sender address shown on outgoing emails.',
|
|
18
|
-
default: 'noreply@shipfox.local'
|
|
19
|
-
}),
|
|
20
|
-
SMTP_HOST: str({
|
|
21
|
-
desc: 'Hostname of the SMTP server. Required when MAILER_TRANSPORT is smtp.',
|
|
22
|
-
default: undefined
|
|
23
|
-
}),
|
|
24
|
-
SMTP_PORT: num({
|
|
25
|
-
desc: 'Port of the SMTP server.',
|
|
26
|
-
default: 587
|
|
27
|
-
}),
|
|
28
|
-
SMTP_USER: str({
|
|
29
|
-
desc: 'Username for SMTP authentication. Leave it unset if the server needs no login.',
|
|
30
|
-
default: undefined
|
|
31
|
-
}),
|
|
32
|
-
SMTP_PASSWORD: str({
|
|
33
|
-
desc: 'Password for SMTP authentication. Leave it unset if the server needs no login.',
|
|
34
|
-
default: undefined
|
|
35
6
|
})
|
|
36
7
|
});
|
|
37
|
-
function createMailer() {
|
|
38
|
-
if (config.MAILER_TRANSPORT === 'smtp') {
|
|
39
|
-
if (!config.SMTP_HOST) throw new Error('SMTP_HOST is required when MAILER_TRANSPORT=smtp');
|
|
40
|
-
return createSmtpMailer({
|
|
41
|
-
host: config.SMTP_HOST,
|
|
42
|
-
port: config.SMTP_PORT,
|
|
43
|
-
...config.SMTP_USER ? {
|
|
44
|
-
user: config.SMTP_USER
|
|
45
|
-
} : {},
|
|
46
|
-
...config.SMTP_PASSWORD ? {
|
|
47
|
-
password: config.SMTP_PASSWORD
|
|
48
|
-
} : {},
|
|
49
|
-
from: config.MAILER_FROM
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
return createConsoleMailer({
|
|
53
|
-
from: config.MAILER_FROM
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
export const mailer = createMailer();
|
|
57
8
|
|
|
58
9
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["import {createConfig,
|
|
1
|
+
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["import {createConfig, str} from '@shipfox/config';\n\nexport const config = createConfig({\n CLIENT_BASE_URL: str({\n desc: 'Base URL of the client app. Used to build links in workspace invitation emails.',\n default: 'http://localhost:5173',\n }),\n});\n"],"names":["createConfig","str","config","CLIENT_BASE_URL","desc","default"],"mappings":"AAAA,SAAQA,YAAY,EAAEC,GAAG,QAAO,kBAAkB;AAElD,OAAO,MAAMC,SAASF,aAAa;IACjCG,iBAAiBF,IAAI;QACnBG,MAAM;QACNC,SAAS;IACX;AACF,GAAG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invitation.d.ts","sourceRoot":"","sources":["../../../src/core/entities/invitation.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;IAChB,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB"}
|
|
1
|
+
{"version":3,"file":"invitation.d.ts","sourceRoot":"","sources":["../../../src/core/entities/invitation.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,IAAI,GAAG,IAAI,CAAC;IACxB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/entities/invitation.ts"],"sourcesContent":["export interface Invitation {\n id: string;\n workspaceId: string;\n email: string;\n hashedToken: string;\n expiresAt: Date;\n acceptedAt: Date | null;\n acceptedByUserId: string | null;\n invitedByUserId: string;\n invitedByDisplay: string | null;\n createdAt: Date;\n updatedAt: Date;\n}\n"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"sources":["../../../src/core/entities/invitation.ts"],"sourcesContent":["export interface Invitation {\n id: string;\n workspaceId: string;\n email: string;\n hashedToken: string;\n expiresAt: Date;\n revokedAt: Date | null;\n acceptedAt: Date | null;\n acceptedByUserId: string | null;\n invitedByUserId: string;\n invitedByDisplay: string | null;\n createdAt: Date;\n updatedAt: Date;\n}\n"],"names":[],"mappings":"AAAA,WAaC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { UserContextMembership } from '@shipfox/api-auth-context';
|
|
2
|
-
import { type AcceptInvitationResult } from '#db/invitations.js';
|
|
3
2
|
import type { Invitation } from './entities/invitation.js';
|
|
3
|
+
import type { Membership } from './entities/membership.js';
|
|
4
4
|
export declare function createWorkspaceInvitation(params: {
|
|
5
5
|
workspaceId: string;
|
|
6
6
|
email: string;
|
|
@@ -28,6 +28,11 @@ export type PreviewInvitationResult = {
|
|
|
28
28
|
} | {
|
|
29
29
|
status: 'invalid';
|
|
30
30
|
};
|
|
31
|
+
export interface AcceptedWorkspaceInvitation {
|
|
32
|
+
invitation: Invitation;
|
|
33
|
+
membership: Membership;
|
|
34
|
+
alreadyMember: boolean;
|
|
35
|
+
}
|
|
31
36
|
export declare function previewInvitation(params: {
|
|
32
37
|
token: string;
|
|
33
38
|
}): Promise<PreviewInvitationResult>;
|
|
@@ -36,7 +41,21 @@ export declare function acceptWorkspaceInvitation(params: {
|
|
|
36
41
|
userId: string;
|
|
37
42
|
email: string;
|
|
38
43
|
name?: string | null | undefined;
|
|
39
|
-
}): Promise<
|
|
44
|
+
}): Promise<AcceptedWorkspaceInvitation>;
|
|
45
|
+
export type WorkspaceInvitationReconciliation = {
|
|
46
|
+
status: 'accepted' | 'already_accepted';
|
|
47
|
+
invitation: Invitation;
|
|
48
|
+
membership: Membership;
|
|
49
|
+
alreadyMember: boolean;
|
|
50
|
+
} | {
|
|
51
|
+
status: 'invalid' | 'expired' | 'revoked' | 'consumed_by_another_user' | 'email_mismatch';
|
|
52
|
+
};
|
|
53
|
+
export declare function reconcileWorkspaceInvitationAcceptance(params: {
|
|
54
|
+
token: string;
|
|
55
|
+
userId: string;
|
|
56
|
+
email: string;
|
|
57
|
+
name?: string | null | undefined;
|
|
58
|
+
}): Promise<WorkspaceInvitationReconciliation>;
|
|
40
59
|
export declare function listWorkspaceInvitations(params: {
|
|
41
60
|
workspaceId: string;
|
|
42
61
|
requesterUserId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invitations.d.ts","sourceRoot":"","sources":["../../src/core/invitations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"invitations.d.ts","sourceRoot":"","sources":["../../src/core/invitations.ts"],"names":[],"mappings":"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"}
|
package/dist/core/invitations.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { emailSchema } from '@shipfox/api-common-dto';
|
|
1
2
|
import { generateOpaqueToken, hashOpaqueToken } from '@shipfox/node-tokens';
|
|
2
3
|
import { config } from '#config.js';
|
|
3
|
-
import {
|
|
4
|
+
import { createInvitation, findInvitationById, findInvitationByToken, listOpenInvitationsByWorkspace, reconcileInvitationAcceptance, revokeInvitation } from '#db/invitations.js';
|
|
4
5
|
import { getWorkspaceById } from '#db/workspaces.js';
|
|
5
6
|
import { InvitationEmailMismatchError, InvitationNotFoundError, InvitationWorkspaceMismatchError, TokenAlreadyUsedError, TokenExpiredError, TokenInvalidError } from './errors.js';
|
|
6
7
|
import { requireWorkspaceMembership } from './workspaces.js';
|
|
@@ -13,6 +14,7 @@ function nonBlankOrFallback(value, fallback) {
|
|
|
13
14
|
return trimmed ? trimmed : fallback;
|
|
14
15
|
}
|
|
15
16
|
export async function createWorkspaceInvitation(params) {
|
|
17
|
+
const email = emailSchema.parse(params.email);
|
|
16
18
|
const { workspace } = await requireWorkspaceMembership({
|
|
17
19
|
workspaceId: params.workspaceId,
|
|
18
20
|
userId: params.invitedByUserId,
|
|
@@ -21,7 +23,7 @@ export async function createWorkspaceInvitation(params) {
|
|
|
21
23
|
const rawToken = generateOpaqueToken('invitation');
|
|
22
24
|
const invitation = await createInvitation({
|
|
23
25
|
workspaceId: params.workspaceId,
|
|
24
|
-
email
|
|
26
|
+
email,
|
|
25
27
|
hashedToken: hashOpaqueToken(rawToken),
|
|
26
28
|
expiresAt: daysFromNow(INVITATION_TTL_DAYS),
|
|
27
29
|
invitedByUserId: params.invitedByUserId,
|
|
@@ -55,6 +57,11 @@ export async function previewInvitation(params) {
|
|
|
55
57
|
status: 'invalid'
|
|
56
58
|
};
|
|
57
59
|
}
|
|
60
|
+
if (invitation.revokedAt !== null) {
|
|
61
|
+
return {
|
|
62
|
+
status: 'invalid'
|
|
63
|
+
};
|
|
64
|
+
}
|
|
58
65
|
if (invitation.acceptedAt !== null) {
|
|
59
66
|
return {
|
|
60
67
|
status: 'already_used',
|
|
@@ -78,28 +85,58 @@ export async function previewInvitation(params) {
|
|
|
78
85
|
};
|
|
79
86
|
}
|
|
80
87
|
export async function acceptWorkspaceInvitation(params) {
|
|
81
|
-
const
|
|
82
|
-
token: params.token
|
|
88
|
+
const result = await reconcileWorkspaceInvitationAcceptance({
|
|
89
|
+
token: params.token,
|
|
90
|
+
email: params.email,
|
|
91
|
+
userId: params.userId,
|
|
92
|
+
name: params.name
|
|
83
93
|
});
|
|
84
|
-
if (
|
|
85
|
-
|
|
94
|
+
if (result.status === 'accepted') {
|
|
95
|
+
return {
|
|
96
|
+
invitation: result.invitation,
|
|
97
|
+
membership: result.membership,
|
|
98
|
+
alreadyMember: result.alreadyMember
|
|
99
|
+
};
|
|
86
100
|
}
|
|
87
|
-
if (
|
|
101
|
+
if (result.status === 'already_accepted' || result.status === 'consumed_by_another_user') {
|
|
88
102
|
throw new TokenAlreadyUsedError();
|
|
89
103
|
}
|
|
90
|
-
if (
|
|
104
|
+
if (result.status === 'expired') {
|
|
91
105
|
throw new TokenExpiredError();
|
|
92
106
|
}
|
|
93
|
-
if (
|
|
107
|
+
if (result.status === 'email_mismatch') {
|
|
94
108
|
throw new InvitationEmailMismatchError();
|
|
95
109
|
}
|
|
96
|
-
|
|
110
|
+
throw new TokenInvalidError(result.status === 'revoked' ? 'Invitation token has been revoked' : 'Invitation is invalid');
|
|
111
|
+
}
|
|
112
|
+
export async function reconcileWorkspaceInvitationAcceptance(params) {
|
|
113
|
+
const invitation = await peekInvitationByRawToken({
|
|
114
|
+
token: params.token
|
|
115
|
+
});
|
|
116
|
+
if (!invitation) return {
|
|
117
|
+
status: 'invalid'
|
|
118
|
+
};
|
|
119
|
+
const result = await reconcileInvitationAcceptance({
|
|
97
120
|
invitationId: invitation.id,
|
|
98
121
|
acceptedByUserId: params.userId,
|
|
122
|
+
email: emailSchema.parse(params.email),
|
|
99
123
|
acceptedByUserName: params.name
|
|
100
124
|
});
|
|
101
|
-
if (
|
|
102
|
-
|
|
125
|
+
if (result.status === 'accepted') {
|
|
126
|
+
return {
|
|
127
|
+
status: result.status,
|
|
128
|
+
invitation: result.invitation,
|
|
129
|
+
membership: result.membership,
|
|
130
|
+
alreadyMember: result.alreadyMember
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (result.status === 'already_accepted') {
|
|
134
|
+
return {
|
|
135
|
+
status: result.status,
|
|
136
|
+
invitation: result.invitation,
|
|
137
|
+
membership: result.membership,
|
|
138
|
+
alreadyMember: true
|
|
139
|
+
};
|
|
103
140
|
}
|
|
104
141
|
return result;
|
|
105
142
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/invitations.ts"],"sourcesContent":["import type {UserContextMembership} from '@shipfox/api-auth-context';\nimport {generateOpaqueToken, hashOpaqueToken} from '@shipfox/node-tokens';\nimport {config} from '#config.js';\nimport {\n type AcceptInvitationResult,\n acceptInvitation,\n createInvitation,\n findInvitationById,\n findInvitationByToken,\n listOpenInvitationsByWorkspace,\n revokeInvitation,\n} from '#db/invitations.js';\nimport {getWorkspaceById} from '#db/workspaces.js';\nimport type {Invitation} from './entities/invitation.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 {workspace} = await requireWorkspaceMembership({\n workspaceId: params.workspaceId,\n userId: params.invitedByUserId,\n memberships: params.invitedByMemberships,\n });\n\n const rawToken = generateOpaqueToken('invitation');\n const invitation = await createInvitation({\n workspaceId: params.workspaceId,\n email: params.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 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.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<AcceptInvitationResult> {\n const invitation = await peekInvitationByRawToken({token: params.token});\n if (!invitation) {\n throw new TokenInvalidError('Invitation token is invalid');\n }\n\n if (invitation.acceptedAt !== null) {\n throw new TokenAlreadyUsedError();\n }\n\n if (invitation.expiresAt.getTime() <= Date.now()) {\n throw new TokenExpiredError();\n }\n\n if (params.email !== invitation.email) {\n throw new InvitationEmailMismatchError();\n }\n\n const result = await acceptInvitation({\n invitationId: invitation.id,\n acceptedByUserId: params.userId,\n acceptedByUserName: params.name,\n });\n if (!result) {\n throw new TokenInvalidError('Invitation is no longer valid');\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 });\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 });\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":["generateOpaqueToken","hashOpaqueToken","config","acceptInvitation","createInvitation","findInvitationById","findInvitationByToken","listOpenInvitationsByWorkspace","revokeInvitation","getWorkspaceById","InvitationEmailMismatchError","InvitationNotFoundError","InvitationWorkspaceMismatchError","TokenAlreadyUsedError","TokenExpiredError","TokenInvalidError","requireWorkspaceMembership","INVITATION_TTL_DAYS","daysFromNow","days","Date","now","nonBlankOrFallback","value","fallback","trimmed","trim","createWorkspaceInvitation","params","workspace","workspaceId","userId","invitedByUserId","memberships","invitedByMemberships","rawToken","invitation","email","hashedToken","expiresAt","invitedByDisplay","sendEmail","workspaceName","name","inviterName","inviteLink","CLIENT_BASE_URL","peekInvitationByRawToken","token","previewInvitation","status","acceptedAt","getTime","id","acceptWorkspaceInvitation","result","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 });\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 });\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 });\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","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,5 +1,5 @@
|
|
|
1
1
|
import type { UserContextMembership } from '@shipfox/api-auth-context';
|
|
2
|
-
import type
|
|
2
|
+
import { type WorkspaceRole } from '@shipfox/api-workspaces-dto';
|
|
3
3
|
import { type MembershipWithUser, type MembershipWithWorkspace } from '#db/memberships.js';
|
|
4
4
|
import type { Workspace } from './entities/workspace.js';
|
|
5
5
|
export interface RequireWorkspaceMembershipParams {
|
|
@@ -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,KAAK,
|
|
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,EAEL,KAAK,aAAa,EAEnB,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EAIL,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAE7B,MAAM,oBAAoB,CAAC;AAK5B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,yBAAyB,CAAC;AASvD,MAAM,WAAW,gCAAgC;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;CACnD;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,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,CAuBrB;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAOpF;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,CAQhC;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,CAiBhB"}
|
package/dist/core/workspaces.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { WORKSPACES_WORKSPACE_CREATED } from '@shipfox/api-workspaces-dto';
|
|
2
|
+
import { writeOutboxEvent } from '@shipfox/node-outbox';
|
|
1
3
|
import { db } from '#db/db.js';
|
|
2
4
|
import { findMembership, listMembershipsByUser, listMembershipsByWorkspace, removeMembership } from '#db/memberships.js';
|
|
3
5
|
import { memberships } from '#db/schema/memberships.js';
|
|
6
|
+
import { workspacesOutbox } from '#db/schema/outbox.js';
|
|
4
7
|
import { toWorkspace, workspaces } from '#db/schema/workspaces.js';
|
|
5
8
|
import { getWorkspaceById } from '#db/workspaces.js';
|
|
6
9
|
import { MembershipNotFoundError, MembershipRequiredError, SelfRemovalNotAllowedError, WorkspaceInactiveError, WorkspaceNotFoundError } from './errors.js';
|
|
@@ -35,7 +38,16 @@ export async function createWorkspaceForUser(params) {
|
|
|
35
38
|
userName: params.userName ?? null,
|
|
36
39
|
workspaceId: workspaceRow.id
|
|
37
40
|
});
|
|
38
|
-
|
|
41
|
+
const workspace = toWorkspace(workspaceRow);
|
|
42
|
+
await writeOutboxEvent(tx, workspacesOutbox, {
|
|
43
|
+
type: WORKSPACES_WORKSPACE_CREATED,
|
|
44
|
+
payload: {
|
|
45
|
+
workspaceId: workspace.id,
|
|
46
|
+
name: workspace.name,
|
|
47
|
+
creatorUserId: params.userId
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return workspace;
|
|
39
51
|
});
|
|
40
52
|
}
|
|
41
53
|
export async function getWorkspace(params) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/workspaces.ts"],"sourcesContent":["import type {UserContextMembership} from '@shipfox/api-auth-context';\nimport type
|
|
1
|
+
{"version":3,"sources":["../../src/core/workspaces.ts"],"sourcesContent":["import type {UserContextMembership} from '@shipfox/api-auth-context';\nimport {\n WORKSPACES_WORKSPACE_CREATED,\n type WorkspaceRole,\n type WorkspacesEventMap,\n} from '@shipfox/api-workspaces-dto';\nimport {writeOutboxEvent} from '@shipfox/node-outbox';\nimport {db} from '#db/db.js';\nimport {\n findMembership,\n listMembershipsByUser,\n listMembershipsByWorkspace,\n type MembershipWithUser,\n type MembershipWithWorkspace,\n removeMembership,\n} from '#db/memberships.js';\nimport {memberships} from '#db/schema/memberships.js';\nimport {workspacesOutbox} from '#db/schema/outbox.js';\nimport {toWorkspace, workspaces} from '#db/schema/workspaces.js';\nimport {getWorkspaceById} from '#db/workspaces.js';\nimport type {Workspace} from './entities/workspace.js';\nimport {\n MembershipNotFoundError,\n MembershipRequiredError,\n SelfRemovalNotAllowedError,\n WorkspaceInactiveError,\n WorkspaceNotFoundError,\n} from './errors.js';\n\nexport interface RequireWorkspaceMembershipParams {\n workspaceId: string;\n userId: string;\n memberships: ReadonlyArray<UserContextMembership>;\n}\n\nexport interface RequireWorkspaceMembershipResult {\n workspace: Workspace;\n workspaceId: string;\n userId: string;\n role: WorkspaceRole;\n}\n\nexport async function requireWorkspaceMembership(\n params: RequireWorkspaceMembershipParams,\n): Promise<RequireWorkspaceMembershipResult> {\n const membership = params.memberships.find((m) => m.workspaceId === params.workspaceId);\n if (!membership) {\n throw new MembershipRequiredError(params.workspaceId);\n }\n\n const workspace = await getWorkspaceById(params.workspaceId);\n if (!workspace) {\n throw new WorkspaceNotFoundError(params.workspaceId);\n }\n if (workspace.status !== 'active') {\n throw new WorkspaceInactiveError(params.workspaceId);\n }\n\n return {workspace, workspaceId: workspace.id, userId: params.userId, role: membership.role};\n}\n\nexport async function createWorkspaceForUser(params: {\n name: string;\n userId: string;\n userEmail?: string | undefined;\n userName?: string | null | undefined;\n}): Promise<Workspace> {\n return await db().transaction(async (tx) => {\n const [workspaceRow] = await tx.insert(workspaces).values({name: params.name}).returning();\n if (!workspaceRow) throw new Error('Insert returned no rows');\n await tx.insert(memberships).values({\n userId: params.userId,\n userEmail: params.userEmail ?? `user-${params.userId}@example.local`,\n userName: params.userName ?? null,\n workspaceId: workspaceRow.id,\n });\n const workspace = toWorkspace(workspaceRow);\n\n await writeOutboxEvent<WorkspacesEventMap>(tx, workspacesOutbox, {\n type: WORKSPACES_WORKSPACE_CREATED,\n payload: {\n workspaceId: workspace.id,\n name: workspace.name,\n creatorUserId: params.userId,\n },\n });\n\n return workspace;\n });\n}\n\nexport async function getWorkspace(params: {workspaceId: string}): Promise<Workspace> {\n const workspace = await getWorkspaceById(params.workspaceId);\n if (!workspace) {\n throw new WorkspaceNotFoundError(params.workspaceId);\n }\n\n return workspace;\n}\n\nexport async function listUserWorkspaceMemberships(params: {\n userId: string;\n}): Promise<MembershipWithWorkspace[]> {\n return await listMembershipsByUser({userId: params.userId});\n}\n\nexport async function listWorkspaceMembers(params: {\n workspaceId: string;\n requesterUserId: string;\n requesterMemberships: ReadonlyArray<UserContextMembership>;\n}): Promise<MembershipWithUser[]> {\n await requireWorkspaceMembership({\n workspaceId: params.workspaceId,\n userId: params.requesterUserId,\n memberships: params.requesterMemberships,\n });\n\n return listMembershipsByWorkspace({workspaceId: params.workspaceId});\n}\n\nexport async function removeWorkspaceMember(params: {\n workspaceId: string;\n requesterUserId: string;\n requesterMemberships: ReadonlyArray<UserContextMembership>;\n userId: string;\n}): Promise<void> {\n await requireWorkspaceMembership({\n workspaceId: params.workspaceId,\n userId: params.requesterUserId,\n memberships: params.requesterMemberships,\n });\n\n if (params.userId === params.requesterUserId) {\n throw new SelfRemovalNotAllowedError();\n }\n\n const target = await findMembership({userId: params.userId, workspaceId: params.workspaceId});\n if (!target) {\n throw new MembershipNotFoundError(params.userId, params.workspaceId);\n }\n\n await removeMembership({userId: params.userId, workspaceId: params.workspaceId});\n}\n"],"names":["WORKSPACES_WORKSPACE_CREATED","writeOutboxEvent","db","findMembership","listMembershipsByUser","listMembershipsByWorkspace","removeMembership","memberships","workspacesOutbox","toWorkspace","workspaces","getWorkspaceById","MembershipNotFoundError","MembershipRequiredError","SelfRemovalNotAllowedError","WorkspaceInactiveError","WorkspaceNotFoundError","requireWorkspaceMembership","params","membership","find","m","workspaceId","workspace","status","id","userId","role","createWorkspaceForUser","transaction","tx","workspaceRow","insert","values","name","returning","Error","userEmail","userName","type","payload","creatorUserId","getWorkspace","listUserWorkspaceMemberships","listWorkspaceMembers","requesterUserId","requesterMemberships","removeWorkspaceMember","target"],"mappings":"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"}
|
package/dist/db/db.d.ts
CHANGED
|
@@ -324,6 +324,23 @@ export declare const schema: {
|
|
|
324
324
|
identity: undefined;
|
|
325
325
|
generated: undefined;
|
|
326
326
|
}, {}, {}>;
|
|
327
|
+
revokedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
328
|
+
name: "revoked_at";
|
|
329
|
+
tableName: "invitations";
|
|
330
|
+
dataType: "date";
|
|
331
|
+
columnType: "PgTimestamp";
|
|
332
|
+
data: Date;
|
|
333
|
+
driverParam: string;
|
|
334
|
+
notNull: false;
|
|
335
|
+
hasDefault: false;
|
|
336
|
+
isPrimaryKey: false;
|
|
337
|
+
isAutoincrement: false;
|
|
338
|
+
hasRuntimeDefault: false;
|
|
339
|
+
enumValues: undefined;
|
|
340
|
+
baseColumn: never;
|
|
341
|
+
identity: undefined;
|
|
342
|
+
generated: undefined;
|
|
343
|
+
}, {}, {}>;
|
|
327
344
|
acceptedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
328
345
|
name: "accepted_at";
|
|
329
346
|
tableName: "invitations";
|
|
@@ -949,6 +966,23 @@ export declare function db(): NodePgDatabase<{
|
|
|
949
966
|
identity: undefined;
|
|
950
967
|
generated: undefined;
|
|
951
968
|
}, {}, {}>;
|
|
969
|
+
revokedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
970
|
+
name: "revoked_at";
|
|
971
|
+
tableName: "invitations";
|
|
972
|
+
dataType: "date";
|
|
973
|
+
columnType: "PgTimestamp";
|
|
974
|
+
data: Date;
|
|
975
|
+
driverParam: string;
|
|
976
|
+
notNull: false;
|
|
977
|
+
hasDefault: false;
|
|
978
|
+
isPrimaryKey: false;
|
|
979
|
+
isAutoincrement: false;
|
|
980
|
+
hasRuntimeDefault: false;
|
|
981
|
+
enumValues: undefined;
|
|
982
|
+
baseColumn: never;
|
|
983
|
+
identity: undefined;
|
|
984
|
+
generated: undefined;
|
|
985
|
+
}, {}, {}>;
|
|
952
986
|
acceptedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
953
987
|
name: "accepted_at";
|
|
954
988
|
tableName: "invitations";
|
package/dist/db/db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/db/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAOnE,eAAO,MAAM,MAAM
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/db/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAOnE,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAKlB,CAAC;AAIF,wBAAgB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAGjB;AAED,wBAAgB,OAAO,IAAI,IAAI,CAE9B"}
|
package/dist/db/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { closeDb, db, schema } from './db.js';
|
|
2
|
-
export type {
|
|
3
|
-
export {
|
|
2
|
+
export type { CreateInvitationParams } from './invitations.js';
|
|
3
|
+
export { createInvitation, findInvitationByToken, listOpenInvitationsByWorkspace, revokeInvitation, } from './invitations.js';
|
|
4
4
|
export type { CreateMembershipParams, MembershipWithUser, MembershipWithWorkspace, RemoveMembershipParams, } from './memberships.js';
|
|
5
5
|
export { createMembership, findMembership, listMembershipsByUser, listMembershipsByWorkspace, removeMembership, } from './memberships.js';
|
|
6
6
|
export { workspacesOutbox } from './schema/outbox.js';
|
package/dist/db/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,OAAO,EAAE,EAAE,EAAE,MAAM,EAAC,MAAM,SAAS,CAAC;AAC5C,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,OAAO,EAAE,EAAE,EAAE,MAAM,EAAC,MAAM,SAAS,CAAC;AAC5C,YAAY,EAAC,sBAAsB,EAAC,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,8BAA8B,EAC9B,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,sBAAsB,EACtB,kBAAkB,EAClB,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,qBAAqB,EACrB,0BAA0B,EAC1B,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAC,gBAAgB,EAAC,MAAM,oBAAoB,CAAC;AACpD,YAAY,EACV,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,0BAA0B,EAC1B,eAAe,GAChB,MAAM,iBAAiB,CAAC;AAEzB,eAAO,MAAM,cAAc,QAAoE,CAAC"}
|
package/dist/db/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { dirname, resolve } from 'node:path';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
export { closeDb, db, schema } from './db.js';
|
|
4
|
-
export {
|
|
4
|
+
export { createInvitation, findInvitationByToken, listOpenInvitationsByWorkspace, revokeInvitation } from './invitations.js';
|
|
5
5
|
export { createMembership, findMembership, listMembershipsByUser, listMembershipsByWorkspace, removeMembership } from './memberships.js';
|
|
6
6
|
export { workspacesOutbox } from './schema/outbox.js';
|
|
7
7
|
export { createWorkspace, getWorkspaceById, getWorkspaceServiceMetrics, updateWorkspace } from './workspaces.js';
|
package/dist/db/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/db/index.ts"],"sourcesContent":["import {dirname, resolve} from 'node:path';\nimport {fileURLToPath} from 'node:url';\n\nexport {closeDb, db, schema} from './db.js';\nexport type {
|
|
1
|
+
{"version":3,"sources":["../../src/db/index.ts"],"sourcesContent":["import {dirname, resolve} from 'node:path';\nimport {fileURLToPath} from 'node:url';\n\nexport {closeDb, db, schema} from './db.js';\nexport type {CreateInvitationParams} from './invitations.js';\nexport {\n createInvitation,\n findInvitationByToken,\n listOpenInvitationsByWorkspace,\n revokeInvitation,\n} from './invitations.js';\nexport type {\n CreateMembershipParams,\n MembershipWithUser,\n MembershipWithWorkspace,\n RemoveMembershipParams,\n} from './memberships.js';\nexport {\n createMembership,\n findMembership,\n listMembershipsByUser,\n listMembershipsByWorkspace,\n removeMembership,\n} from './memberships.js';\nexport {workspacesOutbox} from './schema/outbox.js';\nexport type {\n CreateWorkspaceParams,\n UpdateWorkspaceParams,\n WorkspaceServiceMetrics,\n} from './workspaces.js';\nexport {\n createWorkspace,\n getWorkspaceById,\n getWorkspaceServiceMetrics,\n updateWorkspace,\n} from './workspaces.js';\n\nexport const migrationsPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../drizzle');\n"],"names":["dirname","resolve","fileURLToPath","closeDb","db","schema","createInvitation","findInvitationByToken","listOpenInvitationsByWorkspace","revokeInvitation","createMembership","findMembership","listMembershipsByUser","listMembershipsByWorkspace","removeMembership","workspacesOutbox","createWorkspace","getWorkspaceById","getWorkspaceServiceMetrics","updateWorkspace","migrationsPath","url"],"mappings":"AAAA,SAAQA,OAAO,EAAEC,OAAO,QAAO,YAAY;AAC3C,SAAQC,aAAa,QAAO,WAAW;AAEvC,SAAQC,OAAO,EAAEC,EAAE,EAAEC,MAAM,QAAO,UAAU;AAE5C,SACEC,gBAAgB,EAChBC,qBAAqB,EACrBC,8BAA8B,EAC9BC,gBAAgB,QACX,mBAAmB;AAO1B,SACEC,gBAAgB,EAChBC,cAAc,EACdC,qBAAqB,EACrBC,0BAA0B,EAC1BC,gBAAgB,QACX,mBAAmB;AAC1B,SAAQC,gBAAgB,QAAO,qBAAqB;AAMpD,SACEC,eAAe,EACfC,gBAAgB,EAChBC,0BAA0B,EAC1BC,eAAe,QACV,kBAAkB;AAEzB,OAAO,MAAMC,iBAAiBnB,QAAQD,QAAQE,cAAc,YAAYmB,GAAG,IAAI,iBAAiB"}
|