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.
- package/dist/api/files/index.js +2 -1
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.browser.js +64 -148
- package/dist/api/jobs/index.browser.js.map +1 -1
- package/dist/api/jobs/index.d.ts +371 -573
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js +605 -1012
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/notifications/index.d.ts +78 -17
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/notifications/index.js +90 -23
- package/dist/api/notifications/index.js.map +1 -1
- package/dist/api/payments/index.d.ts +2 -1
- package/dist/api/payments/index.d.ts.map +1 -1
- package/dist/api/payments/index.js +4 -2
- package/dist/api/payments/index.js.map +1 -1
- package/dist/api/users/index.d.ts +34 -31
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +13 -7
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.js +2 -1
- package/dist/api/verifications/index.js.map +1 -1
- package/dist/cli/core/index.d.ts +8 -34
- package/dist/cli/core/index.d.ts.map +1 -1
- package/dist/cli/core/index.js +43 -232
- package/dist/cli/core/index.js.map +1 -1
- package/dist/cli/platform/index.d.ts +36 -11
- package/dist/cli/platform/index.d.ts.map +1 -1
- package/dist/cli/platform/index.js +93 -27
- package/dist/cli/platform/index.js.map +1 -1
- package/dist/command/index.d.ts +1 -1
- package/dist/core/index.browser.js +6 -0
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +6 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +6 -0
- package/dist/core/index.native.js.map +1 -1
- package/dist/core/index.workerd.js +6 -0
- package/dist/core/index.workerd.js.map +1 -1
- package/dist/react/form/index.d.ts +60 -1
- package/dist/react/form/index.d.ts.map +1 -1
- package/dist/react/form/index.js +86 -1
- package/dist/react/form/index.js.map +1 -1
- package/dist/react/head/index.browser.js +16 -1
- package/dist/react/head/index.browser.js.map +1 -1
- package/dist/react/head/index.d.ts +6 -0
- package/dist/react/head/index.d.ts.map +1 -1
- package/dist/react/head/index.js +16 -1
- package/dist/react/head/index.js.map +1 -1
- package/dist/react/router/index.browser.js +0 -10
- package/dist/react/router/index.browser.js.map +1 -1
- package/dist/react/router/index.d.ts +35 -12
- package/dist/react/router/index.d.ts.map +1 -1
- package/dist/react/router/index.js +0 -10
- package/dist/react/router/index.js.map +1 -1
- package/dist/react/ui/index.d.ts +124 -0
- package/dist/react/ui/index.d.ts.map +1 -0
- package/dist/react/ui/index.js +206 -0
- package/dist/react/ui/index.js.map +1 -0
- package/dist/router/index.d.ts +13 -13
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +45 -32
- package/dist/router/index.js.map +1 -1
- package/dist/system/index.d.ts.map +1 -1
- package/dist/system/index.js +1 -0
- package/dist/system/index.js.map +1 -1
- package/dist/topic/core/index.js +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/package.json +6 -23
- package/src/api/files/jobs/FileJobs.ts +2 -1
- package/src/api/jobs/__tests__/$job.spec.ts +316 -2867
- package/src/api/jobs/controllers/AdminJobController.ts +29 -138
- package/src/api/jobs/entities/jobExecutionEntity.ts +27 -19
- package/src/api/jobs/index.browser.ts +5 -7
- package/src/api/jobs/index.ts +23 -51
- package/src/api/jobs/primitives/$job.ts +66 -58
- package/src/api/jobs/providers/JobProvider.ts +561 -566
- package/src/api/jobs/providers/JobQueueProvider.ts +18 -19
- package/src/api/jobs/schemas/jobConfigAtom.ts +20 -23
- package/src/api/jobs/schemas/jobExecutionQuerySchema.ts +3 -27
- package/src/api/jobs/schemas/jobExecutionResourceSchema.ts +5 -7
- package/src/api/jobs/schemas/jobRegistrationSchema.ts +7 -4
- package/src/api/jobs/schemas/triggerJobSchema.ts +0 -1
- package/src/api/jobs/services/JobService.ts +90 -483
- package/src/api/notifications/controllers/AdminNotificationController.ts +19 -12
- package/src/api/notifications/index.ts +7 -4
- package/src/api/notifications/jobs/NotificationJobs.ts +83 -12
- package/src/api/payments/services/PaymentService.ts +4 -2
- package/src/api/users/__tests__/UserJobs.spec.ts +10 -49
- package/src/api/users/audits/UserAudits.ts +3 -1
- package/src/api/users/buckets/UserBuckets.ts +2 -1
- package/src/api/users/index.ts +1 -4
- package/src/api/users/jobs/UserJobs.ts +5 -4
- package/src/api/verifications/jobs/VerificationJobs.ts +2 -1
- package/src/cli/core/__tests__/init.spec.ts +1 -1
- package/src/cli/core/commands/init.ts +0 -12
- package/src/cli/core/services/PackageManagerUtils.ts +2 -9
- package/src/cli/core/services/ProjectScaffolder.ts +17 -65
- package/src/cli/core/templates/agentMd.ts +2 -8
- package/src/cli/core/templates/apiIndexTs.ts +4 -18
- package/src/cli/core/templates/mainCss.ts +1 -36
- package/src/cli/core/templates/vitestConfigTs.ts +17 -0
- package/src/cli/core/templates/webAppRouterTs.ts +2 -85
- package/src/cli/platform/__tests__/CloudflareAdapter.spec.ts +22 -71
- package/src/cli/platform/adapters/CloudflareAdapter.ts +12 -11
- package/src/cli/platform/atoms/platformOptions.ts +9 -0
- package/src/cli/platform/schemas/cloudflare.ts +3 -2
- package/src/cli/platform/services/CloudflareApi.ts +164 -25
- package/src/cli/platform/services/WranglerApi.ts +0 -17
- package/src/core/Alepha.ts +9 -0
- package/src/react/form/index.ts +2 -0
- package/src/react/form/services/parseField.ts +163 -0
- package/src/react/form/services/prettyName.ts +19 -0
- package/src/react/head/providers/BrowserHeadProvider.ts +31 -10
- package/src/react/router/primitives/$page.ts +35 -12
- package/src/react/ui/atoms/uiAtom.ts +28 -0
- package/src/react/ui/components/ColorScheme.tsx +36 -0
- package/src/react/ui/hooks/useColorMode.ts +49 -0
- package/src/react/ui/hooks/useSidebarState.ts +26 -0
- package/src/react/ui/hooks/useTheme.ts +22 -0
- package/src/react/ui/index.ts +35 -0
- package/src/react/ui/services/UiPersistence.ts +41 -0
- package/src/router/TemplatedPathParser.ts +50 -51
- package/src/router/__tests__/RouterProvider.spec.ts +62 -0
- package/src/router/__tests__/TemplatedPathParser.spec.ts +18 -0
- package/src/router/providers/RouterProvider.ts +10 -5
- package/src/system/providers/NodeShellProvider.ts +1 -0
- package/src/topic/core/providers/TopicProvider.ts +1 -1
- package/dist/api/invitations/index.d.ts +0 -790
- package/dist/api/invitations/index.d.ts.map +0 -1
- package/dist/api/invitations/index.js +0 -662
- package/dist/api/invitations/index.js.map +0 -1
- package/dist/api/issues/index.d.ts +0 -810
- package/dist/api/issues/index.d.ts.map +0 -1
- package/dist/api/issues/index.js +0 -444
- package/dist/api/issues/index.js.map +0 -1
- package/dist/api/subscriptions/index.d.ts +0 -1692
- package/dist/api/subscriptions/index.d.ts.map +0 -1
- package/dist/api/subscriptions/index.js +0 -1867
- package/dist/api/subscriptions/index.js.map +0 -1
- package/dist/api/workflows/index.browser.js +0 -246
- package/dist/api/workflows/index.browser.js.map +0 -1
- package/dist/api/workflows/index.d.ts +0 -1618
- package/dist/api/workflows/index.d.ts.map +0 -1
- package/dist/api/workflows/index.js +0 -1495
- package/dist/api/workflows/index.js.map +0 -1
- package/src/api/invitations/__tests__/InvitationService.spec.ts +0 -439
- package/src/api/invitations/controllers/AdminInvitationController.ts +0 -86
- package/src/api/invitations/controllers/InvitationController.ts +0 -84
- package/src/api/invitations/entities/invitations.ts +0 -33
- package/src/api/invitations/index.ts +0 -58
- package/src/api/invitations/jobs/InvitationJobs.ts +0 -37
- package/src/api/invitations/providers/InvitationProvider.ts +0 -45
- package/src/api/invitations/schemas/createInvitationSchema.ts +0 -12
- package/src/api/invitations/schemas/invitationConfigAtom.ts +0 -20
- package/src/api/invitations/schemas/invitationQuerySchema.ts +0 -15
- package/src/api/invitations/schemas/invitationResourceSchema.ts +0 -6
- package/src/api/invitations/schemas/invitationWithResourceInfoSchema.ts +0 -22
- package/src/api/invitations/schemas/myInvitationsQuerySchema.ts +0 -10
- package/src/api/invitations/services/InvitationService.ts +0 -556
- package/src/api/issues/__tests__/IssueService.spec.ts +0 -263
- package/src/api/issues/controllers/AdminIssueController.ts +0 -149
- package/src/api/issues/controllers/IssueController.ts +0 -44
- package/src/api/issues/entities/issues.ts +0 -49
- package/src/api/issues/index.ts +0 -50
- package/src/api/issues/schemas/createIssueSchema.ts +0 -13
- package/src/api/issues/schemas/issueConfigAtom.ts +0 -13
- package/src/api/issues/schemas/issueQuerySchema.ts +0 -18
- package/src/api/issues/schemas/issueResourceSchema.ts +0 -6
- package/src/api/issues/schemas/myIssueQuerySchema.ts +0 -10
- package/src/api/issues/schemas/updateIssueSchema.ts +0 -13
- package/src/api/issues/services/IssueService.ts +0 -264
- package/src/api/jobs/__tests__/$job-middleware.spec.ts +0 -126
- package/src/api/jobs/__tests__/JobService.spec.ts +0 -31
- package/src/api/jobs/entities/jobExecutionLogEntity.ts +0 -13
- package/src/api/jobs/schemas/jobActivitySchema.ts +0 -15
- package/src/api/jobs/schemas/jobCronInfoSchema.ts +0 -22
- package/src/api/jobs/schemas/jobExecutionDetailResourceSchema.ts +0 -20
- package/src/api/jobs/schemas/jobFailureSchema.ts +0 -9
- package/src/api/jobs/schemas/jobQueueDepthSchema.ts +0 -14
- package/src/api/jobs/schemas/jobStatsSchema.ts +0 -14
- package/src/api/jobs/services/JobService-tests.ts +0 -157
- package/src/api/subscriptions/__tests__/BillingService.spec.ts +0 -218
- package/src/api/subscriptions/__tests__/SubscriptionService.spec.ts +0 -278
- package/src/api/subscriptions/controllers/AdminSubscriptionController.ts +0 -212
- package/src/api/subscriptions/controllers/SubscriptionController.ts +0 -189
- package/src/api/subscriptions/entities/subscriptionEvents.ts +0 -54
- package/src/api/subscriptions/entities/subscriptions.ts +0 -68
- package/src/api/subscriptions/index.ts +0 -133
- package/src/api/subscriptions/jobs/SubscriptionJobs.ts +0 -382
- package/src/api/subscriptions/middleware/$requireLimit.ts +0 -50
- package/src/api/subscriptions/middleware/$requirePlan.ts +0 -49
- package/src/api/subscriptions/notifications/SubscriptionNotifications.ts +0 -110
- package/src/api/subscriptions/schemas/cancelSubscriptionSchema.ts +0 -8
- package/src/api/subscriptions/schemas/changePlanSchema.ts +0 -9
- package/src/api/subscriptions/schemas/createSubscriptionSchema.ts +0 -11
- package/src/api/subscriptions/schemas/entitlementsSchema.ts +0 -21
- package/src/api/subscriptions/schemas/mrrSchema.ts +0 -13
- package/src/api/subscriptions/schemas/planDefinitionSchema.ts +0 -71
- package/src/api/subscriptions/schemas/planResourceSchema.ts +0 -25
- package/src/api/subscriptions/schemas/subscriptionEventResourceSchema.ts +0 -8
- package/src/api/subscriptions/schemas/subscriptionQuerySchema.ts +0 -19
- package/src/api/subscriptions/schemas/subscriptionResourceSchema.ts +0 -6
- package/src/api/subscriptions/schemas/subscriptionSettingsSchema.ts +0 -32
- package/src/api/subscriptions/schemas/subscriptionStatsSchema.ts +0 -23
- package/src/api/subscriptions/services/BillingService.ts +0 -437
- package/src/api/subscriptions/services/SubscriptionConfig.ts +0 -56
- package/src/api/subscriptions/services/SubscriptionService.ts +0 -867
- package/src/api/subscriptions/services/UsageService.ts +0 -118
- package/src/api/workflows/__tests__/$workflow.spec.ts +0 -616
- package/src/api/workflows/controllers/AdminWorkflowController.ts +0 -191
- package/src/api/workflows/entities/workflowExecutions.ts +0 -74
- package/src/api/workflows/entities/workflowStepExecutions.ts +0 -74
- package/src/api/workflows/entities/workflowStepLogs.ts +0 -13
- package/src/api/workflows/index.browser.ts +0 -22
- package/src/api/workflows/index.ts +0 -115
- package/src/api/workflows/jobs/WorkflowJobs.ts +0 -77
- package/src/api/workflows/primitives/$workflow.ts +0 -202
- package/src/api/workflows/providers/WorkflowProvider.ts +0 -1284
- package/src/api/workflows/schemas/workflowActivitySchema.ts +0 -15
- package/src/api/workflows/schemas/workflowConfigAtom.ts +0 -51
- package/src/api/workflows/schemas/workflowExecutionDetailSchema.ts +0 -18
- package/src/api/workflows/schemas/workflowExecutionQuerySchema.ts +0 -26
- package/src/api/workflows/schemas/workflowExecutionResourceSchema.ts +0 -30
- package/src/api/workflows/schemas/workflowRegistrationSchema.ts +0 -26
- package/src/api/workflows/schemas/workflowStatsSchema.ts +0 -16
- package/src/api/workflows/schemas/workflowStepExecutionResourceSchema.ts +0 -15
- package/src/api/workflows/services/WorkflowService.ts +0 -382
- package/src/cli/core/templates/apiAppSecurityTs.ts +0 -43
- 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>;
|
package/src/api/issues/index.ts
DELETED
|
@@ -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,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>;
|