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,263 +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 } from "alepha/server";
6
- import { describe, expect, it } from "vitest";
7
- import { AlephaApiIssues } from "../index.ts";
8
- import { issueConfigAtom } from "../schemas/issueConfigAtom.ts";
9
- import { IssueService } from "../services/IssueService.ts";
10
-
11
- class TestRepositories {
12
- users = $repository(users);
13
- }
14
-
15
- const setup = async () => {
16
- const alepha = Alepha.create({ env: { LOG_LEVEL: "error" } });
17
-
18
- alepha.with(AlephaOrmPostgres);
19
- alepha.with(AlephaApiIssues);
20
- alepha.with(TestRepositories);
21
-
22
- const service = alepha.inject(IssueService);
23
- const repos = alepha.inject(TestRepositories);
24
-
25
- await alepha.start();
26
-
27
- const user = await repos.users.create({
28
- email: "test@example.com",
29
- roles: [],
30
- });
31
-
32
- const admin = await repos.users.create({
33
- email: "admin@example.com",
34
- roles: ["admin"],
35
- });
36
-
37
- return { alepha, service, repos, user, admin };
38
- };
39
-
40
- describe("IssueService", () => {
41
- describe("create", () => {
42
- it("should create an issue with defaults", async () => {
43
- const { service, user } = await setup();
44
-
45
- const issue = await service.create(
46
- { title: "Something is broken" },
47
- { id: user.id },
48
- );
49
-
50
- expect(issue.title).toBe("Something is broken");
51
- expect(issue.type).toBe("bug");
52
- expect(issue.priority).toBe("medium");
53
- expect(issue.status).toBe("open");
54
- expect(issue.createdBy).toBe(user.id);
55
- });
56
-
57
- it("should create an issue with all fields", async () => {
58
- const { service, user } = await setup();
59
-
60
- const issue = await service.create(
61
- {
62
- title: "Add dark mode",
63
- type: "feature",
64
- priority: "high",
65
- description: "We need dark mode support",
66
- pageUrl: "https://example.com/settings",
67
- },
68
- { id: user.id },
69
- );
70
-
71
- expect(issue.type).toBe("feature");
72
- expect(issue.priority).toBe("high");
73
- expect(issue.description).toBe("We need dark mode support");
74
- expect(issue.pageUrl).toBe("https://example.com/settings");
75
- });
76
-
77
- it("should reject when max open issues reached", async () => {
78
- const { alepha, service, user } = await setup();
79
-
80
- alepha.store.set(issueConfigAtom, {
81
- enabled: true,
82
- maxOpenPerUser: 1,
83
- });
84
-
85
- await service.create({ title: "First" }, { id: user.id });
86
-
87
- await expect(
88
- service.create({ title: "Second" }, { id: user.id }),
89
- ).rejects.toThrowError(BadRequestError);
90
- });
91
-
92
- it("should reject when issues are disabled", async () => {
93
- const { alepha, service, user } = await setup();
94
-
95
- alepha.store.set(issueConfigAtom, {
96
- enabled: false,
97
- maxOpenPerUser: 50,
98
- });
99
-
100
- await expect(
101
- service.create({ title: "Test" }, { id: user.id }),
102
- ).rejects.toThrowError(BadRequestError);
103
- });
104
- });
105
-
106
- describe("assign", () => {
107
- it("should assign an open issue", async () => {
108
- const { service, user, admin } = await setup();
109
- const issue = await service.create({ title: "Bug" }, { id: user.id });
110
-
111
- const assigned = await service.assign(issue.id, admin.id);
112
-
113
- expect(assigned.status).toBe("assigned");
114
- expect(assigned.assigneeId).toBe(admin.id);
115
- expect(assigned.assignedAt).toBeDefined();
116
- });
117
-
118
- it("should reject assigning a non-open issue", async () => {
119
- const { service, user, admin } = await setup();
120
- const issue = await service.create({ title: "Bug" }, { id: user.id });
121
- await service.assign(issue.id, admin.id);
122
-
123
- await expect(service.assign(issue.id, admin.id)).rejects.toThrowError(
124
- BadRequestError,
125
- );
126
- });
127
- });
128
-
129
- describe("complete", () => {
130
- it("should complete an assigned issue", async () => {
131
- const { service, user, admin } = await setup();
132
- const issue = await service.create({ title: "Bug" }, { id: user.id });
133
- await service.assign(issue.id, admin.id);
134
-
135
- const completed = await service.complete(issue.id, "Fixed the bug");
136
-
137
- expect(completed.status).toBe("completed");
138
- expect(completed.resolution).toBe("Fixed the bug");
139
- expect(completed.completedAt).toBeDefined();
140
- });
141
-
142
- it("should reject completing a non-assigned issue", async () => {
143
- const { service, user } = await setup();
144
- const issue = await service.create({ title: "Bug" }, { id: user.id });
145
-
146
- await expect(service.complete(issue.id, "Fixed")).rejects.toThrowError(
147
- BadRequestError,
148
- );
149
- });
150
- });
151
-
152
- describe("reopen", () => {
153
- it("should reopen a completed issue", async () => {
154
- const { service, user, admin } = await setup();
155
- const issue = await service.create({ title: "Bug" }, { id: user.id });
156
- await service.assign(issue.id, admin.id);
157
- await service.complete(issue.id, "Fixed");
158
-
159
- const reopened = await service.reopen(issue.id, "Not actually fixed");
160
-
161
- expect(reopened.status).toBe("open");
162
- expect(reopened.reopenReason).toBe("Not actually fixed");
163
- });
164
-
165
- it("should reject reopening a non-completed issue", async () => {
166
- const { service, user } = await setup();
167
- const issue = await service.create({ title: "Bug" }, { id: user.id });
168
-
169
- await expect(service.reopen(issue.id, "Reason")).rejects.toThrowError(
170
- BadRequestError,
171
- );
172
- });
173
- });
174
-
175
- describe("archive", () => {
176
- it("should archive a completed issue", async () => {
177
- const { service, user, admin } = await setup();
178
- const issue = await service.create({ title: "Bug" }, { id: user.id });
179
- await service.assign(issue.id, admin.id);
180
- await service.complete(issue.id, "Fixed");
181
-
182
- const archived = await service.archive(issue.id);
183
-
184
- expect(archived.status).toBe("archived");
185
- expect(archived.archivedAt).toBeDefined();
186
- });
187
-
188
- it("should reject archiving a non-completed issue", async () => {
189
- const { service, user } = await setup();
190
- const issue = await service.create({ title: "Bug" }, { id: user.id });
191
-
192
- await expect(service.archive(issue.id)).rejects.toThrowError(
193
- BadRequestError,
194
- );
195
- });
196
- });
197
-
198
- describe("find", () => {
199
- it("should paginate issues", async () => {
200
- const { service, user } = await setup();
201
- await service.create({ title: "Bug 1" }, { id: user.id });
202
- await service.create({ title: "Bug 2" }, { id: user.id });
203
-
204
- const page = await service.find({ size: 10 });
205
-
206
- expect(page.content.length).toBeGreaterThanOrEqual(2);
207
- expect(page.page.totalElements).toBeGreaterThanOrEqual(2);
208
- });
209
-
210
- it("should filter by status", async () => {
211
- const { service, user, admin } = await setup();
212
- await service.create({ title: "Open bug" }, { id: user.id });
213
- const bug2 = await service.create(
214
- { title: "Assigned bug" },
215
- { id: user.id },
216
- );
217
- await service.assign(bug2.id, admin.id);
218
-
219
- const openOnly = await service.find({ status: "open" });
220
-
221
- for (const issue of openOnly.content) {
222
- expect(issue.status).toBe("open");
223
- }
224
- });
225
-
226
- it("should search by title", async () => {
227
- const { service, user } = await setup();
228
- await service.create({ title: "Login page broken" }, { id: user.id });
229
- await service.create({ title: "Dark mode request" }, { id: user.id });
230
-
231
- const result = await service.find({ search: "Login" });
232
-
233
- expect(result.content.some((i) => i.title === "Login page broken")).toBe(
234
- true,
235
- );
236
- });
237
- });
238
-
239
- describe("findMine", () => {
240
- it("should return only the user's issues", async () => {
241
- const { service, user, admin } = await setup();
242
- await service.create({ title: "User bug" }, { id: user.id });
243
- await service.create({ title: "Admin bug" }, { id: admin.id });
244
-
245
- const mine = await service.findMine(user.id);
246
-
247
- for (const issue of mine.content) {
248
- expect(issue.createdBy).toBe(user.id);
249
- }
250
- });
251
- });
252
-
253
- describe("deleteIssue", () => {
254
- it("should delete an issue", async () => {
255
- const { service, user } = await setup();
256
- const issue = await service.create({ title: "Bug" }, { id: user.id });
257
-
258
- await service.deleteIssue(issue.id);
259
-
260
- await expect(service.getById(issue.id)).rejects.toThrow();
261
- });
262
- });
263
- });
@@ -1,149 +0,0 @@
1
- import { $inject, t } from "alepha";
2
- import { $secure } from "alepha/security";
3
- import { $action, okSchema } from "alepha/server";
4
- import { issueQuerySchema } from "../schemas/issueQuerySchema.ts";
5
- import { issueResourceSchema } from "../schemas/issueResourceSchema.ts";
6
- import { updateIssueSchema } from "../schemas/updateIssueSchema.ts";
7
- import { IssueService } from "../services/IssueService.ts";
8
-
9
- export class AdminIssueController {
10
- protected readonly url = "/issues";
11
- protected readonly group = "admin:issues";
12
- protected readonly issueService = $inject(IssueService);
13
-
14
- /**
15
- * Find issues with pagination and filtering.
16
- */
17
- public readonly findIssues = $action({
18
- path: this.url,
19
- group: this.group,
20
- use: [$secure({ permissions: ["admin:issue:read"] })],
21
- description: "Find issues with pagination and filtering",
22
- schema: {
23
- query: issueQuerySchema,
24
- response: t.page(issueResourceSchema),
25
- },
26
- handler: ({ query }) => this.issueService.find(query),
27
- });
28
-
29
- /**
30
- * Get an issue by ID.
31
- */
32
- public readonly getIssue = $action({
33
- path: `${this.url}/:id`,
34
- group: this.group,
35
- use: [$secure({ permissions: ["admin:issue:read"] })],
36
- description: "Get an issue by ID",
37
- schema: {
38
- params: t.object({ id: t.uuid() }),
39
- response: issueResourceSchema,
40
- },
41
- handler: ({ params }) => this.issueService.getById(params.id),
42
- });
43
-
44
- /**
45
- * Update issue fields.
46
- */
47
- public readonly updateIssue = $action({
48
- method: "PATCH",
49
- path: `${this.url}/:id`,
50
- group: this.group,
51
- use: [$secure({ permissions: ["admin:issue:update"] })],
52
- description: "Update issue fields",
53
- schema: {
54
- params: t.object({ id: t.uuid() }),
55
- body: updateIssueSchema,
56
- response: issueResourceSchema,
57
- },
58
- handler: ({ params, body }) => this.issueService.update(params.id, body),
59
- });
60
-
61
- /**
62
- * Assign an issue to a user.
63
- */
64
- public readonly assignIssue = $action({
65
- method: "POST",
66
- path: `${this.url}/:id/assign`,
67
- group: this.group,
68
- use: [$secure({ permissions: ["admin:issue:update"] })],
69
- description: "Assign an issue to a user",
70
- schema: {
71
- params: t.object({ id: t.uuid() }),
72
- body: t.object({ assigneeId: t.uuid() }),
73
- response: issueResourceSchema,
74
- },
75
- handler: ({ params, body }) =>
76
- this.issueService.assign(params.id, body.assigneeId),
77
- });
78
-
79
- /**
80
- * Mark an issue as completed.
81
- */
82
- public readonly completeIssue = $action({
83
- method: "POST",
84
- path: `${this.url}/:id/complete`,
85
- group: this.group,
86
- use: [$secure({ permissions: ["admin:issue:update"] })],
87
- description: "Mark an issue as completed with resolution notes",
88
- schema: {
89
- params: t.object({ id: t.uuid() }),
90
- body: t.object({ resolution: t.text({ minLength: 1 }) }),
91
- response: issueResourceSchema,
92
- },
93
- handler: ({ params, body }) =>
94
- this.issueService.complete(params.id, body.resolution),
95
- });
96
-
97
- /**
98
- * Reopen a completed issue.
99
- */
100
- public readonly reopenIssue = $action({
101
- method: "POST",
102
- path: `${this.url}/:id/reopen`,
103
- group: this.group,
104
- use: [$secure({ permissions: ["admin:issue:update"] })],
105
- description: "Reopen a completed issue with a reason",
106
- schema: {
107
- params: t.object({ id: t.uuid() }),
108
- body: t.object({ reason: t.text({ minLength: 1 }) }),
109
- response: issueResourceSchema,
110
- },
111
- handler: ({ params, body }) =>
112
- this.issueService.reopen(params.id, body.reason),
113
- });
114
-
115
- /**
116
- * Archive a completed issue.
117
- */
118
- public readonly archiveIssue = $action({
119
- method: "POST",
120
- path: `${this.url}/:id/archive`,
121
- group: this.group,
122
- use: [$secure({ permissions: ["admin:issue:update"] })],
123
- description: "Archive a completed issue",
124
- schema: {
125
- params: t.object({ id: t.uuid() }),
126
- response: issueResourceSchema,
127
- },
128
- handler: ({ params }) => this.issueService.archive(params.id),
129
- });
130
-
131
- /**
132
- * Delete an issue.
133
- */
134
- public readonly deleteIssue = $action({
135
- method: "DELETE",
136
- path: `${this.url}/:id`,
137
- group: this.group,
138
- use: [$secure({ permissions: ["admin:issue:delete"] })],
139
- description: "Delete an issue",
140
- schema: {
141
- params: t.object({ id: t.uuid() }),
142
- response: okSchema,
143
- },
144
- handler: async ({ params }) => {
145
- await this.issueService.deleteIssue(params.id);
146
- return { ok: true, id: params.id };
147
- },
148
- });
149
- }
@@ -1,44 +0,0 @@
1
- import { $inject, t } from "alepha";
2
- import { $secure } from "alepha/security";
3
- import { $action } from "alepha/server";
4
- import { createIssueSchema } from "../schemas/createIssueSchema.ts";
5
- import { issueResourceSchema } from "../schemas/issueResourceSchema.ts";
6
- import { myIssueQuerySchema } from "../schemas/myIssueQuerySchema.ts";
7
- import { IssueService } from "../services/IssueService.ts";
8
-
9
- export class IssueController {
10
- protected readonly url = "/issues";
11
- protected readonly group = "issues";
12
- protected readonly issueService = $inject(IssueService);
13
-
14
- /**
15
- * Submit a new issue.
16
- */
17
- public readonly createIssue = $action({
18
- method: "POST",
19
- path: this.url,
20
- group: this.group,
21
- use: [$secure({ permissions: ["issue:create"] })],
22
- description: "Submit a new issue",
23
- schema: {
24
- body: createIssueSchema,
25
- response: issueResourceSchema,
26
- },
27
- handler: ({ body, user }) => this.issueService.create(body, user),
28
- });
29
-
30
- /**
31
- * List issues for the current user.
32
- */
33
- public readonly getMyIssues = $action({
34
- path: `${this.url}/mine`,
35
- group: this.group,
36
- use: [$secure({ permissions: ["issue:read"] })],
37
- description: "List issues submitted by the current user",
38
- schema: {
39
- query: myIssueQuerySchema,
40
- response: t.page(issueResourceSchema),
41
- },
42
- handler: ({ query, user }) => this.issueService.findMine(user.id, query),
43
- });
44
- }
@@ -1,49 +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 issueTypeSchema = t.enum(["bug", "feature", "improvement"], {
6
- default: "bug",
7
- });
8
-
9
- export const issuePrioritySchema = t.enum(["low", "medium", "high", "urgent"], {
10
- default: "medium",
11
- });
12
-
13
- export const issueStatusSchema = t.enum([
14
- "open",
15
- "assigned",
16
- "completed",
17
- "archived",
18
- ]);
19
-
20
- export const issues = $entity({
21
- name: "issues",
22
- schema: t.object({
23
- id: db.primaryKey(t.uuid()),
24
- version: db.version(),
25
- createdAt: db.createdAt(),
26
- updatedAt: db.updatedAt(),
27
- createdBy: db.ref(t.uuid(), () => users.cols.id, { onDelete: "cascade" }),
28
- title: t.text({ maxLength: 255 }),
29
- type: db.default(issueTypeSchema, "bug"),
30
- priority: db.default(issuePrioritySchema, "medium"),
31
- status: db.default(issueStatusSchema, "open"),
32
- description: t.optional(t.text({ maxLength: 65535 })),
33
- pageUrl: t.optional(t.string({ format: "uri" })),
34
- assigneeId: t.optional(db.ref(t.uuid(), () => users.cols.id)),
35
- assignedAt: t.optional(t.datetime()),
36
- resolution: t.optional(t.text({ maxLength: 65535 })),
37
- completedAt: t.optional(t.datetime()),
38
- reopenReason: t.optional(t.text({ maxLength: 1024 })),
39
- archivedAt: t.optional(t.datetime()),
40
- }),
41
- indexes: [
42
- "status",
43
- "createdBy",
44
- "assigneeId",
45
- { columns: ["type", "status"] },
46
- ],
47
- });
48
-
49
- export type IssueEntity = Static<typeof issues.schema>;
@@ -1,50 +0,0 @@
1
- import { $module } from "alepha";
2
- import { AdminIssueController } from "./controllers/AdminIssueController.ts";
3
- import { IssueController } from "./controllers/IssueController.ts";
4
- import { IssueService } from "./services/IssueService.ts";
5
-
6
- export * from "./controllers/AdminIssueController.ts";
7
- export * from "./controllers/IssueController.ts";
8
- export * from "./entities/issues.ts";
9
- export * from "./schemas/createIssueSchema.ts";
10
- export * from "./schemas/issueConfigAtom.ts";
11
- export * from "./schemas/issueQuerySchema.ts";
12
- export * from "./schemas/issueResourceSchema.ts";
13
- export * from "./schemas/myIssueQuerySchema.ts";
14
- export * from "./schemas/updateIssueSchema.ts";
15
- export * from "./services/IssueService.ts";
16
-
17
- declare module "alepha" {
18
- interface Hooks {
19
- "issue:created": {
20
- issue: import("./entities/issues.ts").IssueEntity;
21
- };
22
- "issue:assigned": {
23
- issue: import("./entities/issues.ts").IssueEntity;
24
- assigneeId: string;
25
- };
26
- "issue:completed": {
27
- issue: import("./entities/issues.ts").IssueEntity;
28
- };
29
- "issue:reopened": {
30
- issue: import("./entities/issues.ts").IssueEntity;
31
- reason: string;
32
- };
33
- "issue:archived": {
34
- issue: import("./entities/issues.ts").IssueEntity;
35
- };
36
- "issue:deleted": {
37
- issue: import("./entities/issues.ts").IssueEntity;
38
- };
39
- }
40
- }
41
-
42
- /**
43
- * Issue tracking module — submit, assign, complete, reopen, and archive issues.
44
- *
45
- * @module alepha.api.issues
46
- */
47
- export const AlephaApiIssues = $module({
48
- name: "alepha.api.issues",
49
- services: [IssueService, IssueController, AdminIssueController],
50
- });
@@ -1,13 +0,0 @@
1
- import type { Static } from "alepha";
2
- import { t } from "alepha";
3
- import { issuePrioritySchema, issueTypeSchema } from "../entities/issues.ts";
4
-
5
- export const createIssueSchema = t.object({
6
- title: t.text({ maxLength: 255 }),
7
- type: t.optional(issueTypeSchema),
8
- priority: t.optional(issuePrioritySchema),
9
- description: t.optional(t.text({ maxLength: 65535 })),
10
- pageUrl: t.optional(t.string({ format: "uri" })),
11
- });
12
-
13
- export type CreateIssue = Static<typeof createIssueSchema>;
@@ -1,13 +0,0 @@
1
- import { $atom, t } from "alepha";
2
-
3
- export const issueConfigAtom = $atom({
4
- name: "alepha.api.issues.config",
5
- schema: t.object({
6
- enabled: t.boolean(),
7
- maxOpenPerUser: t.integer({ minimum: 1, maximum: 1000 }),
8
- }),
9
- default: {
10
- enabled: true,
11
- maxOpenPerUser: 50,
12
- },
13
- });
@@ -1,18 +0,0 @@
1
- import type { Static } from "alepha";
2
- import { t } from "alepha";
3
- import { pageQuerySchema } from "alepha/orm";
4
- import {
5
- issuePrioritySchema,
6
- issueStatusSchema,
7
- issueTypeSchema,
8
- } from "../entities/issues.ts";
9
-
10
- export const issueQuerySchema = t.extend(pageQuerySchema, {
11
- status: t.optional(issueStatusSchema),
12
- type: t.optional(issueTypeSchema),
13
- priority: t.optional(issuePrioritySchema),
14
- assigneeId: t.optional(t.uuid({ description: "Filter by assignee" })),
15
- search: t.optional(t.text({ description: "Search in title" })),
16
- });
17
-
18
- export type IssueQuery = Static<typeof issueQuerySchema>;
@@ -1,6 +0,0 @@
1
- import type { Static } from "alepha";
2
- import { issues } from "../entities/issues.ts";
3
-
4
- export const issueResourceSchema = issues.schema;
5
-
6
- export type IssueResource = Static<typeof issueResourceSchema>;
@@ -1,10 +0,0 @@
1
- import type { Static } from "alepha";
2
- import { t } from "alepha";
3
- import { pageQuerySchema } from "alepha/orm";
4
- import { issueStatusSchema } from "../entities/issues.ts";
5
-
6
- export const myIssueQuerySchema = t.extend(pageQuerySchema, {
7
- status: t.optional(issueStatusSchema),
8
- });
9
-
10
- export type MyIssueQuery = Static<typeof myIssueQuerySchema>;
@@ -1,13 +0,0 @@
1
- import type { Static } from "alepha";
2
- import { t } from "alepha";
3
- import { issuePrioritySchema, issueTypeSchema } from "../entities/issues.ts";
4
-
5
- export const updateIssueSchema = t.object({
6
- title: t.optional(t.text({ maxLength: 255 })),
7
- type: t.optional(issueTypeSchema),
8
- priority: t.optional(issuePrioritySchema),
9
- description: t.optional(t.text({ maxLength: 65535 })),
10
- pageUrl: t.optional(t.string({ format: "uri" })),
11
- });
12
-
13
- export type UpdateIssue = Static<typeof updateIssueSchema>;