@storyshelf/core 0.1.0 → 0.1.3
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/LICENSE +21 -0
- package/README.md +9 -8
- package/dist/adapter/auth.mjs.map +1 -1
- package/dist/adapter/capture-runner.d.mts +4 -0
- package/dist/adapter/git-host/helpers.d.mts +1 -0
- package/dist/adapter/git-host/helpers.mjs +2 -1
- package/dist/adapter/git-host/helpers.mjs.map +1 -1
- package/dist/adapter/storage.d.mts +1 -0
- package/dist/adapters/capture-queue.d.mts +2 -0
- package/dist/adapters/webhook-events.mjs +8 -0
- package/dist/adapters/webhook-events.mjs.map +1 -1
- package/dist/capture/adapter.d.mts +11 -1
- package/dist/capture/adapter.mjs +19 -1
- package/dist/capture/adapter.mjs.map +1 -1
- package/dist/capture/orchestrator.d.mts +7 -0
- package/dist/capture/orchestrator.mjs +43 -10
- package/dist/capture/orchestrator.mjs.map +1 -1
- package/dist/capture/pipeline.d.mts +5 -3
- package/dist/capture/pipeline.mjs +18 -6
- package/dist/capture/pipeline.mjs.map +1 -1
- package/dist/capture/queue.d.mts +2 -0
- package/dist/capture/queue.mjs +2 -1
- package/dist/capture/queue.mjs.map +1 -1
- package/dist/capture/status-fanout.mjs +1 -0
- package/dist/capture/status-fanout.mjs.map +1 -1
- package/dist/capture/storybook.d.mts +1 -0
- package/dist/capture/storybook.mjs +12 -3
- package/dist/capture/storybook.mjs.map +1 -1
- package/dist/config.d.mts +27 -20
- package/dist/config.mjs +16 -2
- package/dist/config.mjs.map +1 -1
- package/dist/ddl.d.mts +2 -1
- package/dist/ddl.mjs +3 -0
- package/dist/ddl.mjs.map +1 -1
- package/dist/diff/engine.d.mts +8 -0
- package/dist/diff/engine.mjs +10 -2
- package/dist/diff/engine.mjs.map +1 -1
- package/dist/index.d.mts +7 -4
- package/dist/index.mjs +10 -3
- package/dist/index.mjs.map +1 -1
- package/dist/logger.d.mts +3 -3
- package/dist/logger.mjs.map +1 -1
- package/dist/middleware/csrf.mjs +2 -1
- package/dist/middleware/csrf.mjs.map +1 -1
- package/dist/middleware/rate-limit.mjs +1 -0
- package/dist/middleware/rate-limit.mjs.map +1 -1
- package/dist/models/baseline.d.mts +1 -0
- package/dist/models/baseline.mjs +3 -1
- package/dist/models/baseline.mjs.map +1 -1
- package/dist/models/build.d.mts +19 -1
- package/dist/models/build.mjs +22 -2
- package/dist/models/build.mjs.map +1 -1
- package/dist/models/comment.d.mts +2 -0
- package/dist/models/comment.mjs +3 -1
- package/dist/models/comment.mjs.map +1 -1
- package/dist/models/label.d.mts +6 -0
- package/dist/models/label.mjs +13 -1
- package/dist/models/label.mjs.map +1 -1
- package/dist/models/member.mjs +2 -1
- package/dist/models/member.mjs.map +1 -1
- package/dist/models/project.d.mts +1 -0
- package/dist/models/project.mjs +3 -2
- package/dist/models/project.mjs.map +1 -1
- package/dist/models/snapshot.d.mts +1 -0
- package/dist/models/snapshot.mjs +2 -1
- package/dist/models/snapshot.mjs.map +1 -1
- package/dist/models/status-config.mjs +2 -1
- package/dist/models/status-config.mjs.map +1 -1
- package/dist/models/token.mjs +2 -1
- package/dist/models/token.mjs.map +1 -1
- package/dist/models/webhook.d.mts +1 -0
- package/dist/models/webhook.mjs +2 -1
- package/dist/models/webhook.mjs.map +1 -1
- package/dist/pages/build-detail.mjs +2 -1
- package/dist/pages/build-detail.mjs.map +1 -1
- package/dist/pages/build-diff.mjs +1 -0
- package/dist/pages/build-diff.mjs.map +1 -1
- package/dist/pages/compute-jobs.mjs +2 -0
- package/dist/pages/compute-jobs.mjs.map +1 -1
- package/dist/pages/label-detail.mjs +227 -0
- package/dist/pages/label-detail.mjs.map +1 -0
- package/dist/pages/login.mjs +1 -0
- package/dist/pages/login.mjs.map +1 -1
- package/dist/pages/project-builds.mjs +1 -0
- package/dist/pages/project-builds.mjs.map +1 -1
- package/dist/pages/project-create.mjs +1 -0
- package/dist/pages/project-create.mjs.map +1 -1
- package/dist/pages/project-settings.mjs +33 -42
- package/dist/pages/project-settings.mjs.map +1 -1
- package/dist/pages/projects.mjs +1 -0
- package/dist/pages/projects.mjs.map +1 -1
- package/dist/pages/root.mjs +1 -0
- package/dist/pages/root.mjs.map +1 -1
- package/dist/pages/settings-general.mjs +1 -0
- package/dist/pages/settings-general.mjs.map +1 -1
- package/dist/pages/settings-labels.mjs +1 -0
- package/dist/pages/settings-labels.mjs.map +1 -1
- package/dist/pages/settings-members.mjs +1 -0
- package/dist/pages/settings-members.mjs.map +1 -1
- package/dist/pages/settings-status.mjs +1 -0
- package/dist/pages/settings-status.mjs.map +1 -1
- package/dist/pages/settings-tests.mjs +149 -0
- package/dist/pages/settings-tests.mjs.map +1 -0
- package/dist/pages/settings-tokens.mjs +1 -0
- package/dist/pages/settings-tokens.mjs.map +1 -1
- package/dist/pages/settings-webhooks.mjs +1 -0
- package/dist/pages/settings-webhooks.mjs.map +1 -1
- package/dist/pages/storybook.mjs +59 -0
- package/dist/pages/storybook.mjs.map +1 -0
- package/dist/retention/purge.d.mts +3 -0
- package/dist/retention/purge.mjs +2 -1
- package/dist/retention/purge.mjs.map +1 -1
- package/dist/routers/admin.mjs +1 -0
- package/dist/routers/admin.mjs.map +1 -1
- package/dist/routers/assets.mjs +1 -0
- package/dist/routers/assets.mjs.map +1 -1
- package/dist/routers/auth.mjs +1 -0
- package/dist/routers/auth.mjs.map +1 -1
- package/dist/routers/builds.handlers.mjs +15 -3
- package/dist/routers/builds.handlers.mjs.map +1 -1
- package/dist/routers/builds.mjs +5 -2
- package/dist/routers/builds.mjs.map +1 -1
- package/dist/routers/comments.mjs +1 -0
- package/dist/routers/comments.mjs.map +1 -1
- package/dist/routers/helpers.mjs +7 -1
- package/dist/routers/helpers.mjs.map +1 -1
- package/dist/routers/htmx.mjs +2 -0
- package/dist/routers/htmx.mjs.map +1 -1
- package/dist/routers/labels.mjs +34 -1
- package/dist/routers/labels.mjs.map +1 -1
- package/dist/routers/media.mjs +1 -0
- package/dist/routers/media.mjs.map +1 -1
- package/dist/routers/members.mjs +1 -0
- package/dist/routers/members.mjs.map +1 -1
- package/dist/routers/projects.mjs +1 -0
- package/dist/routers/projects.mjs.map +1 -1
- package/dist/routers/schemas.mjs +40 -4
- package/dist/routers/schemas.mjs.map +1 -1
- package/dist/routers/settings.mjs +22 -0
- package/dist/routers/settings.mjs.map +1 -1
- package/dist/routers/snapshots.mjs +1 -0
- package/dist/routers/snapshots.mjs.map +1 -1
- package/dist/routers/status-configs.mjs +1 -0
- package/dist/routers/status-configs.mjs.map +1 -1
- package/dist/routers/storybook.mjs +113 -0
- package/dist/routers/storybook.mjs.map +1 -0
- package/dist/routers/tokens.mjs +1 -0
- package/dist/routers/tokens.mjs.map +1 -1
- package/dist/routers/ui.mjs +25 -2
- package/dist/routers/ui.mjs.map +1 -1
- package/dist/routers/webhooks.mjs +1 -0
- package/dist/routers/webhooks.mjs.map +1 -1
- package/dist/schema-tables.mjs +166 -0
- package/dist/schema-tables.mjs.map +1 -0
- package/dist/schema.d.mts +194 -4352
- package/dist/schema.mjs +26 -136
- package/dist/schema.mjs.map +1 -1
- package/dist/store.mjs +2 -0
- package/dist/store.mjs.map +1 -1
- package/dist/types.d.mts +14 -1
- package/dist/types.mjs +8 -0
- package/dist/types.mjs.map +1 -1
- package/dist/ui/components.mjs +5 -0
- package/dist/ui/components.mjs.map +1 -1
- package/dist/ui/document.d.mts +1 -0
- package/dist/ui/document.mjs +10 -0
- package/dist/ui/document.mjs.map +1 -1
- package/dist/ui/styles.mjs +1 -0
- package/dist/ui/styles.mjs.map +1 -1
- package/dist/ui/theme.mjs +2 -0
- package/dist/ui/theme.mjs.map +1 -1
- package/dist/urls.d.mts +8 -0
- package/dist/urls.mjs +7 -0
- package/dist/urls.mjs.map +1 -1
- package/dist/utils/hash.mjs +3 -0
- package/dist/utils/hash.mjs.map +1 -1
- package/dist/utils/paths.d.mts +5 -0
- package/dist/utils/paths.mjs +5 -0
- package/dist/utils/paths.mjs.map +1 -1
- package/dist/utils/ulid.d.mts +2 -0
- package/dist/utils/ulid.mjs +5 -3
- package/dist/utils/ulid.mjs.map +1 -1
- package/package.json +76 -67
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projects.mjs","names":["forbiddenResponse"],"sources":["../../src/routers/projects.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport type { ShelfApp } from \"../index.tsx\";\n\nimport { LabelModel } from \"../models/label.ts\";\nimport { ProjectModel } from \"../models/project.ts\";\nimport { getStore } from \"../store.ts\";\nimport type { ProjectRole } from \"../types.ts\";\nimport { forbidden, requireSiteAdmin, resolveAuthorizedProject } from \"./helpers.ts\";\nimport {\n badRequest,\n forbidden as forbiddenResponse,\n notFound,\n projectCreateSchema,\n projectSchema,\n projectUpdateSchema,\n unauthorized,\n} from \"./schemas.ts\";\n\nconst VIEW_ROLES: readonly ProjectRole[] = [\"viewer\", \"developer\", \"approver\", \"admin\"];\nconst ADMIN_ROLES: readonly ProjectRole[] = [\"admin\"];\n\nfunction requireSessionUser(): void {\n if (!getStore().authEnabled) {\n return;\n }\n if (!getStore().user) {\n forbidden();\n }\n}\n\nconst listProjectsRoute = createRoute({\n method: \"get\",\n path: \"/api/v1/projects\",\n responses: {\n 200: { content: { \"application/json\": { schema: projectSchema.array() } }, description: \"List projects\" },\n ...unauthorized,\n },\n});\n\nconst createProjectRoute = createRoute({\n method: \"post\",\n path: \"/api/v1/projects\",\n request: { body: { content: { \"application/json\": { schema: projectCreateSchema } } } },\n responses: {\n 201: { content: { \"application/json\": { schema: projectSchema } }, description: \"Created project\" },\n ...badRequest,\n ...forbiddenResponse,\n ...unauthorized,\n },\n});\n\nconst getProjectRoute = createRoute({\n method: \"get\",\n path: \"/api/v1/projects/{slug}\",\n request: { params: z.object({ slug: z.string() }) },\n responses: {\n 200: { content: { \"application/json\": { schema: projectSchema } }, description: \"Fetch a project\" },\n ...notFound,\n ...unauthorized,\n },\n});\n\nconst updateProjectRoute = createRoute({\n method: \"patch\",\n path: \"/api/v1/projects/{slug}\",\n request: {\n params: z.object({ slug: z.string() }),\n body: { content: { \"application/json\": { schema: projectUpdateSchema } } },\n },\n responses: {\n 200: { content: { \"application/json\": { schema: projectSchema } }, description: \"Updated project\" },\n ...forbiddenResponse,\n ...notFound,\n },\n});\n\nconst deleteProjectRoute = createRoute({\n method: \"delete\",\n path: \"/api/v1/projects/{slug}\",\n request: { params: z.object({ slug: z.string() }) },\n responses: {\n 204: { description: \"Deleted project\" },\n ...forbiddenResponse,\n ...notFound,\n },\n});\n\nexport function registerProjects(app: ShelfApp): void {\n app.openapi(listProjectsRoute, async (c) => {\n requireSessionUser();\n const projects = new ProjectModel(getStore().db);\n return c.json(await projects.list());\n });\n\n app.openapi(createProjectRoute, async (c) => {\n requireSiteAdmin();\n const body = c.req.valid(\"json\");\n const projects = new ProjectModel(getStore().db);\n const project = await projects.create(body);\n await new LabelModel(getStore().db).seedFor(project.id);\n return c.json(project, 201);\n });\n\n app.openapi(getProjectRoute, async (c) => {\n const { slug } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...VIEW_ROLES);\n return c.json(project);\n });\n\n app.openapi(updateProjectRoute, async (c) => {\n const { slug } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...ADMIN_ROLES);\n const updated = await new ProjectModel(getStore().db).update(project.id, c.req.valid(\"json\"));\n return c.json(updated);\n });\n\n app.openapi(deleteProjectRoute, async (c) => {\n requireSiteAdmin();\n const { slug } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...VIEW_ROLES);\n await new ProjectModel(getStore().db).remove(project.id);\n return c.body(null, 204);\n });\n}"],"mappings":";;;;;;;;;;AAkBA,MAAM,aAAqC;CAAC;CAAU;CAAa;CAAY;AAAO;AACtF,MAAM,cAAsC,CAAC,OAAO;AAEpD,SAAS,qBAA2B;CAClC,IAAI,CAAC,SAAS,CAAC,CAAC,aACd;CAEF,IAAI,CAAC,SAAS,CAAC,CAAC,MACd,UAAU;AAEd;AAEA,MAAM,oBAAoB,YAAY;CACpC,QAAQ;CACR,MAAM;CACN,WAAW;EACT,KAAK;GAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,cAAc,MAAM,EAAE,EAAE;GAAG,aAAa;EAAgB;EACxG,GAAG;CACL;AACF,CAAC;AAED,MAAM,qBAAqB,YAAY;CACrC,QAAQ;CACR,MAAM;CACN,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,oBAAoB,EAAE,EAAE,EAAE;CACtF,WAAW;EACT,KAAK;GAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,cAAc,EAAE;GAAG,aAAa;EAAkB;EAClG,GAAG;EACH,GAAGA;EACH,GAAG;CACL;AACF,CAAC;AAED,MAAM,kBAAkB,YAAY;CAClC,QAAQ;CACR,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE;CAClD,WAAW;EACT,KAAK;GAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,cAAc,EAAE;GAAG,aAAa;EAAkB;EAClG,GAAG;EACH,GAAG;CACL;AACF,CAAC;AAED,MAAM,qBAAqB,YAAY;CACrC,QAAQ;CACR,MAAM;CACN,SAAS;EACP,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;EACrC,MAAM,EAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,oBAAoB,EAAE,EAAE;CAC3E;CACA,WAAW;EACT,KAAK;GAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,cAAc,EAAE;GAAG,aAAa;EAAkB;EAClG,GAAGA;EACH,GAAG;CACL;AACF,CAAC;AAED,MAAM,qBAAqB,YAAY;CACrC,QAAQ;CACR,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE;CAClD,WAAW;EACT,KAAK,EAAE,aAAa,kBAAkB;EACtC,GAAGA;EACH,GAAG;CACL;AACF,CAAC
|
|
1
|
+
{"version":3,"file":"projects.mjs","names":["forbiddenResponse"],"sources":["../../src/routers/projects.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport type { ShelfApp } from \"../index.tsx\";\n\nimport { LabelModel } from \"../models/label.ts\";\nimport { ProjectModel } from \"../models/project.ts\";\nimport { getStore } from \"../store.ts\";\nimport type { ProjectRole } from \"../types.ts\";\nimport { forbidden, requireSiteAdmin, resolveAuthorizedProject } from \"./helpers.ts\";\nimport {\n badRequest,\n forbidden as forbiddenResponse,\n notFound,\n projectCreateSchema,\n projectSchema,\n projectUpdateSchema,\n unauthorized,\n} from \"./schemas.ts\";\n\nconst VIEW_ROLES: readonly ProjectRole[] = [\"viewer\", \"developer\", \"approver\", \"admin\"];\nconst ADMIN_ROLES: readonly ProjectRole[] = [\"admin\"];\n\nfunction requireSessionUser(): void {\n if (!getStore().authEnabled) {\n return;\n }\n if (!getStore().user) {\n forbidden();\n }\n}\n\nconst listProjectsRoute = createRoute({\n method: \"get\",\n path: \"/api/v1/projects\",\n responses: {\n 200: { content: { \"application/json\": { schema: projectSchema.array() } }, description: \"List projects\" },\n ...unauthorized,\n },\n});\n\nconst createProjectRoute = createRoute({\n method: \"post\",\n path: \"/api/v1/projects\",\n request: { body: { content: { \"application/json\": { schema: projectCreateSchema } } } },\n responses: {\n 201: { content: { \"application/json\": { schema: projectSchema } }, description: \"Created project\" },\n ...badRequest,\n ...forbiddenResponse,\n ...unauthorized,\n },\n});\n\nconst getProjectRoute = createRoute({\n method: \"get\",\n path: \"/api/v1/projects/{slug}\",\n request: { params: z.object({ slug: z.string() }) },\n responses: {\n 200: { content: { \"application/json\": { schema: projectSchema } }, description: \"Fetch a project\" },\n ...notFound,\n ...unauthorized,\n },\n});\n\nconst updateProjectRoute = createRoute({\n method: \"patch\",\n path: \"/api/v1/projects/{slug}\",\n request: {\n params: z.object({ slug: z.string() }),\n body: { content: { \"application/json\": { schema: projectUpdateSchema } } },\n },\n responses: {\n 200: { content: { \"application/json\": { schema: projectSchema } }, description: \"Updated project\" },\n ...forbiddenResponse,\n ...notFound,\n },\n});\n\nconst deleteProjectRoute = createRoute({\n method: \"delete\",\n path: \"/api/v1/projects/{slug}\",\n request: { params: z.object({ slug: z.string() }) },\n responses: {\n 204: { description: \"Deleted project\" },\n ...forbiddenResponse,\n ...notFound,\n },\n});\n\n/** Register the project list, create, fetch, update, and delete endpoints. */\nexport function registerProjects(app: ShelfApp): void {\n app.openapi(listProjectsRoute, async (c) => {\n requireSessionUser();\n const projects = new ProjectModel(getStore().db);\n return c.json(await projects.list());\n });\n\n app.openapi(createProjectRoute, async (c) => {\n requireSiteAdmin();\n const body = c.req.valid(\"json\");\n const projects = new ProjectModel(getStore().db);\n const project = await projects.create(body);\n await new LabelModel(getStore().db).seedFor(project.id);\n return c.json(project, 201);\n });\n\n app.openapi(getProjectRoute, async (c) => {\n const { slug } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...VIEW_ROLES);\n return c.json(project);\n });\n\n app.openapi(updateProjectRoute, async (c) => {\n const { slug } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...ADMIN_ROLES);\n const updated = await new ProjectModel(getStore().db).update(project.id, c.req.valid(\"json\"));\n return c.json(updated);\n });\n\n app.openapi(deleteProjectRoute, async (c) => {\n requireSiteAdmin();\n const { slug } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...VIEW_ROLES);\n await new ProjectModel(getStore().db).remove(project.id);\n return c.body(null, 204);\n });\n}"],"mappings":"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"}
|
package/dist/routers/schemas.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { BUILD_STATUSES, PROJECT_ROLES, SNAPSHOT_STATUSES } from "../types.mjs";
|
|
|
5
5
|
import { z } from "@hono/zod-openapi";
|
|
6
6
|
//#region src/routers/schemas.ts
|
|
7
7
|
const storybookMetaSchema = z.record(z.string(), z.unknown()).nullable().optional();
|
|
8
|
+
/** OpenAPI schema for a project record. */
|
|
8
9
|
const projectSchema = z.object({
|
|
9
10
|
id: z.string(),
|
|
10
11
|
name: z.string(),
|
|
@@ -15,15 +16,21 @@ const projectSchema = z.object({
|
|
|
15
16
|
maxDiffRatio: z.number(),
|
|
16
17
|
publicBranchRegex: z.string().nullable(),
|
|
17
18
|
storybookMeta: z.string().nullable().optional(),
|
|
19
|
+
executePlay: z.boolean(),
|
|
20
|
+
playTimeoutMs: z.number(),
|
|
18
21
|
createdAt: z.string(),
|
|
19
22
|
updatedAt: z.string()
|
|
20
23
|
}).openapi("Project");
|
|
24
|
+
/** OpenAPI schema for creating a project. */
|
|
21
25
|
const projectCreateSchema = z.object({
|
|
22
26
|
name: z.string().min(1),
|
|
23
27
|
gitRepository: z.string().optional(),
|
|
24
28
|
gitDefaultBranch: z.string().optional(),
|
|
25
|
-
storybookMeta: storybookMetaSchema
|
|
29
|
+
storybookMeta: storybookMetaSchema,
|
|
30
|
+
executePlay: z.boolean().optional(),
|
|
31
|
+
playTimeoutMs: z.number().int().min(1e3).max(3e4).optional()
|
|
26
32
|
}).openapi("ProjectCreateInput");
|
|
33
|
+
/** OpenAPI schema for patching a project. */
|
|
27
34
|
const projectUpdateSchema = z.object({
|
|
28
35
|
name: z.string().optional(),
|
|
29
36
|
gitRepository: z.string().optional(),
|
|
@@ -31,8 +38,11 @@ const projectUpdateSchema = z.object({
|
|
|
31
38
|
pixelThreshold: z.number().optional(),
|
|
32
39
|
maxDiffRatio: z.number().optional(),
|
|
33
40
|
publicBranchRegex: z.string().nullable().optional(),
|
|
34
|
-
storybookMeta: storybookMetaSchema
|
|
41
|
+
storybookMeta: storybookMetaSchema,
|
|
42
|
+
executePlay: z.boolean().optional(),
|
|
43
|
+
playTimeoutMs: z.number().int().min(1e3).max(3e4).optional()
|
|
35
44
|
}).openapi("ProjectUpdateInput");
|
|
45
|
+
/** OpenAPI schema for a build record. */
|
|
36
46
|
const buildSchema = z.object({
|
|
37
47
|
id: z.string(),
|
|
38
48
|
projectId: z.string(),
|
|
@@ -51,6 +61,7 @@ const buildSchema = z.object({
|
|
|
51
61
|
createdAt: z.string(),
|
|
52
62
|
updatedAt: z.string()
|
|
53
63
|
}).openapi("Build");
|
|
64
|
+
/** OpenAPI schema for a snapshot record. */
|
|
54
65
|
const snapshotSchema = z.object({
|
|
55
66
|
id: z.string(),
|
|
56
67
|
projectId: z.string(),
|
|
@@ -73,6 +84,7 @@ const snapshotSchema = z.object({
|
|
|
73
84
|
createdAt: z.string(),
|
|
74
85
|
updatedAt: z.string()
|
|
75
86
|
}).openapi("Snapshot");
|
|
87
|
+
/** OpenAPI schema for a build comment. */
|
|
76
88
|
const commentSchema = z.object({
|
|
77
89
|
id: z.string(),
|
|
78
90
|
projectId: z.string(),
|
|
@@ -85,11 +97,13 @@ const commentSchema = z.object({
|
|
|
85
97
|
createdAt: z.string(),
|
|
86
98
|
updatedAt: z.string()
|
|
87
99
|
}).openapi("Comment");
|
|
100
|
+
/** OpenAPI schema for creating a build comment. */
|
|
88
101
|
const commentCreateSchema = z.object({
|
|
89
102
|
body: z.string().min(1),
|
|
90
103
|
snapshotId: z.string().optional(),
|
|
91
104
|
parentId: z.string().optional()
|
|
92
105
|
}).openapi("CommentCreateInput");
|
|
106
|
+
/** OpenAPI schema for a project label type. */
|
|
93
107
|
const labelTypeSchema = z.object({
|
|
94
108
|
id: z.string(),
|
|
95
109
|
projectId: z.string(),
|
|
@@ -99,12 +113,20 @@ const labelTypeSchema = z.object({
|
|
|
99
113
|
color: z.string().nullable(),
|
|
100
114
|
createdAt: z.string()
|
|
101
115
|
}).openapi("LabelType");
|
|
116
|
+
/** OpenAPI schema for creating a label type. */
|
|
102
117
|
const labelTypeCreateSchema = z.object({
|
|
103
118
|
key: z.string().min(1),
|
|
104
119
|
name: z.string().min(1),
|
|
105
120
|
linkTemplate: z.string().optional(),
|
|
106
121
|
color: z.string().optional()
|
|
107
122
|
}).openapi("LabelTypeCreateInput");
|
|
123
|
+
/** OpenAPI schema for patching a label type. */
|
|
124
|
+
const labelTypeUpdateSchema = z.object({
|
|
125
|
+
name: z.string().min(1).optional(),
|
|
126
|
+
linkTemplate: z.string().nullable().optional(),
|
|
127
|
+
color: z.string().nullable().optional()
|
|
128
|
+
}).openapi("LabelTypeUpdateInput");
|
|
129
|
+
/** OpenAPI schema for a public API token (hash omitted). */
|
|
108
130
|
const tokenPublicSchema = z.object({
|
|
109
131
|
id: z.string(),
|
|
110
132
|
projectId: z.string(),
|
|
@@ -112,11 +134,14 @@ const tokenPublicSchema = z.object({
|
|
|
112
134
|
lastUsedAt: z.string().nullable(),
|
|
113
135
|
createdAt: z.string()
|
|
114
136
|
}).openapi("Token");
|
|
137
|
+
/** OpenAPI schema for creating an API token. */
|
|
115
138
|
const tokenCreateSchema = z.object({ name: z.string().min(1) }).openapi("TokenCreateInput");
|
|
139
|
+
/** OpenAPI schema for a newly created token including its secret value. */
|
|
116
140
|
const tokenCreatedSchema = z.object({
|
|
117
141
|
name: z.string(),
|
|
118
142
|
token: z.string()
|
|
119
143
|
}).openapi("TokenCreated");
|
|
144
|
+
/** OpenAPI schema for a project member record. */
|
|
120
145
|
const memberSchema = z.object({
|
|
121
146
|
id: z.string(),
|
|
122
147
|
projectId: z.string(),
|
|
@@ -124,26 +149,32 @@ const memberSchema = z.object({
|
|
|
124
149
|
role: z.enum(PROJECT_ROLES),
|
|
125
150
|
createdAt: z.string()
|
|
126
151
|
}).openapi("ProjectMember");
|
|
152
|
+
/** OpenAPI schema for updating a member's role. */
|
|
127
153
|
const memberRoleSchema = z.object({ role: z.enum(PROJECT_ROLES) }).openapi("MemberRoleInput");
|
|
154
|
+
/** OpenAPI schema for adding a member to a project. */
|
|
128
155
|
const memberSetSchema = z.object({
|
|
129
156
|
userId: z.string().min(1),
|
|
130
157
|
role: z.enum(PROJECT_ROLES)
|
|
131
158
|
}).openapi("MemberSetInput");
|
|
159
|
+
/** OpenAPI schema for a public webhook (secret omitted). */
|
|
132
160
|
const webhookPublicSchema = z.object({
|
|
133
161
|
id: z.string(),
|
|
134
162
|
url: z.string(),
|
|
135
163
|
events: z.array(z.string())
|
|
136
164
|
}).openapi("Webhook");
|
|
165
|
+
/** OpenAPI schema for creating a webhook. */
|
|
137
166
|
const webhookCreateSchema = z.object({
|
|
138
|
-
url: z.url(),
|
|
167
|
+
url: z.string().url(),
|
|
139
168
|
events: z.array(z.string().min(1)).optional()
|
|
140
169
|
}).openapi("WebhookCreateInput");
|
|
170
|
+
/** OpenAPI schema for a newly created webhook including its signing secret. */
|
|
141
171
|
const webhookCreatedSchema = z.object({
|
|
142
172
|
id: z.string(),
|
|
143
173
|
url: z.string(),
|
|
144
174
|
events: z.array(z.string()),
|
|
145
175
|
secret: z.string()
|
|
146
176
|
}).openapi("WebhookCreated");
|
|
177
|
+
/** OpenAPI schema for a git status-check configuration. */
|
|
147
178
|
const statusConfigSchema = z.object({
|
|
148
179
|
id: z.string(),
|
|
149
180
|
projectId: z.string(),
|
|
@@ -153,14 +184,19 @@ const statusConfigSchema = z.object({
|
|
|
153
184
|
createdAt: z.string(),
|
|
154
185
|
updatedAt: z.string()
|
|
155
186
|
}).openapi("StatusConfig");
|
|
187
|
+
/** OpenAPI schema for creating a git status-check configuration. */
|
|
156
188
|
const statusConfigCreateSchema = z.object({
|
|
157
189
|
provider: z.string().min(1),
|
|
158
190
|
config: z.record(z.string(), z.unknown()),
|
|
159
191
|
token: z.string().min(1)
|
|
160
192
|
}).openapi("StatusConfigCreateInput");
|
|
193
|
+
/** OpenAPI schema for a generic `{ ok: true }` acknowledgement. */
|
|
161
194
|
const okSchema = z.object({ ok: z.boolean() }).openapi("Ok");
|
|
195
|
+
/** OpenAPI schema for an error response message. */
|
|
162
196
|
const errorSchema = z.object({ message: z.string() }).openapi("Error");
|
|
197
|
+
/** OpenAPI schema for the retention purge result. */
|
|
163
198
|
const purgeSchema = z.object({ removedBuilds: z.number() }).openapi("PurgeResult");
|
|
199
|
+
/** OpenAPI schema for the retention purge input. */
|
|
164
200
|
const purgeInputSchema = z.object({ ttlDays: z.number().optional() }).openapi("PurgeInput");
|
|
165
201
|
/** Shared response spec for a 400 Bad Request. */
|
|
166
202
|
const badRequest = { 400: {
|
|
@@ -183,6 +219,6 @@ const notFound = { 404: {
|
|
|
183
219
|
description: "Not found"
|
|
184
220
|
} };
|
|
185
221
|
//#endregion
|
|
186
|
-
export { badRequest, buildSchema, commentCreateSchema, commentSchema, errorSchema, forbidden, labelTypeCreateSchema, labelTypeSchema, memberRoleSchema, memberSchema, memberSetSchema, notFound, okSchema, projectCreateSchema, projectSchema, projectUpdateSchema, purgeInputSchema, purgeSchema, snapshotSchema, statusConfigCreateSchema, statusConfigSchema, tokenCreateSchema, tokenCreatedSchema, tokenPublicSchema, unauthorized, webhookCreateSchema, webhookCreatedSchema, webhookPublicSchema };
|
|
222
|
+
export { badRequest, buildSchema, commentCreateSchema, commentSchema, errorSchema, forbidden, labelTypeCreateSchema, labelTypeSchema, labelTypeUpdateSchema, memberRoleSchema, memberSchema, memberSetSchema, notFound, okSchema, projectCreateSchema, projectSchema, projectUpdateSchema, purgeInputSchema, purgeSchema, snapshotSchema, statusConfigCreateSchema, statusConfigSchema, tokenCreateSchema, tokenCreatedSchema, tokenPublicSchema, unauthorized, webhookCreateSchema, webhookCreatedSchema, webhookPublicSchema };
|
|
187
223
|
|
|
188
224
|
//# sourceMappingURL=schemas.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.mjs","names":[],"sources":["../../src/routers/schemas.ts"],"sourcesContent":["import { z } from \"@hono/zod-openapi\";\n\nimport { BUILD_STATUSES, PROJECT_ROLES, SNAPSHOT_STATUSES } from \"../types.ts\";\n\nconst storybookMetaSchema = z.record(z.string(), z.unknown()).nullable().optional();\n\nexport const projectSchema = z.object({\n id: z.string(),\n name: z.string(),\n slug: z.string(),\n gitRepository: z.string().nullable(),\n gitDefaultBranch: z.string(),\n pixelThreshold: z.number(),\n maxDiffRatio: z.number(),\n publicBranchRegex: z.string().nullable(),\n storybookMeta: z.string().nullable().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n}).openapi(\"Project\");\n\nexport const projectCreateSchema = z.object({\n name: z.string().min(1),\n gitRepository: z.string().optional(),\n gitDefaultBranch: z.string().optional(),\n storybookMeta: storybookMetaSchema,\n}).openapi(\"ProjectCreateInput\");\n\nexport const projectUpdateSchema = z.object({\n name: z.string().optional(),\n gitRepository: z.string().optional(),\n gitDefaultBranch: z.string().optional(),\n pixelThreshold: z.number().optional(),\n maxDiffRatio: z.number().optional(),\n publicBranchRegex: z.string().nullable().optional(),\n storybookMeta: storybookMetaSchema,\n}).openapi(\"ProjectUpdateInput\");\n\nexport const buildSchema = z.object({\n id: z.string(),\n projectId: z.string(),\n gitSha: z.string(),\n gitBranch: z.string(),\n isDefault: z.boolean(),\n authorEmail: z.string().nullable(),\n authorName: z.string().nullable(),\n message: z.string().nullable(),\n public: z.boolean(),\n status: z.enum(BUILD_STATUSES),\n snapshotCount: z.number(),\n changedCount: z.number(),\n approvedCount: z.number(),\n rejectedCount: z.number(),\n createdAt: z.string(),\n updatedAt: z.string(),\n}).openapi(\"Build\");\n\nexport const snapshotSchema = z.object({\n id: z.string(),\n projectId: z.string(),\n buildId: z.string(),\n storyId: z.string(),\n storyName: z.string(),\n storyTitle: z.string(),\n storyImportPath: z.string().nullable(),\n viewportName: z.string(),\n viewportWidth: z.number(),\n viewportHeight: z.number(),\n screenshotPath: z.string(),\n diffPath: z.string().nullable(),\n diffPixels: z.number().nullable(),\n diffRatio: z.number().nullable(),\n diffPassed: z.boolean().nullable(),\n status: z.enum(SNAPSHOT_STATUSES),\n reviewedBy: z.string().nullable(),\n reviewedAt: z.string().nullable(),\n createdAt: z.string(),\n updatedAt: z.string(),\n}).openapi(\"Snapshot\");\n\nexport const commentSchema = z.object({\n id: z.string(),\n projectId: z.string(),\n buildId: z.string(),\n snapshotId: z.string().nullable(),\n userId: z.string(),\n body: z.string(),\n parentId: z.string().nullable(),\n resolved: z.boolean(),\n createdAt: z.string(),\n updatedAt: z.string(),\n}).openapi(\"Comment\");\n\nexport const commentCreateSchema = z.object({\n body: z.string().min(1),\n snapshotId: z.string().optional(),\n parentId: z.string().optional(),\n}).openapi(\"CommentCreateInput\");\n\nexport const labelTypeSchema = z.object({\n id: z.string(),\n projectId: z.string(),\n key: z.string(),\n name: z.string(),\n linkTemplate: z.string().nullable(),\n color: z.string().nullable(),\n createdAt: z.string(),\n}).openapi(\"LabelType\");\n\nexport const labelTypeCreateSchema = z.object({\n key: z.string().min(1),\n name: z.string().min(1),\n linkTemplate: z.string().optional(),\n color: z.string().optional(),\n}).openapi(\"LabelTypeCreateInput\");\n\nexport const tokenPublicSchema = z.object({\n id: z.string(),\n projectId: z.string(),\n name: z.string(),\n lastUsedAt: z.string().nullable(),\n createdAt: z.string(),\n}).openapi(\"Token\");\n\nexport const tokenCreateSchema = z.object({ name: z.string().min(1) }).openapi(\"TokenCreateInput\");\n\nexport const tokenCreatedSchema = z.object({\n name: z.string(),\n token: z.string(),\n}).openapi(\"TokenCreated\");\n\nexport const memberSchema = z.object({\n id: z.string(),\n projectId: z.string(),\n userId: z.string(),\n role: z.enum(PROJECT_ROLES),\n createdAt: z.string(),\n}).openapi(\"ProjectMember\");\n\nexport const memberRoleSchema = z.object({ role: z.enum(PROJECT_ROLES) }).openapi(\"MemberRoleInput\");\n\nexport const memberSetSchema = z.object({\n userId: z.string().min(1),\n role: z.enum(PROJECT_ROLES),\n}).openapi(\"MemberSetInput\");\n\nexport const webhookPublicSchema = z.object({\n id: z.string(),\n url: z.string(),\n events: z.array(z.string()),\n}).openapi(\"Webhook\");\n\nexport const webhookCreateSchema = z.object({\n url: z.url(),\n events: z.array(z.string().min(1)).optional(),\n}).openapi(\"WebhookCreateInput\");\n\nexport const webhookCreatedSchema = z.object({\n id: z.string(),\n url: z.string(),\n events: z.array(z.string()),\n secret: z.string(),\n}).openapi(\"WebhookCreated\");\n\nexport const statusConfigSchema = z.object({\n id: z.string(),\n projectId: z.string(),\n provider: z.string(),\n config: z.record(z.string(), z.unknown()),\n hasToken: z.boolean(),\n createdAt: z.string(),\n updatedAt: z.string(),\n}).openapi(\"StatusConfig\");\n\nexport const statusConfigCreateSchema = z.object({\n provider: z.string().min(1),\n config: z.record(z.string(), z.unknown()),\n token: z.string().min(1),\n}).openapi(\"StatusConfigCreateInput\");\n\nexport const okSchema = z.object({ ok: z.boolean() }).openapi(\"Ok\");\n\nexport const errorSchema = z.object({ message: z.string() }).openapi(\"Error\");\n\nexport const purgeSchema = z.object({ removedBuilds: z.number() }).openapi(\"PurgeResult\");\n\nexport const purgeInputSchema = z.object({ ttlDays: z.number().optional() }).openapi(\"PurgeInput\");\n\n/** Shared response spec for a 400 Bad Request. */\nexport const badRequest = {\n 400: { content: { \"application/json\": { schema: errorSchema } }, description: \"Bad request\" },\n} as const;\n\n/** Shared response spec for a 401 Unauthorized. */\nexport const unauthorized = {\n 401: { content: { \"application/json\": { schema: errorSchema } }, description: \"Unauthorized\" },\n} as const;\n\n/** Shared response spec for a 403 Forbidden. */\nexport const forbidden = {\n 403: { content: { \"application/json\": { schema: errorSchema } }, description: \"Forbidden\" },\n} as const;\n\n/** Shared response spec for a 404 Not Found. */\nexport const notFound = {\n 404: { content: { \"application/json\": { schema: errorSchema } }, description: \"Not found\" },\n} as const;"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"schemas.mjs","names":[],"sources":["../../src/routers/schemas.ts"],"sourcesContent":["import { z } from \"@hono/zod-openapi\";\n\nimport { BUILD_STATUSES, PROJECT_ROLES, SNAPSHOT_STATUSES } from \"../types.ts\";\n\nconst storybookMetaSchema = z.record(z.string(), z.unknown()).nullable().optional();\n\n/** OpenAPI schema for a project record. */\nexport const projectSchema = z.object({\n id: z.string(),\n name: z.string(),\n slug: z.string(),\n gitRepository: z.string().nullable(),\n gitDefaultBranch: z.string(),\n pixelThreshold: z.number(),\n maxDiffRatio: z.number(),\n publicBranchRegex: z.string().nullable(),\n storybookMeta: z.string().nullable().optional(),\n executePlay: z.boolean(),\n playTimeoutMs: z.number(),\n createdAt: z.string(),\n updatedAt: z.string(),\n}).openapi(\"Project\");\n\n/** OpenAPI schema for creating a project. */\nexport const projectCreateSchema = z.object({\n name: z.string().min(1),\n gitRepository: z.string().optional(),\n gitDefaultBranch: z.string().optional(),\n storybookMeta: storybookMetaSchema,\n executePlay: z.boolean().optional(),\n playTimeoutMs: z.number().int().min(1000).max(30_000).optional(),\n}).openapi(\"ProjectCreateInput\");\n\n/** OpenAPI schema for patching a project. */\nexport const projectUpdateSchema = z.object({\n name: z.string().optional(),\n gitRepository: z.string().optional(),\n gitDefaultBranch: z.string().optional(),\n pixelThreshold: z.number().optional(),\n maxDiffRatio: z.number().optional(),\n publicBranchRegex: z.string().nullable().optional(),\n storybookMeta: storybookMetaSchema,\n executePlay: z.boolean().optional(),\n playTimeoutMs: z.number().int().min(1000).max(30_000).optional(),\n}).openapi(\"ProjectUpdateInput\");\n\n/** OpenAPI schema for a build record. */\nexport const buildSchema = z.object({\n id: z.string(),\n projectId: z.string(),\n gitSha: z.string(),\n gitBranch: z.string(),\n isDefault: z.boolean(),\n authorEmail: z.string().nullable(),\n authorName: z.string().nullable(),\n message: z.string().nullable(),\n public: z.boolean(),\n status: z.enum(BUILD_STATUSES),\n snapshotCount: z.number(),\n changedCount: z.number(),\n approvedCount: z.number(),\n rejectedCount: z.number(),\n createdAt: z.string(),\n updatedAt: z.string(),\n}).openapi(\"Build\");\n\n/** OpenAPI schema for a snapshot record. */\nexport const snapshotSchema = z.object({\n id: z.string(),\n projectId: z.string(),\n buildId: z.string(),\n storyId: z.string(),\n storyName: z.string(),\n storyTitle: z.string(),\n storyImportPath: z.string().nullable(),\n viewportName: z.string(),\n viewportWidth: z.number(),\n viewportHeight: z.number(),\n screenshotPath: z.string(),\n diffPath: z.string().nullable(),\n diffPixels: z.number().nullable(),\n diffRatio: z.number().nullable(),\n diffPassed: z.boolean().nullable(),\n status: z.enum(SNAPSHOT_STATUSES),\n reviewedBy: z.string().nullable(),\n reviewedAt: z.string().nullable(),\n createdAt: z.string(),\n updatedAt: z.string(),\n}).openapi(\"Snapshot\");\n\n/** OpenAPI schema for a build comment. */\nexport const commentSchema = z.object({\n id: z.string(),\n projectId: z.string(),\n buildId: z.string(),\n snapshotId: z.string().nullable(),\n userId: z.string(),\n body: z.string(),\n parentId: z.string().nullable(),\n resolved: z.boolean(),\n createdAt: z.string(),\n updatedAt: z.string(),\n}).openapi(\"Comment\");\n\n/** OpenAPI schema for creating a build comment. */\nexport const commentCreateSchema = z.object({\n body: z.string().min(1),\n snapshotId: z.string().optional(),\n parentId: z.string().optional(),\n}).openapi(\"CommentCreateInput\");\n\n/** OpenAPI schema for a project label type. */\nexport const labelTypeSchema = z.object({\n id: z.string(),\n projectId: z.string(),\n key: z.string(),\n name: z.string(),\n linkTemplate: z.string().nullable(),\n color: z.string().nullable(),\n createdAt: z.string(),\n}).openapi(\"LabelType\");\n\n/** OpenAPI schema for creating a label type. */\nexport const labelTypeCreateSchema = z.object({\n key: z.string().min(1),\n name: z.string().min(1),\n linkTemplate: z.string().optional(),\n color: z.string().optional(),\n}).openapi(\"LabelTypeCreateInput\");\n\n/** OpenAPI schema for patching a label type. */\nexport const labelTypeUpdateSchema = z.object({\n name: z.string().min(1).optional(),\n linkTemplate: z.string().nullable().optional(),\n color: z.string().nullable().optional(),\n}).openapi(\"LabelTypeUpdateInput\");\n\n/** OpenAPI schema for a public API token (hash omitted). */\nexport const tokenPublicSchema = z.object({\n id: z.string(),\n projectId: z.string(),\n name: z.string(),\n lastUsedAt: z.string().nullable(),\n createdAt: z.string(),\n}).openapi(\"Token\");\n\n/** OpenAPI schema for creating an API token. */\nexport const tokenCreateSchema = z.object({ name: z.string().min(1) }).openapi(\"TokenCreateInput\");\n\n/** OpenAPI schema for a newly created token including its secret value. */\nexport const tokenCreatedSchema = z.object({\n name: z.string(),\n token: z.string(),\n}).openapi(\"TokenCreated\");\n\n/** OpenAPI schema for a project member record. */\nexport const memberSchema = z.object({\n id: z.string(),\n projectId: z.string(),\n userId: z.string(),\n role: z.enum(PROJECT_ROLES),\n createdAt: z.string(),\n}).openapi(\"ProjectMember\");\n\n/** OpenAPI schema for updating a member's role. */\nexport const memberRoleSchema = z.object({ role: z.enum(PROJECT_ROLES) }).openapi(\"MemberRoleInput\");\n\n/** OpenAPI schema for adding a member to a project. */\nexport const memberSetSchema = z.object({\n userId: z.string().min(1),\n role: z.enum(PROJECT_ROLES),\n}).openapi(\"MemberSetInput\");\n\n/** OpenAPI schema for a public webhook (secret omitted). */\nexport const webhookPublicSchema = z.object({\n id: z.string(),\n url: z.string(),\n events: z.array(z.string()),\n}).openapi(\"Webhook\");\n\n/** OpenAPI schema for creating a webhook. */\nexport const webhookCreateSchema = z.object({\n // oxlint-disable-next-line typescript/no-deprecated -- z.string().url() kept for zod v3 API compat\n url: z.string().url(),\n events: z.array(z.string().min(1)).optional(),\n}).openapi(\"WebhookCreateInput\");\n\n/** OpenAPI schema for a newly created webhook including its signing secret. */\nexport const webhookCreatedSchema = z.object({\n id: z.string(),\n url: z.string(),\n events: z.array(z.string()),\n secret: z.string(),\n}).openapi(\"WebhookCreated\");\n\n/** OpenAPI schema for a git status-check configuration. */\nexport const statusConfigSchema = z.object({\n id: z.string(),\n projectId: z.string(),\n provider: z.string(),\n config: z.record(z.string(), z.unknown()),\n hasToken: z.boolean(),\n createdAt: z.string(),\n updatedAt: z.string(),\n}).openapi(\"StatusConfig\");\n\n/** OpenAPI schema for creating a git status-check configuration. */\nexport const statusConfigCreateSchema = z.object({\n provider: z.string().min(1),\n config: z.record(z.string(), z.unknown()),\n token: z.string().min(1),\n}).openapi(\"StatusConfigCreateInput\");\n\n/** OpenAPI schema for a generic `{ ok: true }` acknowledgement. */\nexport const okSchema = z.object({ ok: z.boolean() }).openapi(\"Ok\");\n\n/** OpenAPI schema for an error response message. */\nexport const errorSchema = z.object({ message: z.string() }).openapi(\"Error\");\n\n/** OpenAPI schema for the retention purge result. */\nexport const purgeSchema = z.object({ removedBuilds: z.number() }).openapi(\"PurgeResult\");\n\n/** OpenAPI schema for the retention purge input. */\nexport const purgeInputSchema = z.object({ ttlDays: z.number().optional() }).openapi(\"PurgeInput\");\n\n/** Shared response spec for a 400 Bad Request. */\nexport const badRequest = {\n 400: { content: { \"application/json\": { schema: errorSchema } }, description: \"Bad request\" },\n} as const;\n\n/** Shared response spec for a 401 Unauthorized. */\nexport const unauthorized = {\n 401: { content: { \"application/json\": { schema: errorSchema } }, description: \"Unauthorized\" },\n} as const;\n\n/** Shared response spec for a 403 Forbidden. */\nexport const forbidden = {\n 403: { content: { \"application/json\": { schema: errorSchema } }, description: \"Forbidden\" },\n} as const;\n\n/** Shared response spec for a 404 Not Found. */\nexport const notFound = {\n 404: { content: { \"application/json\": { schema: errorSchema } }, description: \"Not found\" },\n} as const;"],"mappings":"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"}
|
|
@@ -42,6 +42,7 @@ async function loadSettingsData(slug) {
|
|
|
42
42
|
isAdmin: !authEnabled || user?.role === "admin" || members.some((member) => member.userId === user?.id && member.role === "admin")
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
+
/** Render the project settings page for the given tab, optionally with form state. */
|
|
45
46
|
async function renderSettingsPage(c, tab, formState) {
|
|
46
47
|
const data = await loadSettingsData(c.req.param("slug") ?? "");
|
|
47
48
|
if (!data) notFound("Project not found");
|
|
@@ -67,8 +68,10 @@ async function findProject(slug) {
|
|
|
67
68
|
if (!project) notFound("Project not found");
|
|
68
69
|
return project;
|
|
69
70
|
}
|
|
71
|
+
/** Register the server-rendered project settings pages and their form handlers. */
|
|
70
72
|
function registerSettingsPages(app) {
|
|
71
73
|
app.get("/projects/:slug/settings", async (c) => c.html(await settingsPage(c, "general")));
|
|
74
|
+
app.get("/projects/:slug/settings/tests", async (c) => c.html(await settingsPage(c, "tests")));
|
|
72
75
|
app.get("/projects/:slug/settings/labels", async (c) => c.html(await settingsPage(c, "labels")));
|
|
73
76
|
app.get("/projects/:slug/settings/tokens", async (c) => c.html(await settingsPage(c, "tokens")));
|
|
74
77
|
app.get("/projects/:slug/settings/webhooks", async (c) => c.html(await settingsPage(c, "webhooks")));
|
|
@@ -100,6 +103,25 @@ function registerSettingsPages(app) {
|
|
|
100
103
|
return c.html(await renderSettingsPage(c, "general", { globalError: message }) ?? "", 400);
|
|
101
104
|
}
|
|
102
105
|
});
|
|
106
|
+
app.post("/projects/:slug/settings/tests", async (c) => {
|
|
107
|
+
const slug = c.req.param("slug");
|
|
108
|
+
const project = await findProject(slug);
|
|
109
|
+
const form = await c.req.formData();
|
|
110
|
+
const executePlay = form.get("executePlay") === "true";
|
|
111
|
+
const playTimeoutMsRaw = asString(form.get("playTimeoutMs"));
|
|
112
|
+
const playTimeoutMs = playTimeoutMsRaw ? Number(playTimeoutMsRaw) : void 0;
|
|
113
|
+
if (playTimeoutMs !== void 0 && (Number.isNaN(playTimeoutMs) || playTimeoutMs < 1e3 || playTimeoutMs > 3e4)) return c.html(await renderSettingsPage(c, "tests", { globalError: "Play timeout must be between 1000 and 30000" }) ?? "", 400);
|
|
114
|
+
try {
|
|
115
|
+
await new ProjectModel(getStore().db).update(project.id, {
|
|
116
|
+
executePlay,
|
|
117
|
+
playTimeoutMs: playTimeoutMs ?? void 0
|
|
118
|
+
});
|
|
119
|
+
return hxRedirect(c, `/projects/${slug}/settings/tests`);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
const message = error instanceof Error ? error.message : "Failed to update";
|
|
122
|
+
return c.html(await renderSettingsPage(c, "tests", { globalError: message }) ?? "", 400);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
103
125
|
app.post("/projects/:slug/delete", async (c) => {
|
|
104
126
|
const project = await findProject(c.req.param("slug"));
|
|
105
127
|
await new ProjectModel(getStore().db).remove(project.id);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.mjs","names":[],"sources":["../../src/routers/settings.ts"],"sourcesContent":["import type { Context } from \"hono\";\nimport type { GitHostProvider } from \"../adapters/git-host/index.ts\";\nimport type { ShelfApp } from \"../index.tsx\";\n\nimport { LabelModel } from \"../models/label.ts\";\nimport { MemberModel } from \"../models/member.ts\";\nimport { ProjectModel } from \"../models/project.ts\";\nimport { StatusConfigModel } from \"../models/status-config.ts\";\nimport { TokenModel } from \"../models/token.ts\";\nimport { WebhookModel } from \"../models/webhook.ts\";\nimport { renderProjectSettingsPage, type SettingsFormState, type SettingsTab } from \"../pages/project-settings.tsx\";\nimport type { SettingsMember } from \"../pages/settings-members.tsx\";\nimport type { SettingsStatusConfig } from \"../pages/settings-status.tsx\";\nimport type { SettingsWebhook } from \"../pages/settings-webhooks.tsx\";\nimport type { LabelType, Project, Token } from \"../schema.ts\";\nimport { getStore } from \"../store.ts\";\nimport { randomToken } from \"../utils/hash.ts\";\nimport { notFound } from \"./helpers.ts\";\nimport { hxRedirect } from \"./htmx.ts\";\n\ninterface SettingsData {\n project: Project;\n labelTypes: LabelType[];\n tokens: Array<Omit<Token, \"hash\">>;\n members: SettingsMember[];\n webhooks: SettingsWebhook[];\n statusConfigs: SettingsStatusConfig[];\n gitHosts: GitHostProvider[];\n isAdmin: boolean;\n}\n\nasync function loadSettingsData(slug: string): Promise<SettingsData | null> {\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n return null;\n }\n const { db, config, user, authEnabled, gitHosts } = getStore();\n const labelTypes = await new LabelModel(db).listTypes(project.id);\n const tokensDb = await new TokenModel(db).list(project.id);\n const tokens = tokensDb.map(({ hash: _hash, ...rest }) => rest);\n const members = await new MemberModel(db).list(project.id);\n const webhooksDb = await new WebhookModel(db).list(project.id);\n const webhooks = webhooksDb.map((webhook) => ({ id: webhook.id, url: webhook.url, events: WebhookModel.eventsOf(webhook) }));\n const statusConfigsDb = await new StatusConfigModel(db, config.secret).list(project.id);\n const statusConfigs = statusConfigsDb.map((row) => ({\n id: row.id,\n provider: row.provider,\n config: JSON.parse(row.config) as Record<string, unknown>,\n hasToken: row.tokenEncrypted.length > 0,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n }));\n const isAdmin = !authEnabled || user?.role === \"admin\" || members.some((member) => member.userId === user?.id && member.role === \"admin\");\n return { project, labelTypes, tokens, members, webhooks, statusConfigs, gitHosts, isAdmin };\n}\n\nexport async function renderSettingsPage(c: Context, tab: SettingsTab, formState?: SettingsFormState): Promise<string> {\n const slug = c.req.param(\"slug\") ?? \"\";\n const data = await loadSettingsData(slug);\n if (!data) {\n notFound(\"Project not found\");\n }\n return await renderProjectSettingsPage(\n {\n project: data.project,\n activeTab: tab,\n labelTypes: data.labelTypes,\n tokens: data.tokens,\n members: data.members,\n webhooks: data.webhooks,\n statusConfigs: data.statusConfigs,\n gitHosts: data.gitHosts,\n isAdmin: data.isAdmin,\n },\n formState,\n );\n}\n\nasync function settingsPage(c: Context, tab: SettingsTab): Promise<string> {\n const html = await renderSettingsPage(c, tab);\n if (!html) {\n notFound(\"Project not found\");\n }\n return html;\n}\n\nasync function findProject(slug: string): Promise<Project> {\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n notFound(\"Project not found\");\n }\n return project;\n}\n\nexport function registerSettingsPages(app: ShelfApp): void {\n app.get(\"/projects/:slug/settings\", async (c) => c.html(await settingsPage(c, \"general\")));\n app.get(\"/projects/:slug/settings/labels\", async (c) => c.html(await settingsPage(c, \"labels\")));\n app.get(\"/projects/:slug/settings/tokens\", async (c) => c.html(await settingsPage(c, \"tokens\")));\n app.get(\"/projects/:slug/settings/webhooks\", async (c) => c.html(await settingsPage(c, \"webhooks\")));\n app.get(\"/projects/:slug/settings/members\", async (c) => c.html(await settingsPage(c, \"members\")));\n app.get(\"/projects/:slug/settings/status\", async (c) => c.html(await settingsPage(c, \"status\")));\n\n app.post(\"/projects/:slug/settings\", async (c) => {\n const slug = c.req.param(\"slug\");\n const project = await findProject(slug);\n const form = await c.req.formData();\n const name = form.get(\"name\");\n if (typeof name !== \"string\" || name.trim() === \"\") {\n return c.html((await renderSettingsPage(c, \"general\", { errors: { name: \"Name is required\" } })) ?? \"\", 400);\n }\n const gitRepository = asString(form.get(\"gitRepository\"));\n const gitDefaultBranch = asString(form.get(\"gitDefaultBranch\"));\n const pixelThreshold = asString(form.get(\"pixelThreshold\"));\n const maxDiffRatio = asString(form.get(\"maxDiffRatio\"));\n const publicBranchRegex = asString(form.get(\"publicBranchRegex\"));\n try {\n await new ProjectModel(getStore().db).update(project.id, {\n name: name.trim(),\n gitRepository: gitRepository ?? undefined,\n gitDefaultBranch: gitDefaultBranch ?? undefined,\n pixelThreshold: pixelThreshold ? Number(pixelThreshold) : undefined,\n maxDiffRatio: maxDiffRatio ? Number(maxDiffRatio) : undefined,\n publicBranchRegex: publicBranchRegex === \"\" ? null : (publicBranchRegex ?? undefined),\n });\n return hxRedirect(c, `/projects/${slug}/settings`);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to update\";\n return c.html((await renderSettingsPage(c, \"general\", { globalError: message })) ?? \"\", 400);\n }\n });\n\n app.post(\"/projects/:slug/delete\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n await new ProjectModel(getStore().db).remove(project.id);\n return hxRedirect(c, \"/projects\");\n });\n\n app.post(\"/projects/:slug/settings/labels\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n const form = await c.req.formData();\n const key = asString(form.get(\"key\"));\n const labelName = asString(form.get(\"labelName\")) ?? asString(form.get(\"name\"));\n const linkTemplate = asString(form.get(\"linkTemplate\"));\n if (!key || !labelName) {\n return c.html((await renderSettingsPage(c, \"labels\", { globalError: \"Key and name are required\" })) ?? \"\", 400);\n }\n try {\n await new LabelModel(getStore().db).createType(project.id, { key, name: labelName, linkTemplate });\n return hxRedirect(c, `/projects/${project.slug}/settings/labels`);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to create label\";\n return c.html((await renderSettingsPage(c, \"labels\", { globalError: message })) ?? \"\", 400);\n }\n });\n\n app.post(\"/projects/:slug/settings/labels/:key/delete\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n try {\n await new LabelModel(getStore().db).removeType(project.id, c.req.param(\"key\"));\n } catch {\n return c.html((await renderSettingsPage(c, \"labels\", { globalError: \"Cannot delete built-in label\" })) ?? \"\", 400);\n }\n return hxRedirect(c, `/projects/${project.slug}/settings/labels`);\n });\n\n app.post(\"/projects/:slug/settings/tokens\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n const form = await c.req.formData();\n const tokenName = asString(form.get(\"tokenName\")) ?? asString(form.get(\"name\"));\n if (!tokenName) {\n return c.html((await renderSettingsPage(c, \"tokens\", { globalError: \"Name is required\" })) ?? \"\", 400);\n }\n const token = randomToken(\"shelf_\");\n await new TokenModel(getStore().db).create(project.id, tokenName, token.hash);\n const html = await renderSettingsPage(c, \"tokens\");\n const inject = `<div class=\"alert alert--success\" role=\"alert\"><strong class=\"alert__title\">Token created</strong><div class=\"alert__body\">Copy now — shown once: <code>${token.value}</code></div></div>`;\n const withToken = html.replace('<nav class=\"tabs\"', `${inject}<nav class=\"tabs\"`);\n return c.html(withToken);\n });\n\n app.post(\"/projects/:slug/settings/tokens/:tokenId/delete\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n await new TokenModel(getStore().db).remove(c.req.param(\"tokenId\"));\n return hxRedirect(c, `/projects/${project.slug}/settings/tokens`);\n });\n\n app.post(\"/projects/:slug/settings/webhooks\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n const form = await c.req.formData();\n const url = asString(form.get(\"url\"));\n if (!url) {\n return c.html((await renderSettingsPage(c, \"webhooks\", { errors: { url: \"A valid URL is required\" } })) ?? \"\", 400);\n }\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(url);\n } catch {\n return c.html((await renderSettingsPage(c, \"webhooks\", { errors: { url: \"Enter a valid URL\" } })) ?? \"\", 400);\n }\n if (parsedUrl.protocol !== \"https:\" && parsedUrl.protocol !== \"http:\") {\n return c.html((await renderSettingsPage(c, \"webhooks\", { errors: { url: \"URL must use http or https\" } })) ?? \"\", 400);\n }\n const eventsRaw = asString(form.get(\"events\"));\n const events = eventsRaw ? eventsRaw.split(\",\").map((event) => event.trim()).filter((event) => event.length > 0) : undefined;\n const webhookModel = new WebhookModel(getStore().db);\n const secret = randomToken(\"whsec_\").value;\n await webhookModel.create(project.id, { url, events, secret });\n return c.html((await renderSettingsPage(c, \"webhooks\", { secret })) ?? \"\", 201);\n });\n\n app.post(\"/projects/:slug/settings/webhooks/:webhookId/delete\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n const webhook = await new WebhookModel(getStore().db).get(project.id, c.req.param(\"webhookId\"));\n if (!webhook) {\n notFound(\"Webhook not found\");\n }\n await new WebhookModel(getStore().db).remove(project.id, webhook.id);\n return hxRedirect(c, `/projects/${project.slug}/settings/webhooks`);\n });\n\n app.post(\"/projects/:slug/settings/members\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n const form = await c.req.formData();\n const userId = asString(form.get(\"userId\"));\n const role = asString(form.get(\"role\"));\n if (!userId || !role) {\n return c.html((await renderSettingsPage(c, \"members\", { globalError: \"User and role are required\" })) ?? \"\", 400);\n }\n await new MemberModel(getStore().db).set(project.id, userId, role as never);\n return hxRedirect(c, `/projects/${project.slug}/settings/members`);\n });\n\n app.post(\"/projects/:slug/settings/members/:userId/remove\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n await new MemberModel(getStore().db).remove(project.id, c.req.param(\"userId\"));\n return hxRedirect(c, `/projects/${project.slug}/settings/members`);\n });\n\n app.post(\"/projects/:slug/settings/status\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n const form = await c.req.formData();\n const providerKey = asString(form.get(\"provider\"));\n const token = asString(form.get(\"token\"));\n const configRaw = asString(form.get(\"config\")) ?? \"{}\";\n const providers = getStore().gitHosts;\n const provider = providers.find((p) => p.metadata.kind === providerKey);\n if (!provider) {\n return c.html((await renderSettingsPage(c, \"status\", { globalError: \"Unknown git provider\" })) ?? \"\", 400);\n }\n if (!token) {\n return c.html((await renderSettingsPage(c, \"status\", { errors: { token: \"Token is required\" } })) ?? \"\", 400);\n }\n let config: unknown;\n try {\n config = JSON.parse(configRaw);\n } catch {\n return c.html((await renderSettingsPage(c, \"status\", { errors: { config: \"Config must be valid JSON\" } })) ?? \"\", 400);\n }\n const parsed = provider.metadata.schema.safeParse(config);\n if (!parsed.success) {\n return c.html((await renderSettingsPage(c, \"status\", { errors: { config: parsed.error.message } })) ?? \"\", 400);\n }\n const { db, config: shelfConfig } = getStore();\n await new StatusConfigModel(db, shelfConfig.secret).create(project.id, { provider: provider.metadata.kind, config: parsed.data, token });\n return hxRedirect(c, `/projects/${project.slug}/settings/status`);\n });\n\n app.post(\"/projects/:slug/settings/status/:id/delete\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n const { db, config: shelfConfig } = getStore();\n await new StatusConfigModel(db, shelfConfig.secret).remove(project.id, c.req.param(\"id\"));\n return hxRedirect(c, `/projects/${project.slug}/settings/status`);\n });\n}\n\nfunction asString(value: FormDataEntryValue | null): string | undefined {\n return typeof value === \"string\" ? value.trim() : undefined;\n}"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"settings.mjs","names":[],"sources":["../../src/routers/settings.ts"],"sourcesContent":["import type { Context } from \"hono\";\nimport type { GitHostProvider } from \"../adapters/git-host/index.ts\";\nimport type { ShelfApp } from \"../index.tsx\";\n\nimport { LabelModel } from \"../models/label.ts\";\nimport { MemberModel } from \"../models/member.ts\";\nimport { ProjectModel } from \"../models/project.ts\";\nimport { StatusConfigModel } from \"../models/status-config.ts\";\nimport { TokenModel } from \"../models/token.ts\";\nimport { WebhookModel } from \"../models/webhook.ts\";\nimport { renderProjectSettingsPage, type SettingsFormState, type SettingsTab } from \"../pages/project-settings.tsx\";\nimport type { SettingsMember } from \"../pages/settings-members.tsx\";\nimport type { SettingsStatusConfig } from \"../pages/settings-status.tsx\";\nimport type { SettingsWebhook } from \"../pages/settings-webhooks.tsx\";\nimport type { LabelType, Project, Token } from \"../schema.ts\";\nimport { getStore } from \"../store.ts\";\nimport { randomToken } from \"../utils/hash.ts\";\nimport { notFound } from \"./helpers.ts\";\nimport { hxRedirect } from \"./htmx.ts\";\n\ninterface SettingsData {\n project: Project;\n labelTypes: LabelType[];\n tokens: Omit<Token, \"hash\">[];\n members: SettingsMember[];\n webhooks: SettingsWebhook[];\n statusConfigs: SettingsStatusConfig[];\n gitHosts: GitHostProvider[];\n isAdmin: boolean;\n}\n\nasync function loadSettingsData(slug: string): Promise<SettingsData | null> {\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n return null;\n }\n const { db, config, user, authEnabled, gitHosts } = getStore();\n const labelTypes = await new LabelModel(db).listTypes(project.id);\n const tokensDb = await new TokenModel(db).list(project.id);\n const tokens = tokensDb.map(({ hash: _hash, ...rest }) => rest);\n const members = await new MemberModel(db).list(project.id);\n const webhooksDb = await new WebhookModel(db).list(project.id);\n const webhooks = webhooksDb.map((webhook) => ({ id: webhook.id, url: webhook.url, events: WebhookModel.eventsOf(webhook) }));\n const statusConfigsDb = await new StatusConfigModel(db, config.secret).list(project.id);\n const statusConfigs = statusConfigsDb.map((row) => ({\n id: row.id,\n provider: row.provider,\n config: JSON.parse(row.config) as Record<string, unknown>,\n hasToken: row.tokenEncrypted.length > 0,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n }));\n const isAdmin = !authEnabled || user?.role === \"admin\" || members.some((member) => member.userId === user?.id && member.role === \"admin\");\n return { project, labelTypes, tokens, members, webhooks, statusConfigs, gitHosts, isAdmin };\n}\n\n/** Render the project settings page for the given tab, optionally with form state. */\nexport async function renderSettingsPage(c: Context, tab: SettingsTab, formState?: SettingsFormState): Promise<string> {\n const slug = c.req.param(\"slug\") ?? \"\";\n const data = await loadSettingsData(slug);\n if (!data) {\n notFound(\"Project not found\");\n }\n return await renderProjectSettingsPage(\n {\n project: data.project,\n activeTab: tab,\n labelTypes: data.labelTypes,\n tokens: data.tokens,\n members: data.members,\n webhooks: data.webhooks,\n statusConfigs: data.statusConfigs,\n gitHosts: data.gitHosts,\n isAdmin: data.isAdmin,\n },\n formState,\n );\n}\n\nasync function settingsPage(c: Context, tab: SettingsTab): Promise<string> {\n const html = await renderSettingsPage(c, tab);\n if (!html) {\n notFound(\"Project not found\");\n }\n return html;\n}\n\nasync function findProject(slug: string): Promise<Project> {\n const project = await new ProjectModel(getStore().db).getBySlug(slug);\n if (!project) {\n notFound(\"Project not found\");\n }\n return project;\n}\n\n/** Register the server-rendered project settings pages and their form handlers. */\nexport function registerSettingsPages(app: ShelfApp): void {\n app.get(\"/projects/:slug/settings\", async (c) => c.html(await settingsPage(c, \"general\")));\n app.get(\"/projects/:slug/settings/tests\", async (c) => c.html(await settingsPage(c, \"tests\")));\n app.get(\"/projects/:slug/settings/labels\", async (c) => c.html(await settingsPage(c, \"labels\")));\n app.get(\"/projects/:slug/settings/tokens\", async (c) => c.html(await settingsPage(c, \"tokens\")));\n app.get(\"/projects/:slug/settings/webhooks\", async (c) => c.html(await settingsPage(c, \"webhooks\")));\n app.get(\"/projects/:slug/settings/members\", async (c) => c.html(await settingsPage(c, \"members\")));\n app.get(\"/projects/:slug/settings/status\", async (c) => c.html(await settingsPage(c, \"status\")));\n\n app.post(\"/projects/:slug/settings\", async (c) => {\n const slug = c.req.param(\"slug\");\n const project = await findProject(slug);\n const form = await c.req.formData();\n const name = form.get(\"name\");\n if (typeof name !== \"string\" || name.trim() === \"\") {\n return c.html((await renderSettingsPage(c, \"general\", { errors: { name: \"Name is required\" } })) ?? \"\", 400);\n }\n const gitRepository = asString(form.get(\"gitRepository\"));\n const gitDefaultBranch = asString(form.get(\"gitDefaultBranch\"));\n const pixelThreshold = asString(form.get(\"pixelThreshold\"));\n const maxDiffRatio = asString(form.get(\"maxDiffRatio\"));\n const publicBranchRegex = asString(form.get(\"publicBranchRegex\"));\n try {\n await new ProjectModel(getStore().db).update(project.id, {\n name: name.trim(),\n gitRepository: gitRepository ?? undefined,\n gitDefaultBranch: gitDefaultBranch ?? undefined,\n pixelThreshold: pixelThreshold ? Number(pixelThreshold) : undefined,\n maxDiffRatio: maxDiffRatio ? Number(maxDiffRatio) : undefined,\n publicBranchRegex: publicBranchRegex === \"\" ? null : (publicBranchRegex ?? undefined),\n });\n return hxRedirect(c, `/projects/${slug}/settings`);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to update\";\n return c.html((await renderSettingsPage(c, \"general\", { globalError: message })) ?? \"\", 400);\n }\n });\n\n app.post(\"/projects/:slug/settings/tests\", async (c) => {\n const slug = c.req.param(\"slug\");\n const project = await findProject(slug);\n const form = await c.req.formData();\n const executePlay = form.get(\"executePlay\") === \"true\";\n const playTimeoutMsRaw = asString(form.get(\"playTimeoutMs\"));\n const playTimeoutMs = playTimeoutMsRaw ? Number(playTimeoutMsRaw) : undefined;\n if (playTimeoutMs !== undefined && (Number.isNaN(playTimeoutMs) || playTimeoutMs < 1000 || playTimeoutMs > 30_000)) {\n return c.html((await renderSettingsPage(c, \"tests\", { globalError: \"Play timeout must be between 1000 and 30000\" })) ?? \"\", 400);\n }\n try {\n await new ProjectModel(getStore().db).update(project.id, {\n executePlay,\n playTimeoutMs: playTimeoutMs ?? undefined,\n });\n return hxRedirect(c, `/projects/${slug}/settings/tests`);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to update\";\n return c.html((await renderSettingsPage(c, \"tests\", { globalError: message })) ?? \"\", 400);\n }\n });\n\n app.post(\"/projects/:slug/delete\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n await new ProjectModel(getStore().db).remove(project.id);\n return hxRedirect(c, \"/projects\");\n });\n\n app.post(\"/projects/:slug/settings/labels\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n const form = await c.req.formData();\n const key = asString(form.get(\"key\"));\n const labelName = asString(form.get(\"labelName\")) ?? asString(form.get(\"name\"));\n const linkTemplate = asString(form.get(\"linkTemplate\"));\n if (!key || !labelName) {\n return c.html((await renderSettingsPage(c, \"labels\", { globalError: \"Key and name are required\" })) ?? \"\", 400);\n }\n try {\n await new LabelModel(getStore().db).createType(project.id, { key, name: labelName, linkTemplate });\n return hxRedirect(c, `/projects/${project.slug}/settings/labels`);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Failed to create label\";\n return c.html((await renderSettingsPage(c, \"labels\", { globalError: message })) ?? \"\", 400);\n }\n });\n\n app.post(\"/projects/:slug/settings/labels/:key/delete\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n try {\n await new LabelModel(getStore().db).removeType(project.id, c.req.param(\"key\"));\n } catch {\n return c.html((await renderSettingsPage(c, \"labels\", { globalError: \"Cannot delete built-in label\" })) ?? \"\", 400);\n }\n return hxRedirect(c, `/projects/${project.slug}/settings/labels`);\n });\n\n app.post(\"/projects/:slug/settings/tokens\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n const form = await c.req.formData();\n const tokenName = asString(form.get(\"tokenName\")) ?? asString(form.get(\"name\"));\n if (!tokenName) {\n return c.html((await renderSettingsPage(c, \"tokens\", { globalError: \"Name is required\" })) ?? \"\", 400);\n }\n const token = randomToken(\"shelf_\");\n await new TokenModel(getStore().db).create(project.id, tokenName, token.hash);\n const html = await renderSettingsPage(c, \"tokens\");\n const inject = `<div class=\"alert alert--success\" role=\"alert\"><strong class=\"alert__title\">Token created</strong><div class=\"alert__body\">Copy now — shown once: <code>${token.value}</code></div></div>`;\n const withToken = html.replace('<nav class=\"tabs\"', `${inject}<nav class=\"tabs\"`);\n return c.html(withToken);\n });\n\n app.post(\"/projects/:slug/settings/tokens/:tokenId/delete\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n await new TokenModel(getStore().db).remove(c.req.param(\"tokenId\"));\n return hxRedirect(c, `/projects/${project.slug}/settings/tokens`);\n });\n\n app.post(\"/projects/:slug/settings/webhooks\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n const form = await c.req.formData();\n const url = asString(form.get(\"url\"));\n if (!url) {\n return c.html((await renderSettingsPage(c, \"webhooks\", { errors: { url: \"A valid URL is required\" } })) ?? \"\", 400);\n }\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(url);\n } catch {\n return c.html((await renderSettingsPage(c, \"webhooks\", { errors: { url: \"Enter a valid URL\" } })) ?? \"\", 400);\n }\n if (parsedUrl.protocol !== \"https:\" && parsedUrl.protocol !== \"http:\") {\n return c.html((await renderSettingsPage(c, \"webhooks\", { errors: { url: \"URL must use http or https\" } })) ?? \"\", 400);\n }\n const eventsRaw = asString(form.get(\"events\"));\n const events = eventsRaw ? eventsRaw.split(\",\").map((event) => event.trim()).filter((event) => event.length > 0) : undefined;\n const webhookModel = new WebhookModel(getStore().db);\n const secret = randomToken(\"whsec_\").value;\n await webhookModel.create(project.id, { url, events, secret });\n return c.html((await renderSettingsPage(c, \"webhooks\", { secret })) ?? \"\", 201);\n });\n\n app.post(\"/projects/:slug/settings/webhooks/:webhookId/delete\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n const webhook = await new WebhookModel(getStore().db).get(project.id, c.req.param(\"webhookId\"));\n if (!webhook) {\n notFound(\"Webhook not found\");\n }\n await new WebhookModel(getStore().db).remove(project.id, webhook.id);\n return hxRedirect(c, `/projects/${project.slug}/settings/webhooks`);\n });\n\n app.post(\"/projects/:slug/settings/members\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n const form = await c.req.formData();\n const userId = asString(form.get(\"userId\"));\n const role = asString(form.get(\"role\"));\n if (!userId || !role) {\n return c.html((await renderSettingsPage(c, \"members\", { globalError: \"User and role are required\" })) ?? \"\", 400);\n }\n await new MemberModel(getStore().db).set(project.id, userId, role as never);\n return hxRedirect(c, `/projects/${project.slug}/settings/members`);\n });\n\n app.post(\"/projects/:slug/settings/members/:userId/remove\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n await new MemberModel(getStore().db).remove(project.id, c.req.param(\"userId\"));\n return hxRedirect(c, `/projects/${project.slug}/settings/members`);\n });\n\n app.post(\"/projects/:slug/settings/status\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n const form = await c.req.formData();\n const providerKey = asString(form.get(\"provider\"));\n const token = asString(form.get(\"token\"));\n const configRaw = asString(form.get(\"config\")) ?? \"{}\";\n const providers = getStore().gitHosts;\n const provider = providers.find((p) => p.metadata.kind === providerKey);\n if (!provider) {\n return c.html((await renderSettingsPage(c, \"status\", { globalError: \"Unknown git provider\" })) ?? \"\", 400);\n }\n if (!token) {\n return c.html((await renderSettingsPage(c, \"status\", { errors: { token: \"Token is required\" } })) ?? \"\", 400);\n }\n let config: unknown;\n try {\n config = JSON.parse(configRaw);\n } catch {\n return c.html((await renderSettingsPage(c, \"status\", { errors: { config: \"Config must be valid JSON\" } })) ?? \"\", 400);\n }\n const parsed = provider.metadata.schema.safeParse(config);\n if (!parsed.success) {\n return c.html((await renderSettingsPage(c, \"status\", { errors: { config: parsed.error.message } })) ?? \"\", 400);\n }\n const { db, config: shelfConfig } = getStore();\n await new StatusConfigModel(db, shelfConfig.secret).create(project.id, { provider: provider.metadata.kind, config: parsed.data, token });\n return hxRedirect(c, `/projects/${project.slug}/settings/status`);\n });\n\n app.post(\"/projects/:slug/settings/status/:id/delete\", async (c) => {\n const project = await findProject(c.req.param(\"slug\"));\n const { db, config: shelfConfig } = getStore();\n await new StatusConfigModel(db, shelfConfig.secret).remove(project.id, c.req.param(\"id\"));\n return hxRedirect(c, `/projects/${project.slug}/settings/status`);\n });\n}\n\nfunction asString(value: FormDataEntryValue | null): string | undefined {\n return typeof value === \"string\" ? value.trim() : undefined;\n}"],"mappings":";;;;;;;;;;;;;;;AA+BA,eAAe,iBAAiB,MAA4C;CAC1E,MAAM,UAAU,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,IAAI;CACpE,IAAI,CAAC,SACH,OAAO;CAET,MAAM,EAAE,IAAI,QAAQ,MAAM,aAAa,aAAa,SAAS;CAC7D,MAAM,aAAa,MAAM,IAAI,WAAW,EAAE,CAAC,CAAC,UAAU,QAAQ,EAAE;CAEhE,MAAM,UAAS,MADQ,IAAI,WAAW,EAAE,CAAC,CAAC,KAAK,QAAQ,EAAE,EAAA,CACjC,KAAK,EAAE,MAAM,OAAO,GAAG,WAAW,IAAI;CAC9D,MAAM,UAAU,MAAM,IAAI,YAAY,EAAE,CAAC,CAAC,KAAK,QAAQ,EAAE;CAazD,OAAO;EAAE;EAAS;EAAY;EAAQ;EAAS,WAX9B,MADQ,IAAI,aAAa,EAAE,CAAC,CAAC,KAAK,QAAQ,EAAE,EAAA,CACjC,KAAK,aAAa;GAAE,IAAI,QAAQ;GAAI,KAAK,QAAQ;GAAK,QAAQ,aAAa,SAAS,OAAO;EAAE,EAWnE;EAAG,gBATnC,MADQ,IAAI,kBAAkB,IAAI,OAAO,MAAM,CAAC,CAAC,KAAK,QAAQ,EAAE,EAAA,CAChD,KAAK,SAAS;GAClD,IAAI,IAAI;GACR,UAAU,IAAI;GACd,QAAQ,KAAK,MAAM,IAAI,MAAM;GAC7B,UAAU,IAAI,eAAe,SAAS;GACtC,WAAW,IAAI;GACf,WAAW,IAAI;EACjB,EAEqE;EAAG;EAAU,SADlE,CAAC,eAAe,MAAM,SAAS,WAAW,QAAQ,MAAM,WAAW,OAAO,WAAW,MAAM,MAAM,OAAO,SAAS,OAAO;CAC9C;AAC5F;;AAGA,eAAsB,mBAAmB,GAAY,KAAkB,WAAgD;CAErH,MAAM,OAAO,MAAM,iBADN,EAAE,IAAI,MAAM,MAAM,KAAK,EACI;CACxC,IAAI,CAAC,MACH,SAAS,mBAAmB;CAE9B,OAAO,MAAM,0BACX;EACE,SAAS,KAAK;EACd,WAAW;EACX,YAAY,KAAK;EACjB,QAAQ,KAAK;EACb,SAAS,KAAK;EACd,UAAU,KAAK;EACf,eAAe,KAAK;EACpB,UAAU,KAAK;EACf,SAAS,KAAK;CAChB,GACA,SACF;AACF;AAEA,eAAe,aAAa,GAAY,KAAmC;CACzE,MAAM,OAAO,MAAM,mBAAmB,GAAG,GAAG;CAC5C,IAAI,CAAC,MACH,SAAS,mBAAmB;CAE9B,OAAO;AACT;AAEA,eAAe,YAAY,MAAgC;CACzD,MAAM,UAAU,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,IAAI;CACpE,IAAI,CAAC,SACH,SAAS,mBAAmB;CAE9B,OAAO;AACT;;AAGA,SAAgB,sBAAsB,KAAqB;CACzD,IAAI,IAAI,4BAA4B,OAAO,MAAM,EAAE,KAAK,MAAM,aAAa,GAAG,SAAS,CAAC,CAAC;CACzF,IAAI,IAAI,kCAAkC,OAAO,MAAM,EAAE,KAAK,MAAM,aAAa,GAAG,OAAO,CAAC,CAAC;CAC7F,IAAI,IAAI,mCAAmC,OAAO,MAAM,EAAE,KAAK,MAAM,aAAa,GAAG,QAAQ,CAAC,CAAC;CAC/F,IAAI,IAAI,mCAAmC,OAAO,MAAM,EAAE,KAAK,MAAM,aAAa,GAAG,QAAQ,CAAC,CAAC;CAC/F,IAAI,IAAI,qCAAqC,OAAO,MAAM,EAAE,KAAK,MAAM,aAAa,GAAG,UAAU,CAAC,CAAC;CACnG,IAAI,IAAI,oCAAoC,OAAO,MAAM,EAAE,KAAK,MAAM,aAAa,GAAG,SAAS,CAAC,CAAC;CACjG,IAAI,IAAI,mCAAmC,OAAO,MAAM,EAAE,KAAK,MAAM,aAAa,GAAG,QAAQ,CAAC,CAAC;CAE/F,IAAI,KAAK,4BAA4B,OAAO,MAAM;EAChD,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;EAC/B,MAAM,UAAU,MAAM,YAAY,IAAI;EACtC,MAAM,OAAO,MAAM,EAAE,IAAI,SAAS;EAClC,MAAM,OAAO,KAAK,IAAI,MAAM;EAC5B,IAAI,OAAO,SAAS,YAAY,KAAK,KAAK,MAAM,IAC9C,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,WAAW,EAAE,QAAQ,EAAE,MAAM,mBAAmB,EAAE,CAAC,KAAM,IAAI,GAAG;EAE7G,MAAM,gBAAgB,SAAS,KAAK,IAAI,eAAe,CAAC;EACxD,MAAM,mBAAmB,SAAS,KAAK,IAAI,kBAAkB,CAAC;EAC9D,MAAM,iBAAiB,SAAS,KAAK,IAAI,gBAAgB,CAAC;EAC1D,MAAM,eAAe,SAAS,KAAK,IAAI,cAAc,CAAC;EACtD,MAAM,oBAAoB,SAAS,KAAK,IAAI,mBAAmB,CAAC;EAChE,IAAI;GACF,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,QAAQ,IAAI;IACvD,MAAM,KAAK,KAAK;IAChB,eAAe,iBAAiB,KAAA;IAChC,kBAAkB,oBAAoB,KAAA;IACtC,gBAAgB,iBAAiB,OAAO,cAAc,IAAI,KAAA;IAC1D,cAAc,eAAe,OAAO,YAAY,IAAI,KAAA;IACpD,mBAAmB,sBAAsB,KAAK,OAAQ,qBAAqB,KAAA;GAC7E,CAAC;GACD,OAAO,WAAW,GAAG,aAAa,KAAK,UAAU;EACnD,SAAS,OAAO;GACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;GACzD,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,WAAW,EAAE,aAAa,QAAQ,CAAC,KAAM,IAAI,GAAG;EAC7F;CACF,CAAC;CAED,IAAI,KAAK,kCAAkC,OAAO,MAAM;EACtD,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;EAC/B,MAAM,UAAU,MAAM,YAAY,IAAI;EACtC,MAAM,OAAO,MAAM,EAAE,IAAI,SAAS;EAClC,MAAM,cAAc,KAAK,IAAI,aAAa,MAAM;EAChD,MAAM,mBAAmB,SAAS,KAAK,IAAI,eAAe,CAAC;EAC3D,MAAM,gBAAgB,mBAAmB,OAAO,gBAAgB,IAAI,KAAA;EACpE,IAAI,kBAAkB,KAAA,MAAc,OAAO,MAAM,aAAa,KAAK,gBAAgB,OAAQ,gBAAgB,MACzG,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,SAAS,EAAE,aAAa,8CAA8C,CAAC,KAAM,IAAI,GAAG;EAEjI,IAAI;GACF,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,QAAQ,IAAI;IACvD;IACA,eAAe,iBAAiB,KAAA;GAClC,CAAC;GACD,OAAO,WAAW,GAAG,aAAa,KAAK,gBAAgB;EACzD,SAAS,OAAO;GACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;GACzD,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,SAAS,EAAE,aAAa,QAAQ,CAAC,KAAM,IAAI,GAAG;EAC3F;CACF,CAAC;CAED,IAAI,KAAK,0BAA0B,OAAO,MAAM;EAC9C,MAAM,UAAU,MAAM,YAAY,EAAE,IAAI,MAAM,MAAM,CAAC;EACrD,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,QAAQ,EAAE;EACvD,OAAO,WAAW,GAAG,WAAW;CAClC,CAAC;CAED,IAAI,KAAK,mCAAmC,OAAO,MAAM;EACvD,MAAM,UAAU,MAAM,YAAY,EAAE,IAAI,MAAM,MAAM,CAAC;EACrD,MAAM,OAAO,MAAM,EAAE,IAAI,SAAS;EAClC,MAAM,MAAM,SAAS,KAAK,IAAI,KAAK,CAAC;EACpC,MAAM,YAAY,SAAS,KAAK,IAAI,WAAW,CAAC,KAAK,SAAS,KAAK,IAAI,MAAM,CAAC;EAC9E,MAAM,eAAe,SAAS,KAAK,IAAI,cAAc,CAAC;EACtD,IAAI,CAAC,OAAO,CAAC,WACX,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,UAAU,EAAE,aAAa,4BAA4B,CAAC,KAAM,IAAI,GAAG;EAEhH,IAAI;GACF,MAAM,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,WAAW,QAAQ,IAAI;IAAE;IAAK,MAAM;IAAW;GAAa,CAAC;GACjG,OAAO,WAAW,GAAG,aAAa,QAAQ,KAAK,iBAAiB;EAClE,SAAS,OAAO;GACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;GACzD,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,UAAU,EAAE,aAAa,QAAQ,CAAC,KAAM,IAAI,GAAG;EAC5F;CACF,CAAC;CAED,IAAI,KAAK,+CAA+C,OAAO,MAAM;EACnE,MAAM,UAAU,MAAM,YAAY,EAAE,IAAI,MAAM,MAAM,CAAC;EACrD,IAAI;GACF,MAAM,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,WAAW,QAAQ,IAAI,EAAE,IAAI,MAAM,KAAK,CAAC;EAC/E,QAAQ;GACN,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,UAAU,EAAE,aAAa,+BAA+B,CAAC,KAAM,IAAI,GAAG;EACnH;EACA,OAAO,WAAW,GAAG,aAAa,QAAQ,KAAK,iBAAiB;CAClE,CAAC;CAED,IAAI,KAAK,mCAAmC,OAAO,MAAM;EACvD,MAAM,UAAU,MAAM,YAAY,EAAE,IAAI,MAAM,MAAM,CAAC;EACrD,MAAM,OAAO,MAAM,EAAE,IAAI,SAAS;EAClC,MAAM,YAAY,SAAS,KAAK,IAAI,WAAW,CAAC,KAAK,SAAS,KAAK,IAAI,MAAM,CAAC;EAC9E,IAAI,CAAC,WACH,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,UAAU,EAAE,aAAa,mBAAmB,CAAC,KAAM,IAAI,GAAG;EAEvG,MAAM,QAAQ,YAAY,QAAQ;EAClC,MAAM,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,QAAQ,IAAI,WAAW,MAAM,IAAI;EAC5E,MAAM,OAAO,MAAM,mBAAmB,GAAG,QAAQ;EACjD,MAAM,SAAS,2JAA2J,MAAM,MAAM;EACtL,MAAM,YAAY,KAAK,QAAQ,uBAAqB,GAAG,OAAO,kBAAkB;EAChF,OAAO,EAAE,KAAK,SAAS;CACzB,CAAC;CAED,IAAI,KAAK,mDAAmD,OAAO,MAAM;EACvE,MAAM,UAAU,MAAM,YAAY,EAAE,IAAI,MAAM,MAAM,CAAC;EACrD,MAAM,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,IAAI,MAAM,SAAS,CAAC;EACjE,OAAO,WAAW,GAAG,aAAa,QAAQ,KAAK,iBAAiB;CAClE,CAAC;CAED,IAAI,KAAK,qCAAqC,OAAO,MAAM;EACzD,MAAM,UAAU,MAAM,YAAY,EAAE,IAAI,MAAM,MAAM,CAAC;EACrD,MAAM,OAAO,MAAM,EAAE,IAAI,SAAS;EAClC,MAAM,MAAM,SAAS,KAAK,IAAI,KAAK,CAAC;EACpC,IAAI,CAAC,KACH,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,YAAY,EAAE,QAAQ,EAAE,KAAK,0BAA0B,EAAE,CAAC,KAAM,IAAI,GAAG;EAEpH,IAAI;EACJ,IAAI;GACF,YAAY,IAAI,IAAI,GAAG;EACzB,QAAQ;GACN,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,YAAY,EAAE,QAAQ,EAAE,KAAK,oBAAoB,EAAE,CAAC,KAAM,IAAI,GAAG;EAC9G;EACA,IAAI,UAAU,aAAa,YAAY,UAAU,aAAa,SAC5D,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,YAAY,EAAE,QAAQ,EAAE,KAAK,6BAA6B,EAAE,CAAC,KAAM,IAAI,GAAG;EAEvH,MAAM,YAAY,SAAS,KAAK,IAAI,QAAQ,CAAC;EAC7C,MAAM,SAAS,YAAY,UAAU,MAAM,GAAG,CAAC,CAAC,KAAK,UAAU,MAAM,KAAK,CAAC,CAAC,CAAC,QAAQ,UAAU,MAAM,SAAS,CAAC,IAAI,KAAA;EACnH,MAAM,eAAe,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE;EACnD,MAAM,SAAS,YAAY,QAAQ,CAAC,CAAC;EACrC,MAAM,aAAa,OAAO,QAAQ,IAAI;GAAE;GAAK;GAAQ;EAAO,CAAC;EAC7D,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,YAAY,EAAE,OAAO,CAAC,KAAM,IAAI,GAAG;CAChF,CAAC;CAED,IAAI,KAAK,uDAAuD,OAAO,MAAM;EAC3E,MAAM,UAAU,MAAM,YAAY,EAAE,IAAI,MAAM,MAAM,CAAC;EACrD,MAAM,UAAU,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,QAAQ,IAAI,EAAE,IAAI,MAAM,WAAW,CAAC;EAC9F,IAAI,CAAC,SACH,SAAS,mBAAmB;EAE9B,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,QAAQ,IAAI,QAAQ,EAAE;EACnE,OAAO,WAAW,GAAG,aAAa,QAAQ,KAAK,mBAAmB;CACpE,CAAC;CAED,IAAI,KAAK,oCAAoC,OAAO,MAAM;EACxD,MAAM,UAAU,MAAM,YAAY,EAAE,IAAI,MAAM,MAAM,CAAC;EACrD,MAAM,OAAO,MAAM,EAAE,IAAI,SAAS;EAClC,MAAM,SAAS,SAAS,KAAK,IAAI,QAAQ,CAAC;EAC1C,MAAM,OAAO,SAAS,KAAK,IAAI,MAAM,CAAC;EACtC,IAAI,CAAC,UAAU,CAAC,MACd,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,WAAW,EAAE,aAAa,6BAA6B,CAAC,KAAM,IAAI,GAAG;EAElH,MAAM,IAAI,YAAY,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,QAAQ,IAAI,QAAQ,IAAa;EAC1E,OAAO,WAAW,GAAG,aAAa,QAAQ,KAAK,kBAAkB;CACnE,CAAC;CAED,IAAI,KAAK,mDAAmD,OAAO,MAAM;EACvE,MAAM,UAAU,MAAM,YAAY,EAAE,IAAI,MAAM,MAAM,CAAC;EACrD,MAAM,IAAI,YAAY,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,QAAQ,IAAI,EAAE,IAAI,MAAM,QAAQ,CAAC;EAC7E,OAAO,WAAW,GAAG,aAAa,QAAQ,KAAK,kBAAkB;CACnE,CAAC;CAED,IAAI,KAAK,mCAAmC,OAAO,MAAM;EACvD,MAAM,UAAU,MAAM,YAAY,EAAE,IAAI,MAAM,MAAM,CAAC;EACrD,MAAM,OAAO,MAAM,EAAE,IAAI,SAAS;EAClC,MAAM,cAAc,SAAS,KAAK,IAAI,UAAU,CAAC;EACjD,MAAM,QAAQ,SAAS,KAAK,IAAI,OAAO,CAAC;EACxC,MAAM,YAAY,SAAS,KAAK,IAAI,QAAQ,CAAC,KAAK;EAElD,MAAM,WADY,SAAS,CAAC,CAAC,SACF,MAAM,MAAM,EAAE,SAAS,SAAS,WAAW;EACtE,IAAI,CAAC,UACH,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,UAAU,EAAE,aAAa,uBAAuB,CAAC,KAAM,IAAI,GAAG;EAE3G,IAAI,CAAC,OACH,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,UAAU,EAAE,QAAQ,EAAE,OAAO,oBAAoB,EAAE,CAAC,KAAM,IAAI,GAAG;EAE9G,IAAI;EACJ,IAAI;GACF,SAAS,KAAK,MAAM,SAAS;EAC/B,QAAQ;GACN,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,UAAU,EAAE,QAAQ,EAAE,QAAQ,4BAA4B,EAAE,CAAC,KAAM,IAAI,GAAG;EACvH;EACA,MAAM,SAAS,SAAS,SAAS,OAAO,UAAU,MAAM;EACxD,IAAI,CAAC,OAAO,SACV,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,UAAU,EAAE,QAAQ,EAAE,QAAQ,OAAO,MAAM,QAAQ,EAAE,CAAC,KAAM,IAAI,GAAG;EAEhH,MAAM,EAAE,IAAI,QAAQ,gBAAgB,SAAS;EAC7C,MAAM,IAAI,kBAAkB,IAAI,YAAY,MAAM,CAAC,CAAC,OAAO,QAAQ,IAAI;GAAE,UAAU,SAAS,SAAS;GAAM,QAAQ,OAAO;GAAM;EAAM,CAAC;EACvI,OAAO,WAAW,GAAG,aAAa,QAAQ,KAAK,iBAAiB;CAClE,CAAC;CAED,IAAI,KAAK,8CAA8C,OAAO,MAAM;EAClE,MAAM,UAAU,MAAM,YAAY,EAAE,IAAI,MAAM,MAAM,CAAC;EACrD,MAAM,EAAE,IAAI,QAAQ,gBAAgB,SAAS;EAC7C,MAAM,IAAI,kBAAkB,IAAI,YAAY,MAAM,CAAC,CAAC,OAAO,QAAQ,IAAI,EAAE,IAAI,MAAM,IAAI,CAAC;EACxF,OAAO,WAAW,GAAG,aAAa,QAAQ,KAAK,iBAAiB;CAClE,CAAC;AACH;AAEA,SAAS,SAAS,OAAsD;CACtE,OAAO,OAAO,UAAU,WAAW,MAAM,KAAK,IAAI,KAAA;AACpD"}
|
|
@@ -86,6 +86,7 @@ const rejectAllRoute = createRoute({
|
|
|
86
86
|
...notFound
|
|
87
87
|
}
|
|
88
88
|
});
|
|
89
|
+
/** Register the snapshot list, approve, reject, and bulk-review endpoints. */
|
|
89
90
|
function registerSnapshots(app) {
|
|
90
91
|
app.openapi(listSnapshotsRoute, async (c) => {
|
|
91
92
|
const { slug, buildId } = c.req.valid("param");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshots.mjs","names":[],"sources":["../../src/routers/snapshots.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport type { ShelfApp } from \"../index.tsx\";\n\nimport { SnapshotModel } from \"../models/snapshot.ts\";\nimport { getStore } from \"../store.ts\";\nimport { resolveAuthorizedProject } from \"./helpers.ts\";\nimport { snapshotSchema, okSchema, notFound, unauthorized } from \"./schemas.ts\";\nimport { VIEW_ROLES, APPROVER_ROLES, snapshotForBuild, refreshBuild, approveSnapshot, buildForProject } from \"./builds.handlers.ts\";\n\nconst listSnapshotsRoute = createRoute({\n method: \"get\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/snapshots\",\n request: { params: z.object({ slug: z.string(), buildId: z.string() }) },\n responses: {\n 200: { content: { \"application/json\": { schema: snapshotSchema.array() } }, description: \"List snapshots for a build\" },\n ...notFound,\n ...unauthorized,\n },\n});\n\nconst approveSnapshotRoute = createRoute({\n method: \"post\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/snapshots/{snapshotId}/approve\",\n request: { params: z.object({ slug: z.string(), buildId: z.string(), snapshotId: z.string() }) },\n responses: {\n 200: { content: { \"application/json\": { schema: okSchema } }, description: \"Snapshot approved\" },\n ...notFound,\n },\n});\n\nconst rejectSnapshotRoute = createRoute({\n method: \"post\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/snapshots/{snapshotId}/reject\",\n request: { params: z.object({ slug: z.string(), buildId: z.string(), snapshotId: z.string() }) },\n responses: {\n 200: { content: { \"application/json\": { schema: okSchema } }, description: \"Snapshot rejected\" },\n ...notFound,\n },\n});\n\nconst approveAllRoute = createRoute({\n method: \"post\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/approve-all\",\n request: { params: z.object({ slug: z.string(), buildId: z.string() }) },\n responses: {\n 200: { content: { \"application/json\": { schema: okSchema } }, description: \"All snapshots approved\" },\n ...notFound,\n },\n});\n\nconst rejectAllRoute = createRoute({\n method: \"post\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/reject-all\",\n request: { params: z.object({ slug: z.string(), buildId: z.string() }) },\n responses: {\n 200: { content: { \"application/json\": { schema: okSchema } }, description: \"All snapshots rejected\" },\n ...notFound,\n },\n});\n\nexport function registerSnapshots(app: ShelfApp): void {\n app.openapi(listSnapshotsRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...VIEW_ROLES);\n const build = await buildForProject(project.id, buildId);\n const snapshots = new SnapshotModel(getStore().db).listByBuild(build.id);\n return c.json(await snapshots);\n });\n\n app.openapi(approveSnapshotRoute, async (c) => {\n const { slug, buildId, snapshotId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...APPROVER_ROLES);\n const build = await buildForProject(project.id, buildId);\n await snapshotForBuild(build, snapshotId);\n const userId = getStore().user?.id ?? \"anonymous\";\n await approveSnapshot(snapshotId, userId);\n return c.json({ ok: true });\n });\n\n app.openapi(rejectSnapshotRoute, async (c) => {\n const { slug, buildId, snapshotId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...APPROVER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const snapshot = await snapshotForBuild(build, snapshotId);\n const userId = getStore().user?.id ?? \"anonymous\";\n await new SnapshotModel(getStore().db).review(snapshot.id, \"rejected\", userId);\n await refreshBuild(build.id);\n return c.json({ ok: true });\n });\n\n app.openapi(approveAllRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...APPROVER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const snapshots = await new SnapshotModel(getStore().db).listByBuild(build.id);\n const userId = getStore().user?.id ?? \"anonymous\";\n await Promise.all(\n snapshots\n .filter((snapshot) => snapshot.status === \"new\" || snapshot.status === \"changed\")\n .map(async (snapshot) => {\n await approveSnapshot(snapshot.id, userId);\n }),\n );\n return c.json({ ok: true });\n });\n\n app.openapi(rejectAllRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...APPROVER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const snapshots = await new SnapshotModel(getStore().db).listByBuild(build.id);\n const userId = getStore().user?.id ?? \"anonymous\";\n await Promise.all(\n snapshots\n .filter((snapshot) => snapshot.status === \"new\" || snapshot.status === \"changed\")\n .map(async (snapshot) => {\n await new SnapshotModel(getStore().db).review(snapshot.id, \"rejected\", userId);\n }),\n );\n await refreshBuild(build.id);\n return c.json({ ok: true });\n });\n}"],"mappings":";;;;;;;;;;AASA,MAAM,qBAAqB,YAAY;CACrC,QAAQ;CACR,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO;EAAE,MAAM,EAAE,OAAO;EAAG,SAAS,EAAE,OAAO;CAAE,CAAC,EAAE;CACvE,WAAW;EACT,KAAK;GAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,eAAe,MAAM,EAAE,EAAE;GAAG,aAAa;EAA6B;EACtH,GAAG;EACH,GAAG;CACL;AACF,CAAC;AAED,MAAM,uBAAuB,YAAY;CACvC,QAAQ;CACR,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO;EAAE,MAAM,EAAE,OAAO;EAAG,SAAS,EAAE,OAAO;EAAG,YAAY,EAAE,OAAO;CAAE,CAAC,EAAE;CAC/F,WAAW;EACT,KAAK;GAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,SAAS,EAAE;GAAG,aAAa;EAAoB;EAC/F,GAAG;CACL;AACF,CAAC;AAED,MAAM,sBAAsB,YAAY;CACtC,QAAQ;CACR,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO;EAAE,MAAM,EAAE,OAAO;EAAG,SAAS,EAAE,OAAO;EAAG,YAAY,EAAE,OAAO;CAAE,CAAC,EAAE;CAC/F,WAAW;EACT,KAAK;GAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,SAAS,EAAE;GAAG,aAAa;EAAoB;EAC/F,GAAG;CACL;AACF,CAAC;AAED,MAAM,kBAAkB,YAAY;CAClC,QAAQ;CACR,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO;EAAE,MAAM,EAAE,OAAO;EAAG,SAAS,EAAE,OAAO;CAAE,CAAC,EAAE;CACvE,WAAW;EACT,KAAK;GAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,SAAS,EAAE;GAAG,aAAa;EAAyB;EACpG,GAAG;CACL;AACF,CAAC;AAED,MAAM,iBAAiB,YAAY;CACjC,QAAQ;CACR,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO;EAAE,MAAM,EAAE,OAAO;EAAG,SAAS,EAAE,OAAO;CAAE,CAAC,EAAE;CACvE,WAAW;EACT,KAAK;GAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,SAAS,EAAE;GAAG,aAAa;EAAyB;EACpG,GAAG;CACL;AACF,CAAC
|
|
1
|
+
{"version":3,"file":"snapshots.mjs","names":[],"sources":["../../src/routers/snapshots.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport type { ShelfApp } from \"../index.tsx\";\n\nimport { SnapshotModel } from \"../models/snapshot.ts\";\nimport { getStore } from \"../store.ts\";\nimport { resolveAuthorizedProject } from \"./helpers.ts\";\nimport { snapshotSchema, okSchema, notFound, unauthorized } from \"./schemas.ts\";\nimport { VIEW_ROLES, APPROVER_ROLES, snapshotForBuild, refreshBuild, approveSnapshot, buildForProject } from \"./builds.handlers.ts\";\n\nconst listSnapshotsRoute = createRoute({\n method: \"get\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/snapshots\",\n request: { params: z.object({ slug: z.string(), buildId: z.string() }) },\n responses: {\n 200: { content: { \"application/json\": { schema: snapshotSchema.array() } }, description: \"List snapshots for a build\" },\n ...notFound,\n ...unauthorized,\n },\n});\n\nconst approveSnapshotRoute = createRoute({\n method: \"post\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/snapshots/{snapshotId}/approve\",\n request: { params: z.object({ slug: z.string(), buildId: z.string(), snapshotId: z.string() }) },\n responses: {\n 200: { content: { \"application/json\": { schema: okSchema } }, description: \"Snapshot approved\" },\n ...notFound,\n },\n});\n\nconst rejectSnapshotRoute = createRoute({\n method: \"post\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/snapshots/{snapshotId}/reject\",\n request: { params: z.object({ slug: z.string(), buildId: z.string(), snapshotId: z.string() }) },\n responses: {\n 200: { content: { \"application/json\": { schema: okSchema } }, description: \"Snapshot rejected\" },\n ...notFound,\n },\n});\n\nconst approveAllRoute = createRoute({\n method: \"post\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/approve-all\",\n request: { params: z.object({ slug: z.string(), buildId: z.string() }) },\n responses: {\n 200: { content: { \"application/json\": { schema: okSchema } }, description: \"All snapshots approved\" },\n ...notFound,\n },\n});\n\nconst rejectAllRoute = createRoute({\n method: \"post\",\n path: \"/api/v1/projects/{slug}/builds/{buildId}/reject-all\",\n request: { params: z.object({ slug: z.string(), buildId: z.string() }) },\n responses: {\n 200: { content: { \"application/json\": { schema: okSchema } }, description: \"All snapshots rejected\" },\n ...notFound,\n },\n});\n\n/** Register the snapshot list, approve, reject, and bulk-review endpoints. */\nexport function registerSnapshots(app: ShelfApp): void {\n app.openapi(listSnapshotsRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...VIEW_ROLES);\n const build = await buildForProject(project.id, buildId);\n const snapshots = new SnapshotModel(getStore().db).listByBuild(build.id);\n return c.json(await snapshots);\n });\n\n app.openapi(approveSnapshotRoute, async (c) => {\n const { slug, buildId, snapshotId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...APPROVER_ROLES);\n const build = await buildForProject(project.id, buildId);\n await snapshotForBuild(build, snapshotId);\n const userId = getStore().user?.id ?? \"anonymous\";\n await approveSnapshot(snapshotId, userId);\n return c.json({ ok: true });\n });\n\n app.openapi(rejectSnapshotRoute, async (c) => {\n const { slug, buildId, snapshotId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...APPROVER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const snapshot = await snapshotForBuild(build, snapshotId);\n const userId = getStore().user?.id ?? \"anonymous\";\n await new SnapshotModel(getStore().db).review(snapshot.id, \"rejected\", userId);\n await refreshBuild(build.id);\n return c.json({ ok: true });\n });\n\n app.openapi(approveAllRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...APPROVER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const snapshots = await new SnapshotModel(getStore().db).listByBuild(build.id);\n const userId = getStore().user?.id ?? \"anonymous\";\n await Promise.all(\n snapshots\n .filter((snapshot) => snapshot.status === \"new\" || snapshot.status === \"changed\")\n .map(async (snapshot) => {\n await approveSnapshot(snapshot.id, userId);\n }),\n );\n return c.json({ ok: true });\n });\n\n app.openapi(rejectAllRoute, async (c) => {\n const { slug, buildId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...APPROVER_ROLES);\n const build = await buildForProject(project.id, buildId);\n const snapshots = await new SnapshotModel(getStore().db).listByBuild(build.id);\n const userId = getStore().user?.id ?? \"anonymous\";\n await Promise.all(\n snapshots\n .filter((snapshot) => snapshot.status === \"new\" || snapshot.status === \"changed\")\n .map(async (snapshot) => {\n await new SnapshotModel(getStore().db).review(snapshot.id, \"rejected\", userId);\n }),\n );\n await refreshBuild(build.id);\n return c.json({ ok: true });\n });\n}"],"mappings":"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"}
|
|
@@ -63,6 +63,7 @@ function toPublic(row) {
|
|
|
63
63
|
updatedAt: row.updatedAt
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
|
+
/** Register the git status-config list, create, and delete endpoints. */
|
|
66
67
|
function registerStatusConfigs(app) {
|
|
67
68
|
app.openapi(listRoute, async (c) => {
|
|
68
69
|
const { slug } = c.req.valid("param");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status-configs.mjs","names":[],"sources":["../../src/routers/status-configs.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\n\nimport type { ShelfApp } from \"../index.tsx\";\nimport { StatusConfigModel } from \"../models/status-config.ts\";\nimport type { ProjectStatusConfig } from \"../schema.ts\";\nimport { getStore } from \"../store.ts\";\nimport { resolveAuthorizedProject } from \"./helpers.ts\";\nimport { notFound, statusConfigCreateSchema, statusConfigSchema, unauthorized } from \"./schemas.ts\";\n\nconst ADMIN_ROLES = [\"admin\"] as const;\n\nconst listRoute = createRoute({\n method: \"get\",\n path: \"/api/v1/projects/{slug}/status-configs\",\n request: { params: z.object({ slug: z.string() }) },\n responses: {\n 200: { content: { \"application/json\": { schema: statusConfigSchema.array() } }, description: \"List status configs\" },\n ...notFound,\n ...unauthorized,\n },\n});\n\nconst createRouteDef = createRoute({\n method: \"post\",\n path: \"/api/v1/projects/{slug}/status-configs\",\n request: {\n params: z.object({ slug: z.string() }),\n body: { content: { \"application/json\": { schema: statusConfigCreateSchema } } },\n },\n responses: {\n 201: { content: { \"application/json\": { schema: statusConfigSchema } }, description: \"Created status config\" },\n 400: { content: { \"application/json\": { schema: z.object({ message: z.string() }) } }, description: \"Bad request\" },\n ...notFound,\n },\n});\n\nconst deleteRoute = createRoute({\n method: \"delete\",\n path: \"/api/v1/projects/{slug}/status-configs/{id}\",\n request: { params: z.object({ slug: z.string(), id: z.string() }) },\n responses: {\n 204: { description: \"Deleted status config\" },\n ...notFound,\n },\n});\n\nfunction toPublic(row: ProjectStatusConfig): {\n id: string;\n projectId: string;\n provider: string;\n config: Record<string, unknown>;\n hasToken: boolean;\n createdAt: string;\n updatedAt: string;\n} {\n return {\n id: row.id,\n projectId: row.projectId,\n provider: row.provider,\n config: JSON.parse(row.config) as Record<string, unknown>,\n hasToken: true,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nexport function registerStatusConfigs(app: ShelfApp): void {\n app.openapi(listRoute, async (c) => {\n const { slug } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, \"admin\");\n const model = new StatusConfigModel(getStore().db, getStore().config.secret);\n const rows = await model.list(project.id);\n return c.json(rows.map((row) => toPublic(row)));\n });\n\n app.openapi(createRouteDef, async (c) => {\n const { slug } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...ADMIN_ROLES);\n const body = c.req.valid(\"json\");\n const providers = getStore().gitHosts;\n const provider = providers.find((p) => p.metadata.kind === body.provider);\n if (!provider) {\n return c.json({ message: `Unknown provider: ${body.provider}` }, 400);\n }\n const parsed = provider.metadata.schema.safeParse(body.config);\n if (!parsed.success) {\n return c.json({ message: parsed.error.message }, 400);\n }\n const model = new StatusConfigModel(getStore().db, getStore().config.secret);\n const row = await model.create(project.id, {\n provider: body.provider,\n config: parsed.data,\n token: body.token,\n });\n return c.json(toPublic(row), 201);\n });\n\n app.openapi(deleteRoute, async (c) => {\n const { slug, id } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...ADMIN_ROLES);\n const model = new StatusConfigModel(getStore().db, getStore().config.secret);\n const existing = await model.get(project.id, id);\n if (!existing) {\n return c.json({ message: \"Status config not found\" }, 404);\n }\n await model.remove(project.id, id);\n return c.body(null, 204);\n });\n}\n"],"mappings":";;;;;;;;;AASA,MAAM,cAAc,CAAC,OAAO;AAE5B,MAAM,YAAY,YAAY;CAC5B,QAAQ;CACR,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE;CAClD,WAAW;EACT,KAAK;GAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,mBAAmB,MAAM,EAAE,EAAE;GAAG,aAAa;EAAsB;EACnH,GAAG;EACH,GAAG;CACL;AACF,CAAC;AAED,MAAM,iBAAiB,YAAY;CACjC,QAAQ;CACR,MAAM;CACN,SAAS;EACP,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;EACrC,MAAM,EAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,yBAAyB,EAAE,EAAE;CAChF;CACA,WAAW;EACT,KAAK;GAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,mBAAmB,EAAE;GAAG,aAAa;EAAwB;EAC7G,KAAK;GAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE;GAAG,aAAa;EAAc;EAClH,GAAG;CACL;AACF,CAAC;AAED,MAAM,cAAc,YAAY;CAC9B,QAAQ;CACR,MAAM;CACN,SAAS,EAAE,QAAQ,EAAE,OAAO;EAAE,MAAM,EAAE,OAAO;EAAG,IAAI,EAAE,OAAO;CAAE,CAAC,EAAE;CAClE,WAAW;EACT,KAAK,EAAE,aAAa,wBAAwB;EAC5C,GAAG;CACL;AACF,CAAC;AAED,SAAS,SAAS,KAQhB;CACA,OAAO;EACL,IAAI,IAAI;EACR,WAAW,IAAI;EACf,UAAU,IAAI;EACd,QAAQ,KAAK,MAAM,IAAI,MAAM;EAC7B,UAAU;EACV,WAAW,IAAI;EACf,WAAW,IAAI;CACjB;AACF
|
|
1
|
+
{"version":3,"file":"status-configs.mjs","names":[],"sources":["../../src/routers/status-configs.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\n\nimport type { ShelfApp } from \"../index.tsx\";\nimport { StatusConfigModel } from \"../models/status-config.ts\";\nimport type { ProjectStatusConfig } from \"../schema.ts\";\nimport { getStore } from \"../store.ts\";\nimport { resolveAuthorizedProject } from \"./helpers.ts\";\nimport { notFound, statusConfigCreateSchema, statusConfigSchema, unauthorized } from \"./schemas.ts\";\n\nconst ADMIN_ROLES = [\"admin\"] as const;\n\nconst listRoute = createRoute({\n method: \"get\",\n path: \"/api/v1/projects/{slug}/status-configs\",\n request: { params: z.object({ slug: z.string() }) },\n responses: {\n 200: { content: { \"application/json\": { schema: statusConfigSchema.array() } }, description: \"List status configs\" },\n ...notFound,\n ...unauthorized,\n },\n});\n\nconst createRouteDef = createRoute({\n method: \"post\",\n path: \"/api/v1/projects/{slug}/status-configs\",\n request: {\n params: z.object({ slug: z.string() }),\n body: { content: { \"application/json\": { schema: statusConfigCreateSchema } } },\n },\n responses: {\n 201: { content: { \"application/json\": { schema: statusConfigSchema } }, description: \"Created status config\" },\n 400: { content: { \"application/json\": { schema: z.object({ message: z.string() }) } }, description: \"Bad request\" },\n ...notFound,\n },\n});\n\nconst deleteRoute = createRoute({\n method: \"delete\",\n path: \"/api/v1/projects/{slug}/status-configs/{id}\",\n request: { params: z.object({ slug: z.string(), id: z.string() }) },\n responses: {\n 204: { description: \"Deleted status config\" },\n ...notFound,\n },\n});\n\nfunction toPublic(row: ProjectStatusConfig): {\n id: string;\n projectId: string;\n provider: string;\n config: Record<string, unknown>;\n hasToken: boolean;\n createdAt: string;\n updatedAt: string;\n} {\n return {\n id: row.id,\n projectId: row.projectId,\n provider: row.provider,\n config: JSON.parse(row.config) as Record<string, unknown>,\n hasToken: true,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\n/** Register the git status-config list, create, and delete endpoints. */\nexport function registerStatusConfigs(app: ShelfApp): void {\n app.openapi(listRoute, async (c) => {\n const { slug } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, \"admin\");\n const model = new StatusConfigModel(getStore().db, getStore().config.secret);\n const rows = await model.list(project.id);\n return c.json(rows.map((row) => toPublic(row)));\n });\n\n app.openapi(createRouteDef, async (c) => {\n const { slug } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...ADMIN_ROLES);\n const body = c.req.valid(\"json\");\n const providers = getStore().gitHosts;\n const provider = providers.find((p) => p.metadata.kind === body.provider);\n if (!provider) {\n return c.json({ message: `Unknown provider: ${body.provider}` }, 400);\n }\n const parsed = provider.metadata.schema.safeParse(body.config);\n if (!parsed.success) {\n return c.json({ message: parsed.error.message }, 400);\n }\n const model = new StatusConfigModel(getStore().db, getStore().config.secret);\n const row = await model.create(project.id, {\n provider: body.provider,\n config: parsed.data,\n token: body.token,\n });\n return c.json(toPublic(row), 201);\n });\n\n app.openapi(deleteRoute, async (c) => {\n const { slug, id } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...ADMIN_ROLES);\n const model = new StatusConfigModel(getStore().db, getStore().config.secret);\n const existing = await model.get(project.id, id);\n if (!existing) {\n return c.json({ message: \"Status config not found\" }, 404);\n }\n await model.remove(project.id, id);\n return c.body(null, 204);\n });\n}\n"],"mappings":"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"}
|