@shipfox/api-workspaces 2.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.
Files changed (284) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-type$colon$emit.log +1 -0
  3. package/.turbo/turbo-type.log +1 -0
  4. package/CHANGELOG.md +97 -0
  5. package/LICENSE +21 -0
  6. package/README.md +87 -0
  7. package/dist/config.d.ts +12 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +58 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/core/entities/index.d.ts +4 -0
  12. package/dist/core/entities/index.d.ts.map +1 -0
  13. package/dist/core/entities/index.js +3 -0
  14. package/dist/core/entities/index.js.map +1 -0
  15. package/dist/core/entities/invitation.d.ts +14 -0
  16. package/dist/core/entities/invitation.d.ts.map +1 -0
  17. package/dist/core/entities/invitation.js +3 -0
  18. package/dist/core/entities/invitation.js.map +1 -0
  19. package/dist/core/entities/membership.d.ts +10 -0
  20. package/dist/core/entities/membership.d.ts.map +1 -0
  21. package/dist/core/entities/membership.js +3 -0
  22. package/dist/core/entities/membership.js.map +1 -0
  23. package/dist/core/entities/workspace.d.ts +10 -0
  24. package/dist/core/entities/workspace.d.ts.map +1 -0
  25. package/dist/core/entities/workspace.js +3 -0
  26. package/dist/core/entities/workspace.js.map +1 -0
  27. package/dist/core/errors.d.ts +52 -0
  28. package/dist/core/errors.d.ts.map +1 -0
  29. package/dist/core/errors.js +104 -0
  30. package/dist/core/errors.js.map +1 -0
  31. package/dist/core/index.d.ts +8 -0
  32. package/dist/core/index.d.ts.map +1 -0
  33. package/dist/core/index.js +6 -0
  34. package/dist/core/index.js.map +1 -0
  35. package/dist/core/invitations.d.ts +51 -0
  36. package/dist/core/invitations.d.ts.map +1 -0
  37. package/dist/core/invitations.js +136 -0
  38. package/dist/core/invitations.js.map +1 -0
  39. package/dist/core/memberships.d.ts +9 -0
  40. package/dist/core/memberships.d.ts.map +1 -0
  41. package/dist/core/memberships.js +13 -0
  42. package/dist/core/memberships.js.map +1 -0
  43. package/dist/core/workspaces.d.ts +40 -0
  44. package/dist/core/workspaces.d.ts.map +1 -0
  45. package/dist/core/workspaces.js +85 -0
  46. package/dist/core/workspaces.js.map +1 -0
  47. package/dist/db/db.d.ts +1253 -0
  48. package/dist/db/db.d.ts.map +1 -0
  49. package/dist/db/db.js +24 -0
  50. package/dist/db/db.js.map +1 -0
  51. package/dist/db/index.d.ts +10 -0
  52. package/dist/db/index.d.ts.map +1 -0
  53. package/dist/db/index.js +10 -0
  54. package/dist/db/index.js.map +1 -0
  55. package/dist/db/invitations.d.ts +47 -0
  56. package/dist/db/invitations.d.ts.map +1 -0
  57. package/dist/db/invitations.js +107 -0
  58. package/dist/db/invitations.js.map +1 -0
  59. package/dist/db/memberships.d.ts +44 -0
  60. package/dist/db/memberships.d.ts.map +1 -0
  61. package/dist/db/memberships.js +75 -0
  62. package/dist/db/memberships.js.map +1 -0
  63. package/dist/db/schema/common.d.ts +2 -0
  64. package/dist/db/schema/common.d.ts.map +1 -0
  65. package/dist/db/schema/common.js +4 -0
  66. package/dist/db/schema/common.js.map +1 -0
  67. package/dist/db/schema/invitations.d.ts +199 -0
  68. package/dist/db/schema/invitations.d.ts.map +1 -0
  69. package/dist/db/schema/invitations.js +47 -0
  70. package/dist/db/schema/invitations.js.map +1 -0
  71. package/dist/db/schema/memberships.d.ts +131 -0
  72. package/dist/db/schema/memberships.d.ts.map +1 -0
  73. package/dist/db/schema/memberships.js +35 -0
  74. package/dist/db/schema/memberships.js.map +1 -0
  75. package/dist/db/schema/outbox.d.ts +195 -0
  76. package/dist/db/schema/outbox.d.ts.map +1 -0
  77. package/dist/db/schema/outbox.js +5 -0
  78. package/dist/db/schema/outbox.js.map +1 -0
  79. package/dist/db/schema/workspaces.d.ts +115 -0
  80. package/dist/db/schema/workspaces.d.ts.map +1 -0
  81. package/dist/db/schema/workspaces.js +32 -0
  82. package/dist/db/schema/workspaces.js.map +1 -0
  83. package/dist/db/workspaces.d.ts +20 -0
  84. package/dist/db/workspaces.d.ts.map +1 -0
  85. package/dist/db/workspaces.js +53 -0
  86. package/dist/db/workspaces.js.map +1 -0
  87. package/dist/index.d.ts +13 -0
  88. package/dist/index.d.ts.map +1 -0
  89. package/dist/index.js +40 -0
  90. package/dist/index.js.map +1 -0
  91. package/dist/metrics/index.d.ts +2 -0
  92. package/dist/metrics/index.d.ts.map +1 -0
  93. package/dist/metrics/index.js +3 -0
  94. package/dist/metrics/index.js.map +1 -0
  95. package/dist/metrics/instance.d.ts +8 -0
  96. package/dist/metrics/instance.d.ts.map +1 -0
  97. package/dist/metrics/instance.js +41 -0
  98. package/dist/metrics/instance.js.map +1 -0
  99. package/dist/metrics/service.d.ts +2 -0
  100. package/dist/metrics/service.d.ts.map +1 -0
  101. package/dist/metrics/service.js +26 -0
  102. package/dist/metrics/service.js.map +1 -0
  103. package/dist/presentation/dto/index.d.ts +4 -0
  104. package/dist/presentation/dto/index.d.ts.map +1 -0
  105. package/dist/presentation/dto/index.js +5 -0
  106. package/dist/presentation/dto/index.js.map +1 -0
  107. package/dist/presentation/dto/invitation.d.ts +4 -0
  108. package/dist/presentation/dto/invitation.d.ts.map +1 -0
  109. package/dist/presentation/dto/invitation.js +15 -0
  110. package/dist/presentation/dto/invitation.js.map +1 -0
  111. package/dist/presentation/dto/membership.d.ts +5 -0
  112. package/dist/presentation/dto/membership.d.ts.map +1 -0
  113. package/dist/presentation/dto/membership.js +23 -0
  114. package/dist/presentation/dto/membership.js.map +1 -0
  115. package/dist/presentation/dto/workspace.d.ts +4 -0
  116. package/dist/presentation/dto/workspace.d.ts.map +1 -0
  117. package/dist/presentation/dto/workspace.js +12 -0
  118. package/dist/presentation/dto/workspace.js.map +1 -0
  119. package/dist/presentation/e2eRoutes/create-invitation.d.ts +2 -0
  120. package/dist/presentation/e2eRoutes/create-invitation.d.ts.map +1 -0
  121. package/dist/presentation/e2eRoutes/create-invitation.js +39 -0
  122. package/dist/presentation/e2eRoutes/create-invitation.js.map +1 -0
  123. package/dist/presentation/e2eRoutes/create-workspace.d.ts +2 -0
  124. package/dist/presentation/e2eRoutes/create-workspace.d.ts.map +1 -0
  125. package/dist/presentation/e2eRoutes/create-workspace.js +27 -0
  126. package/dist/presentation/e2eRoutes/create-workspace.js.map +1 -0
  127. package/dist/presentation/e2eRoutes/index.d.ts +3 -0
  128. package/dist/presentation/e2eRoutes/index.d.ts.map +1 -0
  129. package/dist/presentation/e2eRoutes/index.js +11 -0
  130. package/dist/presentation/e2eRoutes/index.js.map +1 -0
  131. package/dist/presentation/index.d.ts +2 -0
  132. package/dist/presentation/index.d.ts.map +1 -0
  133. package/dist/presentation/index.js +3 -0
  134. package/dist/presentation/index.js.map +1 -0
  135. package/dist/presentation/routes/index.d.ts +3 -0
  136. package/dist/presentation/routes/index.d.ts.map +1 -0
  137. package/dist/presentation/routes/index.js +23 -0
  138. package/dist/presentation/routes/index.js.map +1 -0
  139. package/dist/presentation/routes/invitations/accept.d.ts +2 -0
  140. package/dist/presentation/routes/invitations/accept.d.ts.map +1 -0
  141. package/dist/presentation/routes/invitations/accept.js +67 -0
  142. package/dist/presentation/routes/invitations/accept.js.map +1 -0
  143. package/dist/presentation/routes/invitations/create.d.ts +2 -0
  144. package/dist/presentation/routes/invitations/create.d.ts.map +1 -0
  145. package/dist/presentation/routes/invitations/create.js +66 -0
  146. package/dist/presentation/routes/invitations/create.js.map +1 -0
  147. package/dist/presentation/routes/invitations/index.d.ts +4 -0
  148. package/dist/presentation/routes/invitations/index.d.ts.map +1 -0
  149. package/dist/presentation/routes/invitations/index.js +19 -0
  150. package/dist/presentation/routes/invitations/index.js.map +1 -0
  151. package/dist/presentation/routes/invitations/list.d.ts +2 -0
  152. package/dist/presentation/routes/invitations/list.d.ts.map +1 -0
  153. package/dist/presentation/routes/invitations/list.js +58 -0
  154. package/dist/presentation/routes/invitations/list.js.map +1 -0
  155. package/dist/presentation/routes/invitations/preview.d.ts +2 -0
  156. package/dist/presentation/routes/invitations/preview.d.ts.map +1 -0
  157. package/dist/presentation/routes/invitations/preview.js +47 -0
  158. package/dist/presentation/routes/invitations/preview.js.map +1 -0
  159. package/dist/presentation/routes/invitations/revoke.d.ts +2 -0
  160. package/dist/presentation/routes/invitations/revoke.d.ts.map +1 -0
  161. package/dist/presentation/routes/invitations/revoke.js +66 -0
  162. package/dist/presentation/routes/invitations/revoke.js.map +1 -0
  163. package/dist/presentation/routes/members/index.d.ts +3 -0
  164. package/dist/presentation/routes/members/index.d.ts.map +1 -0
  165. package/dist/presentation/routes/members/index.js +8 -0
  166. package/dist/presentation/routes/members/index.js.map +1 -0
  167. package/dist/presentation/routes/members/list.d.ts +2 -0
  168. package/dist/presentation/routes/members/list.d.ts.map +1 -0
  169. package/dist/presentation/routes/members/list.js +58 -0
  170. package/dist/presentation/routes/members/list.js.map +1 -0
  171. package/dist/presentation/routes/members/remove.d.ts +2 -0
  172. package/dist/presentation/routes/members/remove.d.ts.map +1 -0
  173. package/dist/presentation/routes/members/remove.js +71 -0
  174. package/dist/presentation/routes/members/remove.js.map +1 -0
  175. package/dist/presentation/routes/workspaces/create.d.ts +2 -0
  176. package/dist/presentation/routes/workspaces/create.d.ts.map +1 -0
  177. package/dist/presentation/routes/workspaces/create.js +36 -0
  178. package/dist/presentation/routes/workspaces/create.js.map +1 -0
  179. package/dist/presentation/routes/workspaces/index.d.ts +3 -0
  180. package/dist/presentation/routes/workspaces/index.d.ts.map +1 -0
  181. package/dist/presentation/routes/workspaces/index.js +4 -0
  182. package/dist/presentation/routes/workspaces/index.js.map +1 -0
  183. package/dist/presentation/routes/workspaces/list.d.ts +2 -0
  184. package/dist/presentation/routes/workspaces/list.d.ts.map +1 -0
  185. package/dist/presentation/routes/workspaces/list.js +32 -0
  186. package/dist/presentation/routes/workspaces/list.js.map +1 -0
  187. package/dist/presentation/subscribers/index.d.ts +2 -0
  188. package/dist/presentation/subscribers/index.d.ts.map +1 -0
  189. package/dist/presentation/subscribers/index.js +3 -0
  190. package/dist/presentation/subscribers/index.js.map +1 -0
  191. package/dist/presentation/subscribers/on-invitation-send-requested.d.ts +3 -0
  192. package/dist/presentation/subscribers/on-invitation-send-requested.d.ts.map +1 -0
  193. package/dist/presentation/subscribers/on-invitation-send-requested.js +16 -0
  194. package/dist/presentation/subscribers/on-invitation-send-requested.js.map +1 -0
  195. package/dist/tsconfig.test.tsbuildinfo +1 -0
  196. package/drizzle/0000_initial.sql +9 -0
  197. package/drizzle/0001_memberships_invitations.sql +30 -0
  198. package/drizzle/0002_membership_user_snapshots.sql +40 -0
  199. package/drizzle/0003_outgoing_enchantress.sql +16 -0
  200. package/drizzle/meta/0000_snapshot.json +80 -0
  201. package/drizzle/meta/0001_snapshot.json +705 -0
  202. package/drizzle/meta/0003_snapshot.json +442 -0
  203. package/drizzle/meta/_journal.json +34 -0
  204. package/drizzle.config.ts +7 -0
  205. package/package.json +71 -0
  206. package/src/config.ts +51 -0
  207. package/src/core/entities/index.ts +3 -0
  208. package/src/core/entities/invitation.ts +13 -0
  209. package/src/core/entities/membership.ts +9 -0
  210. package/src/core/entities/workspace.ts +10 -0
  211. package/src/core/errors.ts +118 -0
  212. package/src/core/index.ts +35 -0
  213. package/src/core/invitations.test.ts +251 -0
  214. package/src/core/invitations.ts +191 -0
  215. package/src/core/memberships.test.ts +77 -0
  216. package/src/core/memberships.ts +17 -0
  217. package/src/core/workspaces.test.ts +72 -0
  218. package/src/core/workspaces.ts +126 -0
  219. package/src/db/db.ts +24 -0
  220. package/src/db/index.ts +43 -0
  221. package/src/db/invitations.test.ts +204 -0
  222. package/src/db/invitations.ts +235 -0
  223. package/src/db/memberships.test.ts +107 -0
  224. package/src/db/memberships.ts +139 -0
  225. package/src/db/schema/common.ts +5 -0
  226. package/src/db/schema/invitations.ts +47 -0
  227. package/src/db/schema/memberships.ts +39 -0
  228. package/src/db/schema/outbox.ts +4 -0
  229. package/src/db/schema/workspaces.ts +33 -0
  230. package/src/db/workspaces.test.ts +114 -0
  231. package/src/db/workspaces.ts +81 -0
  232. package/src/index.test.ts +25 -0
  233. package/src/index.ts +43 -0
  234. package/src/metrics/index.ts +1 -0
  235. package/src/metrics/instance.test.ts +74 -0
  236. package/src/metrics/instance.ts +57 -0
  237. package/src/metrics/service.ts +26 -0
  238. package/src/presentation/dto/index.ts +3 -0
  239. package/src/presentation/dto/invitation.ts +16 -0
  240. package/src/presentation/dto/membership.ts +27 -0
  241. package/src/presentation/dto/workspace.ts +13 -0
  242. package/src/presentation/e2eRoutes/create-invitation.ts +44 -0
  243. package/src/presentation/e2eRoutes/create-workspace.ts +30 -0
  244. package/src/presentation/e2eRoutes/index.ts +8 -0
  245. package/src/presentation/index.ts +1 -0
  246. package/src/presentation/routes/index.ts +23 -0
  247. package/src/presentation/routes/invitations/accept.test.ts +154 -0
  248. package/src/presentation/routes/invitations/accept.ts +70 -0
  249. package/src/presentation/routes/invitations/create.test.ts +99 -0
  250. package/src/presentation/routes/invitations/create.ts +60 -0
  251. package/src/presentation/routes/invitations/index.ts +17 -0
  252. package/src/presentation/routes/invitations/list.test.ts +94 -0
  253. package/src/presentation/routes/invitations/list.ts +53 -0
  254. package/src/presentation/routes/invitations/preview.test.ts +140 -0
  255. package/src/presentation/routes/invitations/preview.ts +46 -0
  256. package/src/presentation/routes/invitations/revoke.test.ts +99 -0
  257. package/src/presentation/routes/invitations/revoke.ts +64 -0
  258. package/src/presentation/routes/members/index.ts +5 -0
  259. package/src/presentation/routes/members/list.test.ts +95 -0
  260. package/src/presentation/routes/members/list.ts +53 -0
  261. package/src/presentation/routes/members/remove.test.ts +110 -0
  262. package/src/presentation/routes/members/remove.ts +66 -0
  263. package/src/presentation/routes/workspaces/create.test.ts +87 -0
  264. package/src/presentation/routes/workspaces/create.ts +36 -0
  265. package/src/presentation/routes/workspaces/index.ts +2 -0
  266. package/src/presentation/routes/workspaces/list.test.ts +56 -0
  267. package/src/presentation/routes/workspaces/list.ts +29 -0
  268. package/src/presentation/subscribers/index.ts +1 -0
  269. package/src/presentation/subscribers/invitation-send-requested.test.ts +64 -0
  270. package/src/presentation/subscribers/on-invitation-send-requested.ts +15 -0
  271. package/test/env.ts +8 -0
  272. package/test/factories/index.ts +3 -0
  273. package/test/factories/membership.ts +24 -0
  274. package/test/factories/user.ts +15 -0
  275. package/test/factories/workspace.ts +18 -0
  276. package/test/globalSetup.ts +15 -0
  277. package/test/index.ts +3 -0
  278. package/test/routes.ts +255 -0
  279. package/test/setup.ts +17 -0
  280. package/tsconfig.build.json +9 -0
  281. package/tsconfig.build.tsbuildinfo +1 -0
  282. package/tsconfig.json +3 -0
  283. package/tsconfig.test.json +8 -0
  284. package/vitest.config.ts +11 -0
@@ -0,0 +1,51 @@
1
+ import type { UserContextMembership } from '@shipfox/api-auth-context';
2
+ import { type AcceptInvitationResult } from '#db/invitations.js';
3
+ import type { Invitation } from './entities/invitation.js';
4
+ export declare function createWorkspaceInvitation(params: {
5
+ workspaceId: string;
6
+ email: string;
7
+ invitedByUserId: string;
8
+ invitedByDisplay?: string | null;
9
+ invitedByMemberships: ReadonlyArray<UserContextMembership>;
10
+ }): Promise<Invitation>;
11
+ export declare function peekInvitationByRawToken(params: {
12
+ token: string;
13
+ }): Promise<Invitation | undefined>;
14
+ export type PreviewInvitationResult = {
15
+ status: 'pending';
16
+ workspaceId: string;
17
+ workspaceName: string;
18
+ email: string;
19
+ invitedByDisplay: string | null;
20
+ expiresAt: Date;
21
+ } | {
22
+ status: 'expired';
23
+ workspaceName: string;
24
+ expiresAt: Date;
25
+ } | {
26
+ status: 'already_used';
27
+ workspaceName: string;
28
+ } | {
29
+ status: 'invalid';
30
+ };
31
+ export declare function previewInvitation(params: {
32
+ token: string;
33
+ }): Promise<PreviewInvitationResult>;
34
+ export declare function acceptWorkspaceInvitation(params: {
35
+ token: string;
36
+ userId: string;
37
+ email: string;
38
+ name?: string | null | undefined;
39
+ }): Promise<AcceptInvitationResult>;
40
+ export declare function listWorkspaceInvitations(params: {
41
+ workspaceId: string;
42
+ requesterUserId: string;
43
+ requesterMemberships: ReadonlyArray<UserContextMembership>;
44
+ }): Promise<Invitation[]>;
45
+ export declare function revokeWorkspaceInvitation(params: {
46
+ workspaceId: string;
47
+ requesterUserId: string;
48
+ requesterMemberships: ReadonlyArray<UserContextMembership>;
49
+ invitationId: string;
50
+ }): Promise<void>;
51
+ //# sourceMappingURL=invitations.d.ts.map
@@ -0,0 +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;AAGrE,OAAO,EACL,KAAK,sBAAsB,EAO5B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,0BAA0B,CAAC;AAsBzD,wBAAsB,yBAAyB,CAAC,MAAM,EAAE;IACtD,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,oBAAoB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;CAC5D,GAAG,OAAO,CAAC,UAAU,CAAC,CAuBtB;AAED,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAElC;AAED,MAAM,MAAM,uBAAuB,GAC/B;IACE,MAAM,EAAE,SAAS,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,IAAI,CAAC;CACjB,GACD;IAAC,MAAM,EAAE,SAAS,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,IAAI,CAAA;CAAC,GAC3D;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAC,GAC/C;IAAC,MAAM,EAAE,SAAS,CAAA;CAAC,CAAC;AAExB,wBAAsB,iBAAiB,CAAC,MAAM,EAAE;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAgCjG;AAED,wBAAsB,yBAAyB,CAAC,MAAM,EAAE;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CAClC,GAAG,OAAO,CAAC,sBAAsB,CAAC,CA4BlC;AAED,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACrD,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;CAC5D,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAQxB;AAED,wBAAsB,yBAAyB,CAAC,MAAM,EAAE;IACtD,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE,aAAa,CAAC,qBAAqB,CAAC,CAAC;IAC3D,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBhB"}
@@ -0,0 +1,136 @@
1
+ import { generateOpaqueToken, hashOpaqueToken } from '@shipfox/node-tokens';
2
+ import { config } from '#config.js';
3
+ import { acceptInvitation, createInvitation, findInvitationById, findInvitationByToken, listOpenInvitationsByWorkspace, revokeInvitation } from '#db/invitations.js';
4
+ import { getWorkspaceById } from '#db/workspaces.js';
5
+ import { InvitationEmailMismatchError, InvitationNotFoundError, InvitationWorkspaceMismatchError, TokenAlreadyUsedError, TokenExpiredError, TokenInvalidError } from './errors.js';
6
+ import { requireWorkspaceMembership } from './workspaces.js';
7
+ const INVITATION_TTL_DAYS = 7;
8
+ function daysFromNow(days) {
9
+ return new Date(Date.now() + days * 24 * 60 * 60 * 1000);
10
+ }
11
+ function nonBlankOrFallback(value, fallback) {
12
+ const trimmed = value?.trim();
13
+ return trimmed ? trimmed : fallback;
14
+ }
15
+ export async function createWorkspaceInvitation(params) {
16
+ const { workspace } = await requireWorkspaceMembership({
17
+ workspaceId: params.workspaceId,
18
+ userId: params.invitedByUserId,
19
+ memberships: params.invitedByMemberships
20
+ });
21
+ const rawToken = generateOpaqueToken('invitation');
22
+ const invitation = await createInvitation({
23
+ workspaceId: params.workspaceId,
24
+ email: params.email,
25
+ hashedToken: hashOpaqueToken(rawToken),
26
+ expiresAt: daysFromNow(INVITATION_TTL_DAYS),
27
+ invitedByUserId: params.invitedByUserId,
28
+ invitedByDisplay: params.invitedByDisplay ?? null,
29
+ sendEmail: {
30
+ workspaceName: nonBlankOrFallback(workspace.name, 'your workspace'),
31
+ inviterName: nonBlankOrFallback(params.invitedByDisplay, 'A teammate'),
32
+ inviteLink: `${config.CLIENT_BASE_URL}/invitations/accept?token=${rawToken}`
33
+ }
34
+ });
35
+ return invitation;
36
+ }
37
+ export async function peekInvitationByRawToken(params) {
38
+ return await findInvitationByToken({
39
+ hashedToken: hashOpaqueToken(params.token)
40
+ });
41
+ }
42
+ export async function previewInvitation(params) {
43
+ const invitation = await peekInvitationByRawToken({
44
+ token: params.token
45
+ });
46
+ if (!invitation) {
47
+ return {
48
+ status: 'invalid'
49
+ };
50
+ }
51
+ const workspace = await getWorkspaceById(invitation.workspaceId);
52
+ if (!workspace) {
53
+ // Workspace cascade-deleted — the invitation row is orphaned. Treat as invalid.
54
+ return {
55
+ status: 'invalid'
56
+ };
57
+ }
58
+ if (invitation.acceptedAt !== null) {
59
+ return {
60
+ status: 'already_used',
61
+ workspaceName: workspace.name
62
+ };
63
+ }
64
+ if (invitation.expiresAt.getTime() <= Date.now()) {
65
+ return {
66
+ status: 'expired',
67
+ workspaceName: workspace.name,
68
+ expiresAt: invitation.expiresAt
69
+ };
70
+ }
71
+ return {
72
+ status: 'pending',
73
+ workspaceId: workspace.id,
74
+ workspaceName: workspace.name,
75
+ email: invitation.email,
76
+ invitedByDisplay: invitation.invitedByDisplay,
77
+ expiresAt: invitation.expiresAt
78
+ };
79
+ }
80
+ export async function acceptWorkspaceInvitation(params) {
81
+ const invitation = await peekInvitationByRawToken({
82
+ token: params.token
83
+ });
84
+ if (!invitation) {
85
+ throw new TokenInvalidError('Invitation token is invalid');
86
+ }
87
+ if (invitation.acceptedAt !== null) {
88
+ throw new TokenAlreadyUsedError();
89
+ }
90
+ if (invitation.expiresAt.getTime() <= Date.now()) {
91
+ throw new TokenExpiredError();
92
+ }
93
+ if (params.email !== invitation.email) {
94
+ throw new InvitationEmailMismatchError();
95
+ }
96
+ const result = await acceptInvitation({
97
+ invitationId: invitation.id,
98
+ acceptedByUserId: params.userId,
99
+ acceptedByUserName: params.name
100
+ });
101
+ if (!result) {
102
+ throw new TokenInvalidError('Invitation is no longer valid');
103
+ }
104
+ return result;
105
+ }
106
+ export async function listWorkspaceInvitations(params) {
107
+ await requireWorkspaceMembership({
108
+ workspaceId: params.workspaceId,
109
+ userId: params.requesterUserId,
110
+ memberships: params.requesterMemberships
111
+ });
112
+ return listOpenInvitationsByWorkspace({
113
+ workspaceId: params.workspaceId
114
+ });
115
+ }
116
+ export async function revokeWorkspaceInvitation(params) {
117
+ await requireWorkspaceMembership({
118
+ workspaceId: params.workspaceId,
119
+ userId: params.requesterUserId,
120
+ memberships: params.requesterMemberships
121
+ });
122
+ const invitation = await findInvitationById({
123
+ id: params.invitationId
124
+ });
125
+ if (!invitation) {
126
+ throw new InvitationNotFoundError(params.invitationId);
127
+ }
128
+ if (invitation.workspaceId !== params.workspaceId) {
129
+ throw new InvitationWorkspaceMismatchError();
130
+ }
131
+ await revokeInvitation({
132
+ invitationId: params.invitationId
133
+ });
134
+ }
135
+
136
+ //# sourceMappingURL=invitations.js.map
@@ -0,0 +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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"}
@@ -0,0 +1,9 @@
1
+ import { type EnsureMembershipParams } from '#db/memberships.js';
2
+ import type { Membership } from './entities/membership.js';
3
+ export type { EnsureMembershipParams } from '#db/memberships.js';
4
+ /**
5
+ * Ensures an external identity has a workspace membership without refreshing
6
+ * the profile snapshot stored on an existing membership.
7
+ */
8
+ export declare function ensureMembership(params: EnsureMembershipParams): Promise<Membership>;
9
+ //# sourceMappingURL=memberships.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memberships.d.ts","sourceRoot":"","sources":["../../src/core/memberships.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,sBAAsB,EAE5B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,0BAA0B,CAAC;AAGzD,YAAY,EAAC,sBAAsB,EAAC,MAAM,oBAAoB,CAAC;AAE/D;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,CAG1F"}
@@ -0,0 +1,13 @@
1
+ import { ensureMembership as ensureDbMembership } from '#db/memberships.js';
2
+ import { getWorkspace } from './workspaces.js';
3
+ /**
4
+ * Ensures an external identity has a workspace membership without refreshing
5
+ * the profile snapshot stored on an existing membership.
6
+ */ export async function ensureMembership(params) {
7
+ await getWorkspace({
8
+ workspaceId: params.workspaceId
9
+ });
10
+ return await ensureDbMembership(params);
11
+ }
12
+
13
+ //# sourceMappingURL=memberships.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/memberships.ts"],"sourcesContent":["import {\n type EnsureMembershipParams,\n ensureMembership as ensureDbMembership,\n} from '#db/memberships.js';\nimport type {Membership} from './entities/membership.js';\nimport {getWorkspace} from './workspaces.js';\n\nexport type {EnsureMembershipParams} from '#db/memberships.js';\n\n/**\n * Ensures an external identity has a workspace membership without refreshing\n * the profile snapshot stored on an existing membership.\n */\nexport async function ensureMembership(params: EnsureMembershipParams): Promise<Membership> {\n await getWorkspace({workspaceId: params.workspaceId});\n return await ensureDbMembership(params);\n}\n"],"names":["ensureMembership","ensureDbMembership","getWorkspace","params","workspaceId"],"mappings":"AAAA,SAEEA,oBAAoBC,kBAAkB,QACjC,qBAAqB;AAE5B,SAAQC,YAAY,QAAO,kBAAkB;AAI7C;;;CAGC,GACD,OAAO,eAAeF,iBAAiBG,MAA8B;IACnE,MAAMD,aAAa;QAACE,aAAaD,OAAOC,WAAW;IAAA;IACnD,OAAO,MAAMH,mBAAmBE;AAClC"}
@@ -0,0 +1,40 @@
1
+ import type { UserContextMembership } from '@shipfox/api-auth-context';
2
+ import type { WorkspaceRole } from '@shipfox/api-workspaces-dto';
3
+ import { type MembershipWithUser, type MembershipWithWorkspace } from '#db/memberships.js';
4
+ import type { Workspace } from './entities/workspace.js';
5
+ export interface RequireWorkspaceMembershipParams {
6
+ workspaceId: string;
7
+ userId: string;
8
+ memberships: ReadonlyArray<UserContextMembership>;
9
+ }
10
+ export interface RequireWorkspaceMembershipResult {
11
+ workspace: Workspace;
12
+ workspaceId: string;
13
+ userId: string;
14
+ role: WorkspaceRole;
15
+ }
16
+ export declare function requireWorkspaceMembership(params: RequireWorkspaceMembershipParams): Promise<RequireWorkspaceMembershipResult>;
17
+ export declare function createWorkspaceForUser(params: {
18
+ name: string;
19
+ userId: string;
20
+ userEmail?: string | undefined;
21
+ userName?: string | null | undefined;
22
+ }): Promise<Workspace>;
23
+ export declare function getWorkspace(params: {
24
+ workspaceId: string;
25
+ }): Promise<Workspace>;
26
+ export declare function listUserWorkspaceMemberships(params: {
27
+ userId: string;
28
+ }): Promise<MembershipWithWorkspace[]>;
29
+ export declare function listWorkspaceMembers(params: {
30
+ workspaceId: string;
31
+ requesterUserId: string;
32
+ requesterMemberships: ReadonlyArray<UserContextMembership>;
33
+ }): Promise<MembershipWithUser[]>;
34
+ export declare function removeWorkspaceMember(params: {
35
+ workspaceId: string;
36
+ requesterUserId: string;
37
+ requesterMemberships: ReadonlyArray<UserContextMembership>;
38
+ userId: string;
39
+ }): Promise<void>;
40
+ //# sourceMappingURL=workspaces.d.ts.map
@@ -0,0 +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,EAAC,aAAa,EAAC,MAAM,6BAA6B,CAAC;AAE/D,OAAO,EAIL,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAE7B,MAAM,oBAAoB,CAAC;AAI5B,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,CAYrB;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"}
@@ -0,0 +1,85 @@
1
+ import { db } from '#db/db.js';
2
+ import { findMembership, listMembershipsByUser, listMembershipsByWorkspace, removeMembership } from '#db/memberships.js';
3
+ import { memberships } from '#db/schema/memberships.js';
4
+ import { toWorkspace, workspaces } from '#db/schema/workspaces.js';
5
+ import { getWorkspaceById } from '#db/workspaces.js';
6
+ import { MembershipNotFoundError, MembershipRequiredError, SelfRemovalNotAllowedError, WorkspaceInactiveError, WorkspaceNotFoundError } from './errors.js';
7
+ export async function requireWorkspaceMembership(params) {
8
+ const membership = params.memberships.find((m)=>m.workspaceId === params.workspaceId);
9
+ if (!membership) {
10
+ throw new MembershipRequiredError(params.workspaceId);
11
+ }
12
+ const workspace = await getWorkspaceById(params.workspaceId);
13
+ if (!workspace) {
14
+ throw new WorkspaceNotFoundError(params.workspaceId);
15
+ }
16
+ if (workspace.status !== 'active') {
17
+ throw new WorkspaceInactiveError(params.workspaceId);
18
+ }
19
+ return {
20
+ workspace,
21
+ workspaceId: workspace.id,
22
+ userId: params.userId,
23
+ role: membership.role
24
+ };
25
+ }
26
+ export async function createWorkspaceForUser(params) {
27
+ return await db().transaction(async (tx)=>{
28
+ const [workspaceRow] = await tx.insert(workspaces).values({
29
+ name: params.name
30
+ }).returning();
31
+ if (!workspaceRow) throw new Error('Insert returned no rows');
32
+ await tx.insert(memberships).values({
33
+ userId: params.userId,
34
+ userEmail: params.userEmail ?? `user-${params.userId}@example.local`,
35
+ userName: params.userName ?? null,
36
+ workspaceId: workspaceRow.id
37
+ });
38
+ return toWorkspace(workspaceRow);
39
+ });
40
+ }
41
+ export async function getWorkspace(params) {
42
+ const workspace = await getWorkspaceById(params.workspaceId);
43
+ if (!workspace) {
44
+ throw new WorkspaceNotFoundError(params.workspaceId);
45
+ }
46
+ return workspace;
47
+ }
48
+ export async function listUserWorkspaceMemberships(params) {
49
+ return await listMembershipsByUser({
50
+ userId: params.userId
51
+ });
52
+ }
53
+ export async function listWorkspaceMembers(params) {
54
+ await requireWorkspaceMembership({
55
+ workspaceId: params.workspaceId,
56
+ userId: params.requesterUserId,
57
+ memberships: params.requesterMemberships
58
+ });
59
+ return listMembershipsByWorkspace({
60
+ workspaceId: params.workspaceId
61
+ });
62
+ }
63
+ export async function removeWorkspaceMember(params) {
64
+ await requireWorkspaceMembership({
65
+ workspaceId: params.workspaceId,
66
+ userId: params.requesterUserId,
67
+ memberships: params.requesterMemberships
68
+ });
69
+ if (params.userId === params.requesterUserId) {
70
+ throw new SelfRemovalNotAllowedError();
71
+ }
72
+ const target = await findMembership({
73
+ userId: params.userId,
74
+ workspaceId: params.workspaceId
75
+ });
76
+ if (!target) {
77
+ throw new MembershipNotFoundError(params.userId, params.workspaceId);
78
+ }
79
+ await removeMembership({
80
+ userId: params.userId,
81
+ workspaceId: params.workspaceId
82
+ });
83
+ }
84
+
85
+ //# sourceMappingURL=workspaces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/core/workspaces.ts"],"sourcesContent":["import type {UserContextMembership} from '@shipfox/api-auth-context';\nimport type {WorkspaceRole} from '@shipfox/api-workspaces-dto';\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 {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 return toWorkspace(workspaceRow);\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":["db","findMembership","listMembershipsByUser","listMembershipsByWorkspace","removeMembership","memberships","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","getWorkspace","listUserWorkspaceMemberships","listWorkspaceMembers","requesterUserId","requesterMemberships","removeWorkspaceMember","target"],"mappings":"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"}