alepha 0.20.1 → 0.20.2

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 (232) hide show
  1. package/dist/api/files/index.js +2 -1
  2. package/dist/api/files/index.js.map +1 -1
  3. package/dist/api/jobs/index.browser.js +64 -148
  4. package/dist/api/jobs/index.browser.js.map +1 -1
  5. package/dist/api/jobs/index.d.ts +371 -573
  6. package/dist/api/jobs/index.d.ts.map +1 -1
  7. package/dist/api/jobs/index.js +605 -1012
  8. package/dist/api/jobs/index.js.map +1 -1
  9. package/dist/api/notifications/index.d.ts +78 -17
  10. package/dist/api/notifications/index.d.ts.map +1 -1
  11. package/dist/api/notifications/index.js +90 -23
  12. package/dist/api/notifications/index.js.map +1 -1
  13. package/dist/api/payments/index.d.ts +2 -1
  14. package/dist/api/payments/index.d.ts.map +1 -1
  15. package/dist/api/payments/index.js +4 -2
  16. package/dist/api/payments/index.js.map +1 -1
  17. package/dist/api/users/index.d.ts +34 -31
  18. package/dist/api/users/index.d.ts.map +1 -1
  19. package/dist/api/users/index.js +13 -7
  20. package/dist/api/users/index.js.map +1 -1
  21. package/dist/api/verifications/index.js +2 -1
  22. package/dist/api/verifications/index.js.map +1 -1
  23. package/dist/cli/core/index.d.ts +8 -34
  24. package/dist/cli/core/index.d.ts.map +1 -1
  25. package/dist/cli/core/index.js +43 -232
  26. package/dist/cli/core/index.js.map +1 -1
  27. package/dist/cli/platform/index.d.ts +36 -11
  28. package/dist/cli/platform/index.d.ts.map +1 -1
  29. package/dist/cli/platform/index.js +93 -27
  30. package/dist/cli/platform/index.js.map +1 -1
  31. package/dist/command/index.d.ts +1 -1
  32. package/dist/core/index.browser.js +6 -0
  33. package/dist/core/index.browser.js.map +1 -1
  34. package/dist/core/index.d.ts +6 -0
  35. package/dist/core/index.d.ts.map +1 -1
  36. package/dist/core/index.js +6 -0
  37. package/dist/core/index.js.map +1 -1
  38. package/dist/core/index.native.js +6 -0
  39. package/dist/core/index.native.js.map +1 -1
  40. package/dist/core/index.workerd.js +6 -0
  41. package/dist/core/index.workerd.js.map +1 -1
  42. package/dist/react/form/index.d.ts +60 -1
  43. package/dist/react/form/index.d.ts.map +1 -1
  44. package/dist/react/form/index.js +86 -1
  45. package/dist/react/form/index.js.map +1 -1
  46. package/dist/react/head/index.browser.js +16 -1
  47. package/dist/react/head/index.browser.js.map +1 -1
  48. package/dist/react/head/index.d.ts +6 -0
  49. package/dist/react/head/index.d.ts.map +1 -1
  50. package/dist/react/head/index.js +16 -1
  51. package/dist/react/head/index.js.map +1 -1
  52. package/dist/react/router/index.browser.js +0 -10
  53. package/dist/react/router/index.browser.js.map +1 -1
  54. package/dist/react/router/index.d.ts +35 -12
  55. package/dist/react/router/index.d.ts.map +1 -1
  56. package/dist/react/router/index.js +0 -10
  57. package/dist/react/router/index.js.map +1 -1
  58. package/dist/react/ui/index.d.ts +124 -0
  59. package/dist/react/ui/index.d.ts.map +1 -0
  60. package/dist/react/ui/index.js +206 -0
  61. package/dist/react/ui/index.js.map +1 -0
  62. package/dist/router/index.d.ts +13 -13
  63. package/dist/router/index.d.ts.map +1 -1
  64. package/dist/router/index.js +45 -32
  65. package/dist/router/index.js.map +1 -1
  66. package/dist/system/index.d.ts.map +1 -1
  67. package/dist/system/index.js +1 -0
  68. package/dist/system/index.js.map +1 -1
  69. package/dist/topic/core/index.js +1 -1
  70. package/dist/topic/core/index.js.map +1 -1
  71. package/package.json +6 -23
  72. package/src/api/files/jobs/FileJobs.ts +2 -1
  73. package/src/api/jobs/__tests__/$job.spec.ts +316 -2867
  74. package/src/api/jobs/controllers/AdminJobController.ts +29 -138
  75. package/src/api/jobs/entities/jobExecutionEntity.ts +27 -19
  76. package/src/api/jobs/index.browser.ts +5 -7
  77. package/src/api/jobs/index.ts +23 -51
  78. package/src/api/jobs/primitives/$job.ts +66 -58
  79. package/src/api/jobs/providers/JobProvider.ts +561 -566
  80. package/src/api/jobs/providers/JobQueueProvider.ts +18 -19
  81. package/src/api/jobs/schemas/jobConfigAtom.ts +20 -23
  82. package/src/api/jobs/schemas/jobExecutionQuerySchema.ts +3 -27
  83. package/src/api/jobs/schemas/jobExecutionResourceSchema.ts +5 -7
  84. package/src/api/jobs/schemas/jobRegistrationSchema.ts +7 -4
  85. package/src/api/jobs/schemas/triggerJobSchema.ts +0 -1
  86. package/src/api/jobs/services/JobService.ts +90 -483
  87. package/src/api/notifications/controllers/AdminNotificationController.ts +19 -12
  88. package/src/api/notifications/index.ts +7 -4
  89. package/src/api/notifications/jobs/NotificationJobs.ts +83 -12
  90. package/src/api/payments/services/PaymentService.ts +4 -2
  91. package/src/api/users/__tests__/UserJobs.spec.ts +10 -49
  92. package/src/api/users/audits/UserAudits.ts +3 -1
  93. package/src/api/users/buckets/UserBuckets.ts +2 -1
  94. package/src/api/users/index.ts +1 -4
  95. package/src/api/users/jobs/UserJobs.ts +5 -4
  96. package/src/api/verifications/jobs/VerificationJobs.ts +2 -1
  97. package/src/cli/core/__tests__/init.spec.ts +1 -1
  98. package/src/cli/core/commands/init.ts +0 -12
  99. package/src/cli/core/services/PackageManagerUtils.ts +2 -9
  100. package/src/cli/core/services/ProjectScaffolder.ts +17 -65
  101. package/src/cli/core/templates/agentMd.ts +2 -8
  102. package/src/cli/core/templates/apiIndexTs.ts +4 -18
  103. package/src/cli/core/templates/mainCss.ts +1 -36
  104. package/src/cli/core/templates/vitestConfigTs.ts +17 -0
  105. package/src/cli/core/templates/webAppRouterTs.ts +2 -85
  106. package/src/cli/platform/__tests__/CloudflareAdapter.spec.ts +22 -71
  107. package/src/cli/platform/adapters/CloudflareAdapter.ts +12 -11
  108. package/src/cli/platform/atoms/platformOptions.ts +9 -0
  109. package/src/cli/platform/schemas/cloudflare.ts +3 -2
  110. package/src/cli/platform/services/CloudflareApi.ts +164 -25
  111. package/src/cli/platform/services/WranglerApi.ts +0 -17
  112. package/src/core/Alepha.ts +9 -0
  113. package/src/react/form/index.ts +2 -0
  114. package/src/react/form/services/parseField.ts +163 -0
  115. package/src/react/form/services/prettyName.ts +19 -0
  116. package/src/react/head/providers/BrowserHeadProvider.ts +31 -10
  117. package/src/react/router/primitives/$page.ts +35 -12
  118. package/src/react/ui/atoms/uiAtom.ts +28 -0
  119. package/src/react/ui/components/ColorScheme.tsx +36 -0
  120. package/src/react/ui/hooks/useColorMode.ts +49 -0
  121. package/src/react/ui/hooks/useSidebarState.ts +26 -0
  122. package/src/react/ui/hooks/useTheme.ts +22 -0
  123. package/src/react/ui/index.ts +35 -0
  124. package/src/react/ui/services/UiPersistence.ts +41 -0
  125. package/src/router/TemplatedPathParser.ts +50 -51
  126. package/src/router/__tests__/RouterProvider.spec.ts +62 -0
  127. package/src/router/__tests__/TemplatedPathParser.spec.ts +18 -0
  128. package/src/router/providers/RouterProvider.ts +10 -5
  129. package/src/system/providers/NodeShellProvider.ts +1 -0
  130. package/src/topic/core/providers/TopicProvider.ts +1 -1
  131. package/dist/api/invitations/index.d.ts +0 -790
  132. package/dist/api/invitations/index.d.ts.map +0 -1
  133. package/dist/api/invitations/index.js +0 -662
  134. package/dist/api/invitations/index.js.map +0 -1
  135. package/dist/api/issues/index.d.ts +0 -810
  136. package/dist/api/issues/index.d.ts.map +0 -1
  137. package/dist/api/issues/index.js +0 -444
  138. package/dist/api/issues/index.js.map +0 -1
  139. package/dist/api/subscriptions/index.d.ts +0 -1692
  140. package/dist/api/subscriptions/index.d.ts.map +0 -1
  141. package/dist/api/subscriptions/index.js +0 -1867
  142. package/dist/api/subscriptions/index.js.map +0 -1
  143. package/dist/api/workflows/index.browser.js +0 -246
  144. package/dist/api/workflows/index.browser.js.map +0 -1
  145. package/dist/api/workflows/index.d.ts +0 -1618
  146. package/dist/api/workflows/index.d.ts.map +0 -1
  147. package/dist/api/workflows/index.js +0 -1495
  148. package/dist/api/workflows/index.js.map +0 -1
  149. package/src/api/invitations/__tests__/InvitationService.spec.ts +0 -439
  150. package/src/api/invitations/controllers/AdminInvitationController.ts +0 -86
  151. package/src/api/invitations/controllers/InvitationController.ts +0 -84
  152. package/src/api/invitations/entities/invitations.ts +0 -33
  153. package/src/api/invitations/index.ts +0 -58
  154. package/src/api/invitations/jobs/InvitationJobs.ts +0 -37
  155. package/src/api/invitations/providers/InvitationProvider.ts +0 -45
  156. package/src/api/invitations/schemas/createInvitationSchema.ts +0 -12
  157. package/src/api/invitations/schemas/invitationConfigAtom.ts +0 -20
  158. package/src/api/invitations/schemas/invitationQuerySchema.ts +0 -15
  159. package/src/api/invitations/schemas/invitationResourceSchema.ts +0 -6
  160. package/src/api/invitations/schemas/invitationWithResourceInfoSchema.ts +0 -22
  161. package/src/api/invitations/schemas/myInvitationsQuerySchema.ts +0 -10
  162. package/src/api/invitations/services/InvitationService.ts +0 -556
  163. package/src/api/issues/__tests__/IssueService.spec.ts +0 -263
  164. package/src/api/issues/controllers/AdminIssueController.ts +0 -149
  165. package/src/api/issues/controllers/IssueController.ts +0 -44
  166. package/src/api/issues/entities/issues.ts +0 -49
  167. package/src/api/issues/index.ts +0 -50
  168. package/src/api/issues/schemas/createIssueSchema.ts +0 -13
  169. package/src/api/issues/schemas/issueConfigAtom.ts +0 -13
  170. package/src/api/issues/schemas/issueQuerySchema.ts +0 -18
  171. package/src/api/issues/schemas/issueResourceSchema.ts +0 -6
  172. package/src/api/issues/schemas/myIssueQuerySchema.ts +0 -10
  173. package/src/api/issues/schemas/updateIssueSchema.ts +0 -13
  174. package/src/api/issues/services/IssueService.ts +0 -264
  175. package/src/api/jobs/__tests__/$job-middleware.spec.ts +0 -126
  176. package/src/api/jobs/__tests__/JobService.spec.ts +0 -31
  177. package/src/api/jobs/entities/jobExecutionLogEntity.ts +0 -13
  178. package/src/api/jobs/schemas/jobActivitySchema.ts +0 -15
  179. package/src/api/jobs/schemas/jobCronInfoSchema.ts +0 -22
  180. package/src/api/jobs/schemas/jobExecutionDetailResourceSchema.ts +0 -20
  181. package/src/api/jobs/schemas/jobFailureSchema.ts +0 -9
  182. package/src/api/jobs/schemas/jobQueueDepthSchema.ts +0 -14
  183. package/src/api/jobs/schemas/jobStatsSchema.ts +0 -14
  184. package/src/api/jobs/services/JobService-tests.ts +0 -157
  185. package/src/api/subscriptions/__tests__/BillingService.spec.ts +0 -218
  186. package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +0 -278
  187. package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +0 -212
  188. package/src/api/subscriptions/controllers/SubscriptionController.ts +0 -189
  189. package/src/api/subscriptions/entities/subscriptionEvents.ts +0 -54
  190. package/src/api/subscriptions/entities/subscriptions.ts +0 -68
  191. package/src/api/subscriptions/index.ts +0 -133
  192. package/src/api/subscriptions/jobs/SubscriptionJobs.ts +0 -382
  193. package/src/api/subscriptions/middleware/$requireLimit.ts +0 -50
  194. package/src/api/subscriptions/middleware/$requirePlan.ts +0 -49
  195. package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +0 -110
  196. package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +0 -8
  197. package/src/api/subscriptions/schemas/changePlanSchema.ts +0 -9
  198. package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +0 -11
  199. package/src/api/subscriptions/schemas/entitlementsSchema.ts +0 -21
  200. package/src/api/subscriptions/schemas/mrrSchema.ts +0 -13
  201. package/src/api/subscriptions/schemas/planDefinitionSchema.ts +0 -71
  202. package/src/api/subscriptions/schemas/planResourceSchema.ts +0 -25
  203. package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +0 -8
  204. package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +0 -19
  205. package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +0 -6
  206. package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +0 -32
  207. package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +0 -23
  208. package/src/api/subscriptions/services/BillingService.ts +0 -437
  209. package/src/api/subscriptions/services/SubscriptionConfig.ts +0 -56
  210. package/src/api/subscriptions/services/SubscriptionService.ts +0 -867
  211. package/src/api/subscriptions/services/UsageService.ts +0 -118
  212. package/src/api/workflows/__tests__/$workflow.spec.ts +0 -616
  213. package/src/api/workflows/controllers/AdminWorkflowController.ts +0 -191
  214. package/src/api/workflows/entities/workflowExecutions.ts +0 -74
  215. package/src/api/workflows/entities/workflowStepExecutions.ts +0 -74
  216. package/src/api/workflows/entities/workflowStepLogs.ts +0 -13
  217. package/src/api/workflows/index.browser.ts +0 -22
  218. package/src/api/workflows/index.ts +0 -115
  219. package/src/api/workflows/jobs/WorkflowJobs.ts +0 -77
  220. package/src/api/workflows/primitives/$workflow.ts +0 -202
  221. package/src/api/workflows/providers/WorkflowProvider.ts +0 -1284
  222. package/src/api/workflows/schemas/workflowActivitySchema.ts +0 -15
  223. package/src/api/workflows/schemas/workflowConfigAtom.ts +0 -51
  224. package/src/api/workflows/schemas/workflowExecutionDetailSchema.ts +0 -18
  225. package/src/api/workflows/schemas/workflowExecutionQuerySchema.ts +0 -26
  226. package/src/api/workflows/schemas/workflowExecutionResourceSchema.ts +0 -30
  227. package/src/api/workflows/schemas/workflowRegistrationSchema.ts +0 -26
  228. package/src/api/workflows/schemas/workflowStatsSchema.ts +0 -16
  229. package/src/api/workflows/schemas/workflowStepExecutionResourceSchema.ts +0 -15
  230. package/src/api/workflows/services/WorkflowService.ts +0 -382
  231. package/src/cli/core/templates/apiAppSecurityTs.ts +0 -43
  232. package/src/cli/core/templates/webAdminDashboardTsx.ts +0 -17
@@ -1,439 +0,0 @@
1
- import { Alepha } from "alepha";
2
- import { users } from "alepha/api/users";
3
- import { $repository } from "alepha/orm";
4
- import { AlephaOrmPostgres } from "alepha/orm/postgres";
5
- import { BadRequestError, ForbiddenError } from "alepha/server";
6
- import { describe, it } from "vitest";
7
- import type { InvitationEntity } from "../entities/invitations.ts";
8
- import { AlephaApiInvitations } from "../index.ts";
9
- import { InvitationProvider } from "../providers/InvitationProvider.ts";
10
- import { InvitationService } from "../services/InvitationService.ts";
11
-
12
- class TestInvitationProvider extends InvitationProvider {
13
- protected members = new Map<string, Set<string>>();
14
-
15
- async validateResource(
16
- _resourceType: string,
17
- _resourceId: string,
18
- _inviter: { id: string; email?: string },
19
- ): Promise<void> {
20
- // Always succeeds
21
- }
22
-
23
- async isMember(
24
- resourceType: string,
25
- resourceId: string,
26
- _email: string,
27
- userId?: string,
28
- ): Promise<boolean> {
29
- if (!userId) {
30
- return false;
31
- }
32
-
33
- const key = `${resourceType}:${resourceId}`;
34
- return this.members.get(key)?.has(userId) ?? false;
35
- }
36
-
37
- async onAccept(
38
- invitation: InvitationEntity,
39
- acceptedBy: { id: string; email?: string },
40
- ): Promise<void> {
41
- const key = `${invitation.resourceType}:${invitation.resourceId}`;
42
-
43
- if (!this.members.has(key)) {
44
- this.members.set(key, new Set());
45
- }
46
-
47
- this.members.get(key)!.add(acceptedBy.id);
48
- }
49
-
50
- async getResourceInfo(
51
- _resourceType: string,
52
- _resourceId: string,
53
- ): Promise<{ name: string; description?: string; url?: string }> {
54
- return { name: "Test Project" };
55
- }
56
-
57
- addMember(resourceType: string, resourceId: string, userId: string): void {
58
- const key = `${resourceType}:${resourceId}`;
59
-
60
- if (!this.members.has(key)) {
61
- this.members.set(key, new Set());
62
- }
63
-
64
- this.members.get(key)!.add(userId);
65
- }
66
- }
67
-
68
- class TestRepositories {
69
- users = $repository(users);
70
- }
71
-
72
- const setup = async () => {
73
- const alepha = Alepha.create()
74
- .with(AlephaOrmPostgres)
75
- .with({ provide: InvitationProvider, use: TestInvitationProvider })
76
- .with(AlephaApiInvitations);
77
-
78
- const service = alepha.inject(InvitationService);
79
- const provider = alepha.inject(
80
- TestInvitationProvider,
81
- ) as TestInvitationProvider;
82
- const repos = alepha.inject(TestRepositories);
83
- await alepha.start();
84
-
85
- const createUser = async (email: string) => {
86
- return repos.users.create({ email, roles: [] });
87
- };
88
-
89
- return { alepha, service, provider, createUser };
90
- };
91
-
92
- describe("InvitationService", () => {
93
- // -----------------------------------------------------------------------------------------------------------------
94
-
95
- describe("create", () => {
96
- it("should create a pending invitation", async ({ expect }) => {
97
- const { service, createUser } = await setup();
98
-
99
- const inviter = await createUser("inviter@example.com");
100
-
101
- const invitation = await service.create(
102
- {
103
- email: "invitee@example.com",
104
- resourceType: "project",
105
- resourceId: "proj-1",
106
- },
107
- { id: inviter.id, email: inviter.email },
108
- );
109
-
110
- expect(invitation.status).toBe("pending");
111
- expect(invitation.email).toBe("invitee@example.com");
112
- expect(invitation.resourceType).toBe("project");
113
- expect(invitation.resourceId).toBe("proj-1");
114
- expect(invitation.invitedBy).toBe(inviter.id);
115
- expect(invitation.token).toBeDefined();
116
- expect(invitation.expiresAt).toBeDefined();
117
- });
118
-
119
- it("should reject self-invite", async ({ expect }) => {
120
- const { service, createUser } = await setup();
121
-
122
- const inviter = await createUser("self@example.com");
123
-
124
- await expect(
125
- service.create(
126
- {
127
- email: "self@example.com",
128
- resourceType: "project",
129
- resourceId: "proj-1",
130
- },
131
- { id: inviter.id, email: inviter.email },
132
- ),
133
- ).rejects.toThrow(BadRequestError);
134
- });
135
-
136
- it("should reject duplicate pending invitation", async ({ expect }) => {
137
- const { service, createUser } = await setup();
138
-
139
- const inviter = await createUser("inviter@example.com");
140
-
141
- await service.create(
142
- {
143
- email: "dup@example.com",
144
- resourceType: "project",
145
- resourceId: "proj-1",
146
- },
147
- { id: inviter.id, email: inviter.email },
148
- );
149
-
150
- await expect(
151
- service.create(
152
- {
153
- email: "dup@example.com",
154
- resourceType: "project",
155
- resourceId: "proj-1",
156
- },
157
- { id: inviter.id, email: inviter.email },
158
- ),
159
- ).rejects.toThrow(BadRequestError);
160
- });
161
-
162
- it("should reject when already a member", async ({ expect }) => {
163
- const { service, provider, createUser } = await setup();
164
-
165
- const inviter = await createUser("inviter@example.com");
166
- const existingMember = await createUser("member@example.com");
167
-
168
- provider.addMember("project", "proj-1", existingMember.id);
169
-
170
- await expect(
171
- service.create(
172
- {
173
- email: "member@example.com",
174
- resourceType: "project",
175
- resourceId: "proj-1",
176
- },
177
- { id: inviter.id, email: inviter.email },
178
- ),
179
- ).rejects.toThrow(BadRequestError);
180
- });
181
- });
182
-
183
- // -----------------------------------------------------------------------------------------------------------------
184
-
185
- describe("accept", () => {
186
- it("should accept invitation and call onAccept", async ({ expect }) => {
187
- const { service, provider, createUser } = await setup();
188
-
189
- const inviter = await createUser("inviter@example.com");
190
- const invitee = await createUser("invitee@example.com");
191
-
192
- const invitation = await service.create(
193
- {
194
- email: "invitee@example.com",
195
- resourceType: "project",
196
- resourceId: "proj-1",
197
- },
198
- { id: inviter.id, email: inviter.email },
199
- );
200
-
201
- await service.accept(invitation.id, {
202
- id: invitee.id,
203
- email: invitee.email,
204
- });
205
-
206
- const updated = await service.getById(invitation.id);
207
- expect(updated.status).toBe("accepted");
208
- expect(updated.resolvedBy).toBe(invitee.id);
209
- expect(updated.resolvedAt).toBeDefined();
210
-
211
- const isMember = await provider.isMember(
212
- "project",
213
- "proj-1",
214
- "invitee@example.com",
215
- invitee.id,
216
- );
217
- expect(isMember).toBe(true);
218
- });
219
-
220
- it("should reject accept for wrong email", async ({ expect }) => {
221
- const { service, createUser } = await setup();
222
-
223
- const inviter = await createUser("inviter@example.com");
224
- const wrongUser = await createUser("wrong@example.com");
225
-
226
- const invitation = await service.create(
227
- {
228
- email: "invitee@example.com",
229
- resourceType: "project",
230
- resourceId: "proj-1",
231
- },
232
- { id: inviter.id, email: inviter.email },
233
- );
234
-
235
- await expect(
236
- service.accept(invitation.id, {
237
- id: wrongUser.id,
238
- email: wrongUser.email,
239
- }),
240
- ).rejects.toThrow(ForbiddenError);
241
- });
242
-
243
- it("should reject accept for non-pending invitation", async ({
244
- expect,
245
- }) => {
246
- const { service, createUser } = await setup();
247
-
248
- const inviter = await createUser("inviter@example.com");
249
- const invitee = await createUser("invitee@example.com");
250
-
251
- const invitation = await service.create(
252
- {
253
- email: "invitee@example.com",
254
- resourceType: "project",
255
- resourceId: "proj-1",
256
- },
257
- { id: inviter.id, email: inviter.email },
258
- );
259
-
260
- await service.accept(invitation.id, {
261
- id: invitee.id,
262
- email: invitee.email,
263
- });
264
-
265
- await expect(
266
- service.accept(invitation.id, {
267
- id: invitee.id,
268
- email: invitee.email,
269
- }),
270
- ).rejects.toThrow(BadRequestError);
271
- });
272
- });
273
-
274
- // -----------------------------------------------------------------------------------------------------------------
275
-
276
- describe("decline", () => {
277
- it("should decline invitation", async ({ expect }) => {
278
- const { service, createUser } = await setup();
279
-
280
- const inviter = await createUser("inviter@example.com");
281
- const invitee = await createUser("invitee@example.com");
282
-
283
- const invitation = await service.create(
284
- {
285
- email: "invitee@example.com",
286
- resourceType: "project",
287
- resourceId: "proj-1",
288
- },
289
- { id: inviter.id, email: inviter.email },
290
- );
291
-
292
- await service.decline(invitation.id, {
293
- id: invitee.id,
294
- email: invitee.email,
295
- });
296
-
297
- const updated = await service.getById(invitation.id);
298
- expect(updated.status).toBe("declined");
299
- expect(updated.resolvedBy).toBe(invitee.id);
300
- expect(updated.resolvedAt).toBeDefined();
301
- });
302
-
303
- it("should reject decline for wrong email", async ({ expect }) => {
304
- const { service, createUser } = await setup();
305
-
306
- const inviter = await createUser("inviter@example.com");
307
- const wrongUser = await createUser("wrong@example.com");
308
-
309
- const invitation = await service.create(
310
- {
311
- email: "invitee@example.com",
312
- resourceType: "project",
313
- resourceId: "proj-1",
314
- },
315
- { id: inviter.id, email: inviter.email },
316
- );
317
-
318
- await expect(
319
- service.decline(invitation.id, {
320
- id: wrongUser.id,
321
- email: wrongUser.email,
322
- }),
323
- ).rejects.toThrow(ForbiddenError);
324
- });
325
- });
326
-
327
- // -----------------------------------------------------------------------------------------------------------------
328
-
329
- describe("revoke", () => {
330
- it("should revoke pending invitation", async ({ expect }) => {
331
- const { service, createUser } = await setup();
332
-
333
- const inviter = await createUser("inviter@example.com");
334
-
335
- const invitation = await service.create(
336
- {
337
- email: "invitee@example.com",
338
- resourceType: "project",
339
- resourceId: "proj-1",
340
- },
341
- { id: inviter.id, email: inviter.email },
342
- );
343
-
344
- await service.revoke(invitation.id, { id: inviter.id });
345
-
346
- const updated = await service.getById(invitation.id);
347
- expect(updated.status).toBe("revoked");
348
- expect(updated.resolvedBy).toBe(inviter.id);
349
- expect(updated.resolvedAt).toBeDefined();
350
- });
351
-
352
- it("should reject revoke for non-pending", async ({ expect }) => {
353
- const { service, createUser } = await setup();
354
-
355
- const inviter = await createUser("inviter@example.com");
356
- const invitee = await createUser("invitee@example.com");
357
-
358
- const invitation = await service.create(
359
- {
360
- email: "invitee@example.com",
361
- resourceType: "project",
362
- resourceId: "proj-1",
363
- },
364
- { id: inviter.id, email: inviter.email },
365
- );
366
-
367
- await service.accept(invitation.id, {
368
- id: invitee.id,
369
- email: invitee.email,
370
- });
371
-
372
- await expect(
373
- service.revoke(invitation.id, { id: inviter.id }),
374
- ).rejects.toThrow(BadRequestError);
375
- });
376
- });
377
-
378
- // -----------------------------------------------------------------------------------------------------------------
379
-
380
- describe("findByEmail", () => {
381
- it("should return enriched invitations with resource info", async ({
382
- expect,
383
- }) => {
384
- const { service, createUser } = await setup();
385
-
386
- const inviter = await createUser("inviter@example.com");
387
-
388
- await service.create(
389
- {
390
- email: "lookup@example.com",
391
- resourceType: "project",
392
- resourceId: "proj-1",
393
- },
394
- { id: inviter.id, email: inviter.email },
395
- );
396
-
397
- const results = await service.findByEmail("lookup@example.com");
398
-
399
- expect(results).toHaveLength(1);
400
- expect(results[0].email).toBe("lookup@example.com");
401
- expect(results[0].resourceName).toBe("Test Project");
402
- expect(results[0].invitedBy).toBe(inviter.id);
403
- expect(results[0].inviterEmail).toBe("inviter@example.com");
404
- });
405
- });
406
-
407
- // -----------------------------------------------------------------------------------------------------------------
408
-
409
- describe("findInvitations", () => {
410
- it("should return paginated results", async ({ expect }) => {
411
- const { service, createUser } = await setup();
412
-
413
- const inviter = await createUser("inviter@example.com");
414
-
415
- await service.create(
416
- {
417
- email: "page1@example.com",
418
- resourceType: "project",
419
- resourceId: "proj-1",
420
- },
421
- { id: inviter.id, email: inviter.email },
422
- );
423
-
424
- await service.create(
425
- {
426
- email: "page2@example.com",
427
- resourceType: "project",
428
- resourceId: "proj-1",
429
- },
430
- { id: inviter.id, email: inviter.email },
431
- );
432
-
433
- const page = await service.findInvitations({ size: 10 });
434
-
435
- expect(page.content.length).toBeGreaterThanOrEqual(2);
436
- expect(page.page.totalElements).toBeGreaterThanOrEqual(2);
437
- });
438
- });
439
- });
@@ -1,86 +0,0 @@
1
- import { $inject, t } from "alepha";
2
- import { $secure } from "alepha/security";
3
- import { $action, okSchema } from "alepha/server";
4
- import { invitationQuerySchema } from "../schemas/invitationQuerySchema.ts";
5
- import { invitationResourceSchema } from "../schemas/invitationResourceSchema.ts";
6
- import { InvitationService } from "../services/InvitationService.ts";
7
-
8
- export class AdminInvitationController {
9
- protected readonly url = "/invitations";
10
- protected readonly group = "admin:invitations";
11
- protected readonly invitationService = $inject(InvitationService);
12
-
13
- /**
14
- * Find invitations with pagination and filtering.
15
- */
16
- public readonly findInvitations = $action({
17
- path: this.url,
18
- group: this.group,
19
- use: [$secure({ permissions: ["admin:invitation:read"] })],
20
- description: "Find invitations with pagination and filtering",
21
- schema: {
22
- query: invitationQuerySchema,
23
- response: t.page(invitationResourceSchema),
24
- },
25
- handler: ({ query }) => this.invitationService.findInvitations(query),
26
- });
27
-
28
- /**
29
- * Get an invitation by ID.
30
- */
31
- public readonly getInvitation = $action({
32
- path: `${this.url}/:id`,
33
- group: this.group,
34
- use: [$secure({ permissions: ["admin:invitation:read"] })],
35
- description: "Get an invitation by ID",
36
- schema: {
37
- params: t.object({
38
- id: t.uuid(),
39
- }),
40
- response: invitationResourceSchema,
41
- },
42
- handler: ({ params }) => this.invitationService.getById(params.id),
43
- });
44
-
45
- /**
46
- * Revoke a pending invitation.
47
- */
48
- public readonly revokeInvitation = $action({
49
- method: "POST",
50
- path: `${this.url}/:id/revoke`,
51
- group: this.group,
52
- use: [$secure({ permissions: ["admin:invitation:delete"] })],
53
- description: "Revoke a pending invitation",
54
- schema: {
55
- params: t.object({
56
- id: t.uuid(),
57
- }),
58
- response: okSchema,
59
- },
60
- handler: async ({ params, user }) => {
61
- await this.invitationService.revoke(params.id, { id: user.id });
62
- return { ok: true };
63
- },
64
- });
65
-
66
- /**
67
- * Delete an invitation.
68
- */
69
- public readonly deleteInvitation = $action({
70
- method: "DELETE",
71
- path: `${this.url}/:id`,
72
- group: this.group,
73
- use: [$secure({ permissions: ["admin:invitation:delete"] })],
74
- description: "Delete an invitation",
75
- schema: {
76
- params: t.object({
77
- id: t.uuid(),
78
- }),
79
- response: okSchema,
80
- },
81
- handler: async ({ params }) => {
82
- await this.invitationService.deleteInvitation(params.id);
83
- return { ok: true, id: params.id };
84
- },
85
- });
86
- }
@@ -1,84 +0,0 @@
1
- import { $inject, t } from "alepha";
2
- import { $secure } from "alepha/security";
3
- import { $action, okSchema } from "alepha/server";
4
- import { createInvitationSchema } from "../schemas/createInvitationSchema.ts";
5
- import { invitationResourceSchema } from "../schemas/invitationResourceSchema.ts";
6
- import { invitationWithResourceInfoSchema } from "../schemas/invitationWithResourceInfoSchema.ts";
7
- import { myInvitationsQuerySchema } from "../schemas/myInvitationsQuerySchema.ts";
8
- import { InvitationService } from "../services/InvitationService.ts";
9
-
10
- export class InvitationController {
11
- protected readonly url = "/invitations";
12
- protected readonly group = "invitations";
13
- protected readonly invitationService = $inject(InvitationService);
14
-
15
- /**
16
- * Create a new invitation.
17
- */
18
- public readonly createInvitation = $action({
19
- method: "POST",
20
- path: this.url,
21
- group: this.group,
22
- use: [$secure({ permissions: ["invitation:create"] })],
23
- description: "Create a new invitation",
24
- schema: {
25
- body: createInvitationSchema,
26
- response: invitationResourceSchema,
27
- },
28
- handler: ({ body, user }) => this.invitationService.create(body, user),
29
- });
30
-
31
- /**
32
- * List invitations for the current user.
33
- */
34
- public readonly getMyInvitations = $action({
35
- path: `${this.url}/mine`,
36
- group: this.group,
37
- use: [$secure()],
38
- description: "List invitations for the current user",
39
- schema: {
40
- query: myInvitationsQuerySchema,
41
- response: t.array(invitationWithResourceInfoSchema),
42
- },
43
- handler: ({ query, user }) =>
44
- this.invitationService.findByEmail(user.email!, query),
45
- });
46
-
47
- /**
48
- * Accept an invitation.
49
- */
50
- public readonly acceptInvitation = $action({
51
- method: "POST",
52
- path: `${this.url}/:id/accept`,
53
- group: this.group,
54
- use: [$secure()],
55
- description: "Accept an invitation",
56
- schema: {
57
- params: t.object({ id: t.uuid() }),
58
- response: okSchema,
59
- },
60
- handler: async ({ params, user }) => {
61
- await this.invitationService.accept(params.id, user);
62
- return { ok: true };
63
- },
64
- });
65
-
66
- /**
67
- * Decline an invitation.
68
- */
69
- public readonly declineInvitation = $action({
70
- method: "POST",
71
- path: `${this.url}/:id/decline`,
72
- group: this.group,
73
- use: [$secure()],
74
- description: "Decline an invitation",
75
- schema: {
76
- params: t.object({ id: t.uuid() }),
77
- response: okSchema,
78
- },
79
- handler: async ({ params, user }) => {
80
- await this.invitationService.decline(params.id, user);
81
- return { ok: true };
82
- },
83
- });
84
- }
@@ -1,33 +0,0 @@
1
- import { type Static, t } from "alepha";
2
- import { users } from "alepha/api/users";
3
- import { $entity, db } from "alepha/orm";
4
-
5
- export const invitations = $entity({
6
- name: "invitations",
7
- schema: t.object({
8
- id: db.primaryKey(t.uuid()),
9
- version: db.version(),
10
- createdAt: db.createdAt(),
11
- updatedAt: db.updatedAt(),
12
- invitedBy: db.ref(t.uuid(), () => users.cols.id, { onDelete: "cascade" }),
13
- email: t.string({ format: "email" }),
14
- resourceType: t.text({ minLength: 1, maxLength: 100 }),
15
- resourceId: t.text({ minLength: 1, maxLength: 255 }),
16
- status: t.enum(["pending", "accepted", "declined", "expired", "revoked"]),
17
- roles: t.optional(t.array(t.text())),
18
- metadata: t.optional(t.record(t.text(), t.any())),
19
- token: t.text(),
20
- expiresAt: t.datetime(),
21
- resolvedAt: t.optional(t.datetime()),
22
- resolvedBy: t.optional(db.ref(t.uuid(), () => users.cols.id)),
23
- }),
24
- indexes: [
25
- { columns: ["email", "status"] },
26
- { columns: ["resourceType", "resourceId", "email", "status"] },
27
- { columns: ["invitedBy"] },
28
- { columns: ["expiresAt"] },
29
- { columns: ["token"], unique: true },
30
- ],
31
- });
32
-
33
- export type InvitationEntity = Static<typeof invitations.schema>;
@@ -1,58 +0,0 @@
1
- import { $module } from "alepha";
2
- import { AdminInvitationController } from "./controllers/AdminInvitationController.ts";
3
- import { InvitationController } from "./controllers/InvitationController.ts";
4
- import { InvitationJobs } from "./jobs/InvitationJobs.ts";
5
- import { InvitationService } from "./services/InvitationService.ts";
6
-
7
- export * from "./controllers/AdminInvitationController.ts";
8
- export * from "./controllers/InvitationController.ts";
9
- export * from "./entities/invitations.ts";
10
- export * from "./jobs/InvitationJobs.ts";
11
- export * from "./providers/InvitationProvider.ts";
12
- export * from "./schemas/createInvitationSchema.ts";
13
- export * from "./schemas/invitationConfigAtom.ts";
14
- export * from "./schemas/invitationQuerySchema.ts";
15
- export * from "./schemas/invitationResourceSchema.ts";
16
- export * from "./schemas/invitationWithResourceInfoSchema.ts";
17
- export * from "./schemas/myInvitationsQuerySchema.ts";
18
- export * from "./services/InvitationService.ts";
19
-
20
- declare module "alepha" {
21
- interface Hooks {
22
- "invitation:created": {
23
- invitation: import("./entities/invitations.ts").InvitationEntity;
24
- token: string;
25
- inviter: { id: string; email?: string };
26
- };
27
- "invitation:accepted": {
28
- invitation: import("./entities/invitations.ts").InvitationEntity;
29
- acceptedBy: { id: string; email?: string };
30
- };
31
- "invitation:declined": {
32
- invitation: import("./entities/invitations.ts").InvitationEntity;
33
- declinedBy: { id: string; email?: string };
34
- };
35
- "invitation:expired": {
36
- invitation: import("./entities/invitations.ts").InvitationEntity;
37
- };
38
- "invitation:revoked": {
39
- invitation: import("./entities/invitations.ts").InvitationEntity;
40
- revokedBy: { id: string };
41
- };
42
- }
43
- }
44
-
45
- /**
46
- * Invitation management module — create, accept, decline, revoke, and expire invitations.
47
- *
48
- * @module alepha.api.invitations
49
- */
50
- export const AlephaApiInvitations = $module({
51
- name: "alepha.api.invitations",
52
- services: [
53
- InvitationService,
54
- InvitationJobs,
55
- InvitationController,
56
- AdminInvitationController,
57
- ],
58
- });