@storyshelf/core 0.1.3 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -46
- package/dist/adapter/auth.d.mts +4 -4
- package/dist/adapter/auth.mjs.map +1 -1
- package/dist/{adapters → adapter}/capture-queue.d.mts +4 -4
- package/dist/adapter/capture-queue.mjs +4 -0
- package/dist/adapter/capture-runner.d.mts +7 -7
- package/dist/adapter/database.d.mts +20 -9
- package/dist/adapter/database.mjs +60 -1
- package/dist/adapter/database.mjs.map +1 -0
- package/dist/adapter/git-host/comments.d.mts +41 -0
- package/dist/adapter/git-host/comments.mjs +54 -0
- package/dist/adapter/git-host/comments.mjs.map +1 -0
- package/dist/adapter/git-host.d.mts +3 -7
- package/dist/adapter/init.d.mts +58 -0
- package/dist/adapter/init.mjs +93 -0
- package/dist/adapter/init.mjs.map +1 -0
- package/dist/adapter/metadata.d.mts +83 -0
- package/dist/adapter/metadata.mjs +4 -0
- package/dist/adapter/storage.d.mts +15 -4
- package/dist/adapters/webhook-events.mjs +12 -4
- package/dist/adapters/webhook-events.mjs.map +1 -1
- package/dist/capture/adapter.d.mts +5 -1
- package/dist/capture/dispatch.mjs +150 -0
- package/dist/capture/dispatch.mjs.map +1 -0
- package/dist/capture/orchestrator.d.mts +2 -0
- package/dist/capture/orchestrator.mjs +5 -44
- package/dist/capture/orchestrator.mjs.map +1 -1
- package/dist/capture/pipeline.d.mts +4 -1
- package/dist/capture/pipeline.mjs +6 -6
- package/dist/capture/pipeline.mjs.map +1 -1
- package/dist/capture/queue.d.mts +2 -1
- package/dist/capture/queue.mjs +2 -1
- package/dist/capture/queue.mjs.map +1 -1
- package/dist/capture/skip-checks.mjs +76 -0
- package/dist/capture/skip-checks.mjs.map +1 -0
- package/dist/capture/statics.mjs +124 -0
- package/dist/capture/statics.mjs.map +1 -0
- package/dist/capture/status-fanout.mjs.map +1 -1
- package/dist/capture/storybook.mjs.map +1 -1
- package/dist/capture.d.mts +8 -0
- package/dist/capture.mjs +9 -0
- package/dist/config.d.mts +10 -2
- package/dist/config.mjs +14 -2
- package/dist/config.mjs.map +1 -1
- package/dist/ddl.d.mts +1 -1
- package/dist/ddl.mjs +1 -1
- package/dist/ddl.mjs.map +1 -1
- package/dist/diff/engine.mjs.map +1 -1
- package/dist/diff/options.d.mts +3 -1
- package/dist/diff.d.mts +5 -0
- package/dist/diff.mjs +6 -0
- package/dist/index.d.mts +32 -25
- package/dist/index.mjs +207 -253
- package/dist/index.mjs.map +1 -1
- package/dist/logger.d.mts +3 -3
- package/dist/middleware/auth-gate.mjs +36 -0
- package/dist/middleware/auth-gate.mjs.map +1 -0
- package/dist/middleware/csrf.mjs.map +1 -1
- package/dist/middleware/request-log.mjs +28 -0
- package/dist/middleware/request-log.mjs.map +1 -0
- package/dist/middleware/store-scope.mjs +29 -0
- package/dist/middleware/store-scope.mjs.map +1 -0
- package/dist/models/baseline.mjs +7 -5
- package/dist/models/baseline.mjs.map +1 -1
- package/dist/models/build.mjs +3 -1
- package/dist/models/build.mjs.map +1 -1
- package/dist/models/comment.mjs +2 -1
- package/dist/models/comment.mjs.map +1 -1
- package/dist/models/label.mjs +2 -1
- package/dist/models/label.mjs.map +1 -1
- package/dist/models/member.mjs +1 -1
- package/dist/models/member.mjs.map +1 -1
- package/dist/models/project.mjs +1 -1
- package/dist/models/project.mjs.map +1 -1
- package/dist/models/snapshot.mjs +1 -1
- package/dist/models/snapshot.mjs.map +1 -1
- package/dist/models/status-config.mjs +1 -1
- package/dist/models/status-config.mjs.map +1 -1
- package/dist/models/token.mjs +1 -1
- package/dist/models/token.mjs.map +1 -1
- package/dist/models/webhook.mjs +11 -3
- package/dist/models/webhook.mjs.map +1 -1
- package/dist/pages/build-detail.mjs +9 -4
- package/dist/pages/build-detail.mjs.map +1 -1
- package/dist/pages/build-diff-comments.mjs +113 -0
- package/dist/pages/build-diff-comments.mjs.map +1 -0
- package/dist/pages/build-diff-header.mjs +163 -0
- package/dist/pages/build-diff-header.mjs.map +1 -0
- package/dist/pages/build-diff-nav.mjs +66 -0
- package/dist/pages/build-diff-nav.mjs.map +1 -0
- package/dist/pages/build-diff-viewer.mjs +197 -0
- package/dist/pages/build-diff-viewer.mjs.map +1 -0
- package/dist/pages/build-diff.mjs +65 -413
- package/dist/pages/build-diff.mjs.map +1 -1
- package/dist/pages/compute-jobs.mjs +1 -1
- package/dist/pages/compute-jobs.mjs.map +1 -1
- package/dist/pages/label-detail.mjs +4 -3
- package/dist/pages/label-detail.mjs.map +1 -1
- package/dist/pages/login.mjs.map +1 -1
- package/dist/pages/project-builds.mjs +5 -3
- package/dist/pages/project-builds.mjs.map +1 -1
- package/dist/pages/project-create.mjs.map +1 -1
- package/dist/pages/project-settings.mjs.map +1 -1
- package/dist/pages/projects.mjs +11 -6
- package/dist/pages/projects.mjs.map +1 -1
- package/dist/pages/root.mjs +6 -3
- package/dist/pages/root.mjs.map +1 -1
- package/dist/pages/settings-general.mjs.map +1 -1
- package/dist/pages/settings-labels.mjs +1 -1
- package/dist/pages/settings-labels.mjs.map +1 -1
- package/dist/pages/settings-members.mjs +1 -1
- package/dist/pages/settings-members.mjs.map +1 -1
- package/dist/pages/settings-status.mjs +2 -1
- package/dist/pages/settings-status.mjs.map +1 -1
- package/dist/pages/settings-tests.mjs +3 -3
- package/dist/pages/settings-tests.mjs.map +1 -1
- package/dist/pages/settings-tokens.mjs.map +1 -1
- package/dist/pages/settings-webhooks.mjs.map +1 -1
- package/dist/pages/storybook.mjs +38 -1
- package/dist/pages/storybook.mjs.map +1 -1
- package/dist/paths.mjs.map +1 -0
- package/dist/retention/purge.mjs +6 -3
- package/dist/retention/purge.mjs.map +1 -1
- package/dist/routers/admin.mjs +1 -1
- package/dist/routers/admin.mjs.map +1 -1
- package/dist/routers/assets.mjs.map +1 -1
- package/dist/routers/auth.mjs.map +1 -1
- package/dist/routers/builds.handlers.mjs +106 -21
- package/dist/routers/builds.handlers.mjs.map +1 -1
- package/dist/routers/builds.mjs +54 -34
- package/dist/routers/builds.mjs.map +1 -1
- package/dist/routers/comments.mjs.map +1 -1
- package/dist/routers/health.mjs +137 -0
- package/dist/routers/health.mjs.map +1 -0
- package/dist/routers/helpers.mjs +2 -2
- package/dist/routers/helpers.mjs.map +1 -1
- package/dist/routers/htmx.mjs.map +1 -1
- package/dist/routers/labels.mjs +1 -1
- package/dist/routers/labels.mjs.map +1 -1
- package/dist/routers/media.mjs +2 -2
- package/dist/routers/media.mjs.map +1 -1
- package/dist/routers/members.mjs.map +1 -1
- package/dist/routers/projects.mjs +1 -1
- package/dist/routers/projects.mjs.map +1 -1
- package/dist/routers/schemas.mjs.map +1 -1
- package/dist/routers/settings-general.mjs +95 -0
- package/dist/routers/settings-general.mjs.map +1 -0
- package/dist/routers/settings-labels.mjs +49 -0
- package/dist/routers/settings-labels.mjs.map +1 -0
- package/dist/routers/settings-members.mjs +30 -0
- package/dist/routers/settings-members.mjs.map +1 -0
- package/dist/routers/settings-status.mjs +73 -0
- package/dist/routers/settings-status.mjs.map +1 -0
- package/dist/routers/settings-tokens.mjs +40 -0
- package/dist/routers/settings-tokens.mjs.map +1 -0
- package/dist/routers/settings-webhooks.mjs +63 -0
- package/dist/routers/settings-webhooks.mjs.map +1 -0
- package/dist/routers/settings.handlers.mjs +93 -0
- package/dist/routers/settings.handlers.mjs.map +1 -0
- package/dist/routers/settings.mjs +13 -247
- package/dist/routers/settings.mjs.map +1 -1
- package/dist/routers/snapshots.mjs +1 -1
- package/dist/routers/snapshots.mjs.map +1 -1
- package/dist/routers/status-configs.mjs +1 -1
- package/dist/routers/status-configs.mjs.map +1 -1
- package/dist/routers/storybook.mjs +11 -4
- package/dist/routers/storybook.mjs.map +1 -1
- package/dist/routers/tokens.mjs +1 -1
- package/dist/routers/tokens.mjs.map +1 -1
- package/dist/routers/ui.mjs +4 -4
- package/dist/routers/ui.mjs.map +1 -1
- package/dist/routers/webhooks.mjs +6 -6
- package/dist/routers/webhooks.mjs.map +1 -1
- package/dist/schema/baseline.d.mts +18 -0
- package/dist/schema/baseline.mjs +22 -0
- package/dist/schema/baseline.mjs.map +1 -0
- package/dist/schema/build.d.mts +26 -0
- package/dist/schema/build.mjs +29 -0
- package/dist/schema/build.mjs.map +1 -0
- package/dist/schema/comment.d.mts +19 -0
- package/dist/schema/comment.mjs +26 -0
- package/dist/schema/comment.mjs.map +1 -0
- package/dist/schema/label.d.mts +25 -0
- package/dist/schema/label.mjs +30 -0
- package/dist/schema/label.mjs.map +1 -0
- package/dist/schema/member.d.mts +15 -0
- package/dist/schema/member.mjs +19 -0
- package/dist/schema/member.mjs.map +1 -0
- package/dist/schema/project.d.mts +34 -0
- package/dist/schema/project.mjs +25 -0
- package/dist/schema/project.mjs.map +1 -0
- package/dist/schema/snapshot.d.mts +30 -0
- package/dist/schema/snapshot.mjs +34 -0
- package/dist/schema/snapshot.mjs.map +1 -0
- package/dist/schema/status-config.d.mts +16 -0
- package/dist/schema/status-config.mjs +20 -0
- package/dist/schema/status-config.mjs.map +1 -0
- package/dist/schema/token.d.mts +15 -0
- package/dist/schema/token.mjs +19 -0
- package/dist/schema/token.mjs.map +1 -0
- package/dist/schema/user.d.mts +17 -0
- package/dist/schema/user.mjs +19 -0
- package/dist/schema/user.mjs.map +1 -0
- package/dist/schema/webhook.d.mts +16 -0
- package/dist/schema/webhook.mjs +20 -0
- package/dist/schema/webhook.mjs.map +1 -0
- package/dist/schema.d.mts +13 -168
- package/dist/schema.mjs +12 -2
- package/dist/schema.mjs.map +1 -1
- package/dist/store.mjs.map +1 -1
- package/dist/types.d.mts +1 -2
- package/dist/types.mjs.map +1 -1
- package/dist/ui/document.mjs.map +1 -1
- package/dist/ui/feedback.mjs +24 -0
- package/dist/ui/feedback.mjs.map +1 -0
- package/dist/ui/{components.mjs → forms.mjs} +3 -18
- package/dist/ui/forms.mjs.map +1 -0
- package/dist/ui/styles.mjs.map +1 -1
- package/dist/urls.mjs.map +1 -1
- package/package.json +47 -45
- package/dist/adapters/metadata.d.mts +0 -24
- package/dist/models/baseline.d.mts +0 -21
- package/dist/models/build.d.mts +0 -53
- package/dist/models/comment.d.mts +0 -22
- package/dist/models/label.d.mts +0 -49
- package/dist/models/member.d.mts +0 -27
- package/dist/models/project.d.mts +0 -43
- package/dist/models/snapshot.d.mts +0 -47
- package/dist/models/token.d.mts +0 -33
- package/dist/models/webhook.d.mts +0 -38
- package/dist/retention/purge.d.mts +0 -31
- package/dist/schema-tables.mjs +0 -166
- package/dist/schema-tables.mjs.map +0 -1
- package/dist/ui/components.mjs.map +0 -1
- package/dist/ui/document.d.mts +0 -9
- package/dist/utils/paths.mjs.map +0 -1
- package/dist/utils/ulid.d.mts +0 -10
- /package/dist/{utils/paths.d.mts → paths.d.mts} +0 -0
- /package/dist/{utils/paths.mjs → paths.mjs} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.mjs","names":[],"sources":["../../src/routers/helpers.ts"],"sourcesContent":["import { eq } from \"drizzle-orm\";\nimport type { Context, Next } from \"hono\";\nimport { HTTPException } from \"hono/http-exception\";\nimport type { ContentfulStatusCode } from \"hono/utils/http-status\";\nimport type { ZodType } from \"zod\";\
|
|
1
|
+
{"version":3,"file":"helpers.mjs","names":[],"sources":["../../src/routers/helpers.ts"],"sourcesContent":["import { eq } from \"drizzle-orm\";\nimport type { Context, Next } from \"hono\";\nimport { HTTPException } from \"hono/http-exception\";\nimport type { ContentfulStatusCode } from \"hono/utils/http-status\";\nimport type { ZodType } from \"zod\";\nimport { MemberModel } from \"../models/member.ts\";\nimport { ProjectModel } from \"../models/project.ts\";\nimport { TokenModel } from \"../models/token.ts\";\nimport { projects } from \"../schema/project.ts\";\nimport type { Project } from \"../schema/project.ts\";\nimport { getStore } from \"../store.ts\";\nimport type { ProjectRole } from \"../types.ts\";\nimport { sha256 } from \"../utils/hash.ts\";\n\n/** Send a JSON response with the given status code. */\nexport function json(c: Context, data: unknown, status: ContentfulStatusCode = 200): Response {\n return c.json(data, status);\n}\n\n/** Parse the request body as JSON and validate it against a Zod schema. */\nexport async function validJson<T>(c: Context, schema: ZodType<T>): Promise<T> {\n const result = schema.safeParse(await c.req.json());\n if (!result.success) {\n throw new HTTPException(400, { message: result.error.message });\n }\n return result.data;\n}\n\n/** Throw a 401 Unauthorized error for unauthenticated requests. */\nexport function unauthorized(): never {\n throw new HTTPException(401, { message: \"Unauthorized\" });\n}\n\n/** Throw a 403 Forbidden error for requests lacking the required role. */\nexport function forbidden(): never {\n throw new HTTPException(403, { message: \"Forbidden\" });\n}\n\n/** Throw a 404 Not Found error with a custom message. */\nexport function notFound(message = \"Not found\"): never {\n throw new HTTPException(404, { message });\n}\n\n/** Resolve the current session user's effective role on a project. */\nexport async function currentProjectRole(projectId: string): Promise<ProjectRole | null> {\n const { db, user } = getStore();\n if (!user) {\n return null;\n }\n return await new MemberModel(db).effectiveRole(user.role, projectId, user.id);\n}\n\n/** Build middleware that requires one of the given project roles. */\nexport function requireRole(...roles: ProjectRole[]) {\n return async (c: Context, next: Next): Promise<void> => {\n const projectId = c.req.param(\"projectId\") ?? c.req.param(\"slug\");\n if (!projectId) {\n forbidden();\n }\n const role = await currentProjectRole(projectId);\n if (!role || !roles.includes(role)) {\n forbidden();\n }\n await next();\n };\n}\n\n/** Look up a project by its URL slug, returning null when absent. */\nexport async function findProjectBySlug(slug: string): Promise<Project | null> {\n const { db } = getStore();\n const rows = await db.list(projects, { where: eq(projects.slug, slug), limit: 1 });\n return rows[0] ?? null;\n}\n\nasync function resolveProjectByToken(c: Context, slug: string): Promise<Project | null> {\n const authHeader = c.req.header(\"authorization\");\n if (authHeader?.startsWith(\"Bearer \")) {\n const token = authHeader.slice(\"Bearer \".length);\n const found = await new TokenModel(getStore().db).findByHash(sha256(token));\n if (!found) {\n unauthorized();\n }\n const project = await new ProjectModel(getStore().db).get(found.projectId);\n if (!project || project.slug !== slug) {\n forbidden();\n }\n return project;\n }\n return null;\n}\n\n/** Resolve a project by slug, honoring CLI bearer-token access. */\nexport async function resolveProject(c: Context, slug: string): Promise<Project> {\n const project = await resolveProjectByToken(c, slug);\n if (project) {\n return project;\n }\n const found = await findProjectBySlug(slug);\n if (!found) {\n notFound(\"Project not found\");\n }\n return found;\n}\n\n/**\n * Enforce the caller's project role for a given project.\n *\n * Authorization model (ADR 0008):\n * - No auth adapter configured -> all operations permitted (development mode).\n * - Bearer token (CLI) -> grants access to its own project regardless of role.\n * - Session user -> must have an effective role in `minRoles`.\n */\nexport async function assertRole(projectId: string, ...minRoles: ProjectRole[]): Promise<void> {\n if (!getStore().authEnabled) {\n return;\n }\n const role = await currentProjectRole(projectId);\n if (!role || !minRoles.includes(role)) {\n forbidden();\n }\n}\n\n/** Resolve a project by slug and enforce the caller's minimum role. */\nexport async function resolveAuthorizedProject(\n c: Context,\n slug: string,\n ...minRoles: ProjectRole[]\n): Promise<Project> {\n const project = await resolveProjectByToken(c, slug);\n if (project) {\n return project;\n }\n const found = await findProjectBySlug(slug);\n if (!found) {\n notFound(\"Project not found\");\n }\n await assertRole(found.id, ...minRoles);\n return found;\n}\n\n/** Require a site-level admin (or permit when auth is disabled). */\nexport function requireSiteAdmin(): void {\n if (!getStore().authEnabled) {\n return;\n }\n const { user } = getStore();\n if (user?.role !== \"admin\") {\n forbidden();\n }\n}\n\n/** Require the current session user to hold one of the given project roles. */\nexport async function requireProjectRole(\n projectId: string,\n ...minRoles: ProjectRole[]\n): Promise<void> {\n await assertRole(projectId, ...minRoles);\n}\n"],"mappings":";;;;;;;;;;;;;AA6BA,SAAgB,eAAsB;CACpC,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,eAAe,CAAC;AAC1D;;AAGA,SAAgB,YAAmB;CACjC,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,YAAY,CAAC;AACvD;;AAGA,SAAgB,SAAS,UAAU,aAAoB;CACrD,MAAM,IAAI,cAAc,KAAK,EAAE,QAAQ,CAAC;AAC1C;;AAGA,eAAsB,mBAAmB,WAAgD;CACvF,MAAM,EAAE,IAAI,SAAS,SAAS;CAC9B,IAAI,CAAC,MACH,OAAO;CAET,OAAO,MAAM,IAAI,YAAY,EAAE,CAAC,CAAC,cAAc,KAAK,MAAM,WAAW,KAAK,EAAE;AAC9E;;AAkBA,eAAsB,kBAAkB,MAAuC;CAC7E,MAAM,EAAE,OAAO,SAAS;CAExB,QAAO,MADY,GAAG,KAAK,UAAU;EAAE,OAAO,GAAG,SAAS,MAAM,IAAI;EAAG,OAAO;CAAE,CAAC,EAAA,CACrE,MAAM;AACpB;AAEA,eAAe,sBAAsB,GAAY,MAAuC;CACtF,MAAM,aAAa,EAAE,IAAI,OAAO,eAAe;CAC/C,IAAI,YAAY,WAAW,SAAS,GAAG;EACrC,MAAM,QAAQ,WAAW,MAAM,CAAgB;EAC/C,MAAM,QAAQ,MAAM,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,WAAW,OAAO,KAAK,CAAC;EAC1E,IAAI,CAAC,OACH,aAAa;EAEf,MAAM,UAAU,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,SAAS;EACzE,IAAI,CAAC,WAAW,QAAQ,SAAS,MAC/B,UAAU;EAEZ,OAAO;CACT;CACA,OAAO;AACT;;;;;;;;;AAuBA,eAAsB,WAAW,WAAmB,GAAG,UAAwC;CAC7F,IAAI,CAAC,SAAS,CAAC,CAAC,aACd;CAEF,MAAM,OAAO,MAAM,mBAAmB,SAAS;CAC/C,IAAI,CAAC,QAAQ,CAAC,SAAS,SAAS,IAAI,GAClC,UAAU;AAEd;;AAGA,eAAsB,yBACpB,GACA,MACA,GAAG,UACe;CAClB,MAAM,UAAU,MAAM,sBAAsB,GAAG,IAAI;CACnD,IAAI,SACF,OAAO;CAET,MAAM,QAAQ,MAAM,kBAAkB,IAAI;CAC1C,IAAI,CAAC,OACH,SAAS,mBAAmB;CAE9B,MAAM,WAAW,MAAM,IAAI,GAAG,QAAQ;CACtC,OAAO;AACT;;AAGA,SAAgB,mBAAyB;CACvC,IAAI,CAAC,SAAS,CAAC,CAAC,aACd;CAEF,MAAM,EAAE,SAAS,SAAS;CAC1B,IAAI,MAAM,SAAS,SACjB,UAAU;AAEd"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"htmx.mjs","names":[],"sources":["../../src/routers/htmx.ts"],"sourcesContent":["import type { Context } from \"hono\";\n\n/** Check whether the request came from HTMX (HX-Request header). */\nexport function isHxRequest(c: Context): boolean {\n return c.req.header(\"HX-Request\") === \"true\";\n}\n\n/** Redirect HTMX requests via HX-Redirect and plain requests via 302. */\nexport function hxRedirect(c: Context, url: string): Response {\n if (isHxRequest(c)) {\n c.header(\"HX-Redirect\", url);\n return c.body(null, 204);\n }\n return c.redirect(url, 302);\n}"],"mappings":";;;;;AAGA,SAAgB,YAAY,GAAqB;CAC/C,OAAO,EAAE,IAAI,OAAO,YAAY,MAAM;AACxC;;AAGA,SAAgB,WAAW,GAAY,KAAuB;CAC5D,IAAI,YAAY,CAAC,GAAG;EAClB,EAAE,OAAO,eAAe,GAAG;EAC3B,OAAO,EAAE,KAAK,MAAM,GAAG;CACzB;CACA,OAAO,EAAE,SAAS,KAAK,GAAG;AAC5B"}
|
|
1
|
+
{"version":3,"file":"htmx.mjs","names":[],"sources":["../../src/routers/htmx.ts"],"sourcesContent":["import type { Context } from \"hono\";\n\n/** Check whether the request came from HTMX (HX-Request header). */\nexport function isHxRequest(c: Context): boolean {\n return c.req.header(\"HX-Request\") === \"true\";\n}\n\n/** Redirect HTMX requests via HX-Redirect and plain requests via 302. */\nexport function hxRedirect(c: Context, url: string): Response {\n if (isHxRequest(c)) {\n c.header(\"HX-Redirect\", url);\n return c.body(null, 204);\n }\n return c.redirect(url, 302);\n}\n"],"mappings":";;;;;AAGA,SAAgB,YAAY,GAAqB;CAC/C,OAAO,EAAE,IAAI,OAAO,YAAY,MAAM;AACxC;;AAGA,SAAgB,WAAW,GAAY,KAAuB;CAC5D,IAAI,YAAY,CAAC,GAAG;EAClB,EAAE,OAAO,eAAe,GAAG;EAC3B,OAAO,EAAE,KAAK,MAAM,GAAG;CACzB;CACA,OAAO,EAAE,SAAS,KAAK,GAAG;AAC5B"}
|
package/dist/routers/labels.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "node:path";
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
|
-
import { LabelModel } from "../models/label.mjs";
|
|
5
4
|
import { getStore } from "../store.mjs";
|
|
5
|
+
import { LabelModel } from "../models/label.mjs";
|
|
6
6
|
import { resolveAuthorizedProject } from "./helpers.mjs";
|
|
7
7
|
import { labelTypeCreateSchema, labelTypeSchema, labelTypeUpdateSchema, notFound, unauthorized } from "./schemas.mjs";
|
|
8
8
|
import { createRoute, z } from "@hono/zod-openapi";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"labels.mjs","names":[],"sources":["../../src/routers/labels.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport { HTTPException } from \"hono/http-exception\";\nimport type { ShelfApp } from \"../index.tsx\";\
|
|
1
|
+
{"version":3,"file":"labels.mjs","names":[],"sources":["../../src/routers/labels.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport { HTTPException } from \"hono/http-exception\";\nimport type { ShelfApp } from \"../index.tsx\";\nimport { LabelModel } from \"../models/label.ts\";\nimport { getStore } from \"../store.ts\";\nimport type { ProjectRole } from \"../types.ts\";\nimport { resolveAuthorizedProject } from \"./helpers.ts\";\nimport {\n labelTypeCreateSchema,\n labelTypeSchema,\n labelTypeUpdateSchema,\n notFound,\n unauthorized,\n} from \"./schemas.ts\";\n\nconst VIEW_ROLES: readonly ProjectRole[] = [\"viewer\", \"developer\", \"approver\", \"admin\"];\nconst ADMIN_ROLES: readonly ProjectRole[] = [\"admin\"];\n\nconst listLabelsRoute = createRoute({\n method: \"get\",\n path: \"/api/v1/projects/{slug}/label-types\",\n request: { params: z.object({ slug: z.string() }) },\n responses: {\n 200: {\n content: { \"application/json\": { schema: labelTypeSchema.array() } },\n description: \"List label types\",\n },\n ...notFound,\n ...unauthorized,\n },\n});\n\nconst createLabelRoute = createRoute({\n method: \"post\",\n path: \"/api/v1/projects/{slug}/label-types\",\n request: {\n params: z.object({ slug: z.string() }),\n body: { content: { \"application/json\": { schema: labelTypeCreateSchema } } },\n },\n responses: {\n 201: {\n content: { \"application/json\": { schema: labelTypeSchema } },\n description: \"Created label type\",\n },\n ...notFound,\n },\n});\n\nconst deleteLabelRoute = createRoute({\n method: \"delete\",\n path: \"/api/v1/projects/{slug}/label-types/{key}\",\n request: { params: z.object({ slug: z.string(), key: z.string() }) },\n responses: {\n 204: { description: \"Deleted label type\" },\n ...notFound,\n },\n});\n\nconst updateLabelRoute = createRoute({\n method: \"patch\",\n path: \"/api/v1/projects/{slug}/label-types/{key}\",\n request: {\n params: z.object({ slug: z.string(), key: z.string() }),\n body: { content: { \"application/json\": { schema: labelTypeUpdateSchema } } },\n },\n responses: {\n 200: {\n content: { \"application/json\": { schema: labelTypeSchema } },\n description: \"Updated label type\",\n },\n ...notFound,\n },\n});\n\n/** Register the project label-type list, create, update, and delete endpoints. */\nexport function registerLabels(app: ShelfApp): void {\n app.openapi(listLabelsRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...VIEW_ROLES);\n return c.json(await new LabelModel(getStore().db).listTypes(project.id));\n });\n\n app.openapi(createLabelRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...ADMIN_ROLES);\n const body = c.req.valid(\"json\");\n return c.json(await new LabelModel(getStore().db).createType(project.id, body), 201);\n });\n\n app.openapi(deleteLabelRoute, async (c) => {\n const { slug, key } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...ADMIN_ROLES);\n await new LabelModel(getStore().db).removeType(project.id, key);\n return c.body(null, 204);\n });\n\n app.openapi(updateLabelRoute, async (c) => {\n const { slug, key } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...ADMIN_ROLES);\n const body = c.req.valid(\"json\");\n try {\n const updated = await new LabelModel(getStore().db).updateType(project.id, key, body);\n if (!updated) {\n throw new HTTPException(404, { message: \"Label type not found\" });\n }\n return c.json(updated);\n } catch (error) {\n if (error instanceof HTTPException) {\n throw error;\n }\n throw new HTTPException(400, {\n message: error instanceof Error ? error.message : \"Cannot update label type\",\n });\n }\n });\n}\n"],"mappings":"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"}
|
package/dist/routers/media.mjs
CHANGED
|
@@ -2,10 +2,10 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { BuildModel } from "../models/build.mjs";
|
|
5
|
-
import { getStore } from "../store.mjs";
|
|
6
|
-
import { notFound, resolveAuthorizedProject } from "./helpers.mjs";
|
|
7
5
|
import { BaselineModel } from "../models/baseline.mjs";
|
|
8
6
|
import { SnapshotModel } from "../models/snapshot.mjs";
|
|
7
|
+
import { getStore } from "../store.mjs";
|
|
8
|
+
import { notFound, resolveAuthorizedProject } from "./helpers.mjs";
|
|
9
9
|
//#region src/routers/media.ts
|
|
10
10
|
const VIEW_ROLES = [
|
|
11
11
|
"viewer",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"media.mjs","names":[],"sources":["../../src/routers/media.ts"],"sourcesContent":["import type { ShelfApp } from \"../index.tsx\";\
|
|
1
|
+
{"version":3,"file":"media.mjs","names":[],"sources":["../../src/routers/media.ts"],"sourcesContent":["import type { ShelfApp } from \"../index.tsx\";\nimport { BaselineModel } from \"../models/baseline.ts\";\nimport { BuildModel } from \"../models/build.ts\";\nimport { SnapshotModel } from \"../models/snapshot.ts\";\nimport type { Snapshot } from \"../schema/snapshot.ts\";\nimport { getStore } from \"../store.ts\";\nimport type { ProjectRole } from \"../types.ts\";\nimport { notFound, resolveAuthorizedProject } from \"./helpers.ts\";\n\nconst VIEW_ROLES: readonly ProjectRole[] = [\"viewer\", \"developer\", \"approver\", \"admin\"];\n\nconst PNG = \"image/png\";\n\nfunction imageResponse(buffer: Buffer): Response {\n return new Response(new Uint8Array(buffer), {\n headers: { \"content-type\": PNG, \"cache-control\": \"private, max-age=3600\" },\n });\n}\n\n/** Register the PNG image, diff, and baseline media endpoints for snapshots. */\nexport function registerMedia(app: ShelfApp): void {\n app.get(\"/api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/image\", async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.param(\"slug\"), ...VIEW_ROLES);\n const snapshot = await findSnapshot(\n c.req.param(\"buildId\"),\n c.req.param(\"snapshotId\"),\n project.id,\n );\n return imageResponse(await getStore().storage.read(snapshot.screenshotPath));\n });\n\n app.get(\"/api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/diff\", async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.param(\"slug\"), ...VIEW_ROLES);\n const snapshot = await findSnapshot(\n c.req.param(\"buildId\"),\n c.req.param(\"snapshotId\"),\n project.id,\n );\n if (!snapshot.diffPath) {\n notFound(\"No diff for this snapshot\");\n }\n return imageResponse(await getStore().storage.read(snapshot.diffPath));\n });\n\n app.get(\"/api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/baseline\", async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.param(\"slug\"), ...VIEW_ROLES);\n const snapshot = await findSnapshot(\n c.req.param(\"buildId\"),\n c.req.param(\"snapshotId\"),\n project.id,\n );\n const build = await new BuildModel(getStore().db).get(snapshot.buildId);\n if (!build) {\n notFound(\"Build not found\");\n }\n const baselines = new BaselineModel(getStore().db, getStore().storage);\n const baseline = await baselines.resolve(\n project.id,\n snapshot.storyId,\n snapshot.viewportName,\n build.gitBranch,\n project.gitDefaultBranch,\n );\n if (!baseline) {\n notFound(\"No baseline for this snapshot\");\n }\n return imageResponse(await baselines.read(baseline));\n });\n}\n\nasync function findSnapshot(\n buildId: string,\n snapshotId: string,\n projectId: string,\n): Promise<Snapshot> {\n const snapshot = await new SnapshotModel(getStore().db).get(snapshotId);\n if (!snapshot || snapshot.buildId !== buildId || snapshot.projectId !== projectId) {\n notFound(\"Snapshot not found\");\n }\n return snapshot;\n}\n"],"mappings":";;;;;;;;;AASA,MAAM,aAAqC;CAAC;CAAU;CAAa;CAAY;AAAO;AAEtF,MAAM,MAAM;AAEZ,SAAS,cAAc,QAA0B;CAC/C,OAAO,IAAI,SAAS,IAAI,WAAW,MAAM,GAAG,EAC1C,SAAS;EAAE,gBAAgB;EAAK,iBAAiB;CAAwB,EAC3E,CAAC;AACH;;AAGA,SAAgB,cAAc,KAAqB;CACjD,IAAI,IAAI,sEAAsE,OAAO,MAAM;EACzF,MAAM,UAAU,MAAM,yBAAyB,GAAG,EAAE,IAAI,MAAM,MAAM,GAAG,GAAG,UAAU;EACpF,MAAM,WAAW,MAAM,aACrB,EAAE,IAAI,MAAM,SAAS,GACrB,EAAE,IAAI,MAAM,YAAY,GACxB,QAAQ,EACV;EACA,OAAO,cAAc,MAAM,SAAS,CAAC,CAAC,QAAQ,KAAK,SAAS,cAAc,CAAC;CAC7E,CAAC;CAED,IAAI,IAAI,qEAAqE,OAAO,MAAM;EACxF,MAAM,UAAU,MAAM,yBAAyB,GAAG,EAAE,IAAI,MAAM,MAAM,GAAG,GAAG,UAAU;EACpF,MAAM,WAAW,MAAM,aACrB,EAAE,IAAI,MAAM,SAAS,GACrB,EAAE,IAAI,MAAM,YAAY,GACxB,QAAQ,EACV;EACA,IAAI,CAAC,SAAS,UACZ,SAAS,2BAA2B;EAEtC,OAAO,cAAc,MAAM,SAAS,CAAC,CAAC,QAAQ,KAAK,SAAS,QAAQ,CAAC;CACvE,CAAC;CAED,IAAI,IAAI,yEAAyE,OAAO,MAAM;EAC5F,MAAM,UAAU,MAAM,yBAAyB,GAAG,EAAE,IAAI,MAAM,MAAM,GAAG,GAAG,UAAU;EACpF,MAAM,WAAW,MAAM,aACrB,EAAE,IAAI,MAAM,SAAS,GACrB,EAAE,IAAI,MAAM,YAAY,GACxB,QAAQ,EACV;EACA,MAAM,QAAQ,MAAM,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,SAAS,OAAO;EACtE,IAAI,CAAC,OACH,SAAS,iBAAiB;EAE5B,MAAM,YAAY,IAAI,cAAc,SAAS,CAAC,CAAC,IAAI,SAAS,CAAC,CAAC,OAAO;EACrE,MAAM,WAAW,MAAM,UAAU,QAC/B,QAAQ,IACR,SAAS,SACT,SAAS,cACT,MAAM,WACN,QAAQ,gBACV;EACA,IAAI,CAAC,UACH,SAAS,+BAA+B;EAE1C,OAAO,cAAc,MAAM,UAAU,KAAK,QAAQ,CAAC;CACrD,CAAC;AACH;AAEA,eAAe,aACb,SACA,YACA,WACmB;CACnB,MAAM,WAAW,MAAM,IAAI,cAAc,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,UAAU;CACtE,IAAI,CAAC,YAAY,SAAS,YAAY,WAAW,SAAS,cAAc,WACtE,SAAS,oBAAoB;CAE/B,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"members.mjs","names":[],"sources":["../../src/routers/members.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport type { ShelfApp } from \"../index.tsx\";\
|
|
1
|
+
{"version":3,"file":"members.mjs","names":[],"sources":["../../src/routers/members.ts"],"sourcesContent":["import { createRoute, z } from \"@hono/zod-openapi\";\nimport type { ShelfApp } from \"../index.tsx\";\nimport { MemberModel } from \"../models/member.ts\";\nimport { getStore } from \"../store.ts\";\nimport type { ProjectRole } from \"../types.ts\";\nimport { resolveAuthorizedProject } from \"./helpers.ts\";\nimport {\n memberRoleSchema,\n memberSchema,\n memberSetSchema,\n notFound,\n unauthorized,\n} from \"./schemas.ts\";\n\nconst VIEW_ROLES: readonly ProjectRole[] = [\"viewer\", \"developer\", \"approver\", \"admin\"];\nconst ADMIN_ROLES: readonly ProjectRole[] = [\"admin\"];\n\nconst listMembersRoute = createRoute({\n method: \"get\",\n path: \"/api/v1/projects/{slug}/members\",\n request: { params: z.object({ slug: z.string() }) },\n responses: {\n 200: {\n content: { \"application/json\": { schema: memberSchema.array() } },\n description: \"List project members\",\n },\n ...notFound,\n ...unauthorized,\n },\n});\n\nconst setMemberRoute = createRoute({\n method: \"post\",\n path: \"/api/v1/projects/{slug}/members\",\n request: {\n params: z.object({ slug: z.string() }),\n body: { content: { \"application/json\": { schema: memberSetSchema } } },\n },\n responses: {\n 201: {\n content: { \"application/json\": { schema: memberSchema } },\n description: \"Added or updated member\",\n },\n ...notFound,\n },\n});\n\nconst updateMemberRoute = createRoute({\n method: \"patch\",\n path: \"/api/v1/projects/{slug}/members/{userId}\",\n request: {\n params: z.object({ slug: z.string(), userId: z.string() }),\n body: { content: { \"application/json\": { schema: memberRoleSchema } } },\n },\n responses: {\n 200: {\n content: { \"application/json\": { schema: memberSchema } },\n description: \"Updated member role\",\n },\n ...notFound,\n },\n});\n\nconst deleteMemberRoute = createRoute({\n method: \"delete\",\n path: \"/api/v1/projects/{slug}/members/{userId}\",\n request: { params: z.object({ slug: z.string(), userId: z.string() }) },\n responses: {\n 204: { description: \"Removed member\" },\n ...notFound,\n },\n});\n\n/** Register the project member list, upsert, and removal endpoints. */\nexport function registerMembers(app: ShelfApp): void {\n app.openapi(listMembersRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...VIEW_ROLES);\n return c.json(await new MemberModel(getStore().db).list(project.id));\n });\n\n app.openapi(setMemberRoute, async (c) => {\n const project = await resolveAuthorizedProject(c, c.req.valid(\"param\").slug, ...ADMIN_ROLES);\n const body = c.req.valid(\"json\");\n return c.json(\n await new MemberModel(getStore().db).set(project.id, body.userId, body.role),\n 201,\n );\n });\n\n app.openapi(updateMemberRoute, async (c) => {\n const { slug, userId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...ADMIN_ROLES);\n const body = c.req.valid(\"json\");\n return c.json(await new MemberModel(getStore().db).set(project.id, userId, body.role));\n });\n\n app.openapi(deleteMemberRoute, async (c) => {\n const { slug, userId } = c.req.valid(\"param\");\n const project = await resolveAuthorizedProject(c, slug, ...ADMIN_ROLES);\n await new MemberModel(getStore().db).remove(project.id, userId);\n return c.body(null, 204);\n });\n}\n"],"mappings":"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"}
|
|
@@ -2,8 +2,8 @@ import "node:path";
|
|
|
2
2
|
import "node:url";
|
|
3
3
|
import.meta.url;
|
|
4
4
|
import { ProjectModel } from "../models/project.mjs";
|
|
5
|
-
import { LabelModel } from "../models/label.mjs";
|
|
6
5
|
import { getStore } from "../store.mjs";
|
|
6
|
+
import { LabelModel } from "../models/label.mjs";
|
|
7
7
|
import { forbidden, requireSiteAdmin, resolveAuthorizedProject } from "./helpers.mjs";
|
|
8
8
|
import { badRequest, forbidden as forbidden$1, notFound, projectCreateSchema, projectSchema, projectUpdateSchema, unauthorized } from "./schemas.mjs";
|
|
9
9
|
import { createRoute, z } from "@hono/zod-openapi";
|
|
@@ -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\";\
|
|
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\";\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: {\n content: { \"application/json\": { schema: projectSchema.array() } },\n description: \"List projects\",\n },\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: {\n content: { \"application/json\": { schema: projectSchema } },\n description: \"Created project\",\n },\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: {\n content: { \"application/json\": { schema: projectSchema } },\n description: \"Fetch a project\",\n },\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: {\n content: { \"application/json\": { schema: projectSchema } },\n description: \"Updated project\",\n },\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}\n"],"mappings":"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"}
|
|
@@ -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\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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"}
|
|
1
|
+
{"version":3,"file":"schemas.mjs","names":[],"sources":["../../src/routers/schemas.ts"],"sourcesContent":["import { z } from \"@hono/zod-openapi\";\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\n .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 })\n .openapi(\"Project\");\n\n/** OpenAPI schema for creating a project. */\nexport const projectCreateSchema = z\n .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 })\n .openapi(\"ProjectCreateInput\");\n\n/** OpenAPI schema for patching a project. */\nexport const projectUpdateSchema = z\n .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 })\n .openapi(\"ProjectUpdateInput\");\n\n/** OpenAPI schema for a build record. */\nexport const buildSchema = z\n .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 })\n .openapi(\"Build\");\n\n/** OpenAPI schema for a snapshot record. */\nexport const snapshotSchema = z\n .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 })\n .openapi(\"Snapshot\");\n\n/** OpenAPI schema for a build comment. */\nexport const commentSchema = z\n .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 })\n .openapi(\"Comment\");\n\n/** OpenAPI schema for creating a build comment. */\nexport const commentCreateSchema = z\n .object({\n body: z.string().min(1),\n snapshotId: z.string().optional(),\n parentId: z.string().optional(),\n })\n .openapi(\"CommentCreateInput\");\n\n/** OpenAPI schema for a project label type. */\nexport const labelTypeSchema = z\n .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 })\n .openapi(\"LabelType\");\n\n/** OpenAPI schema for creating a label type. */\nexport const labelTypeCreateSchema = z\n .object({\n key: z.string().min(1),\n name: z.string().min(1),\n linkTemplate: z.string().optional(),\n color: z.string().optional(),\n })\n .openapi(\"LabelTypeCreateInput\");\n\n/** OpenAPI schema for patching a label type. */\nexport const labelTypeUpdateSchema = z\n .object({\n name: z.string().min(1).optional(),\n linkTemplate: z.string().nullable().optional(),\n color: z.string().nullable().optional(),\n })\n .openapi(\"LabelTypeUpdateInput\");\n\n/** OpenAPI schema for a public API token (hash omitted). */\nexport const tokenPublicSchema = z\n .object({\n id: z.string(),\n projectId: z.string(),\n name: z.string(),\n lastUsedAt: z.string().nullable(),\n createdAt: z.string(),\n })\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\n .object({\n name: z.string(),\n token: z.string(),\n })\n .openapi(\"TokenCreated\");\n\n/** OpenAPI schema for a project member record. */\nexport const memberSchema = z\n .object({\n id: z.string(),\n projectId: z.string(),\n userId: z.string(),\n role: z.enum(PROJECT_ROLES),\n createdAt: z.string(),\n })\n .openapi(\"ProjectMember\");\n\n/** OpenAPI schema for updating a member's role. */\nexport const memberRoleSchema = z\n .object({ role: z.enum(PROJECT_ROLES) })\n .openapi(\"MemberRoleInput\");\n\n/** OpenAPI schema for adding a member to a project. */\nexport const memberSetSchema = z\n .object({\n userId: z.string().min(1),\n role: z.enum(PROJECT_ROLES),\n })\n .openapi(\"MemberSetInput\");\n\n/** OpenAPI schema for a public webhook (secret omitted). */\nexport const webhookPublicSchema = z\n .object({\n id: z.string(),\n url: z.string(),\n events: z.array(z.string()),\n })\n .openapi(\"Webhook\");\n\n/** OpenAPI schema for creating a webhook. */\nexport const webhookCreateSchema = z\n .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 })\n .openapi(\"WebhookCreateInput\");\n\n/** OpenAPI schema for a newly created webhook including its signing secret. */\nexport const webhookCreatedSchema = z\n .object({\n id: z.string(),\n url: z.string(),\n events: z.array(z.string()),\n secret: z.string(),\n })\n .openapi(\"WebhookCreated\");\n\n/** OpenAPI schema for a git status-check configuration. */\nexport const statusConfigSchema = z\n .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 })\n .openapi(\"StatusConfig\");\n\n/** OpenAPI schema for creating a git status-check configuration. */\nexport const statusConfigCreateSchema = z\n .object({\n provider: z.string().min(1),\n config: z.record(z.string(), z.unknown()),\n token: z.string().min(1),\n })\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;\n"],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { ProjectModel } from "../models/project.mjs";
|
|
5
|
+
import { getStore } from "../store.mjs";
|
|
6
|
+
import { hxRedirect } from "./htmx.mjs";
|
|
7
|
+
import { asString, findProject, renderSettingsPage } from "./settings.handlers.mjs";
|
|
8
|
+
//#region src/routers/settings-general.ts
|
|
9
|
+
/** General project settings (name, repo, thresholds) and danger-zone delete. */
|
|
10
|
+
function registerGeneralSettings(app) {
|
|
11
|
+
app.get("/projects/:slug/settings", async (c) => c.html(await renderSettingsPage(c, "general")));
|
|
12
|
+
app.get("/projects/:slug/settings/tests", async (c) => c.html(await renderSettingsPage(c, "tests")));
|
|
13
|
+
app.post("/projects/:slug/settings", handleGeneralUpdate);
|
|
14
|
+
app.post("/projects/:slug/settings/tests", handleTestsUpdate);
|
|
15
|
+
app.post("/projects/:slug/delete", handleDeleteProject);
|
|
16
|
+
}
|
|
17
|
+
/** Read the general-settings form fields (name validated by the caller). */
|
|
18
|
+
function readGeneralFields(form) {
|
|
19
|
+
return {
|
|
20
|
+
gitRepository: asString(form.get("gitRepository")),
|
|
21
|
+
gitDefaultBranch: asString(form.get("gitDefaultBranch")),
|
|
22
|
+
pixelThreshold: asString(form.get("pixelThreshold")),
|
|
23
|
+
maxDiffRatio: asString(form.get("maxDiffRatio")),
|
|
24
|
+
publicBranchRegex: asString(form.get("publicBranchRegex"))
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
async function handleGeneralUpdate(c) {
|
|
28
|
+
const slug = c.req.param("slug") ?? "";
|
|
29
|
+
const project = await findProject(slug);
|
|
30
|
+
const form = await c.req.formData();
|
|
31
|
+
const name = form.get("name");
|
|
32
|
+
if (typeof name !== "string" || name.trim() === "") return c.html(await renderSettingsPage(c, "general", { errors: { name: "Name is required" } }) ?? "", 400);
|
|
33
|
+
return await persistGeneralUpdate(c, project, slug, {
|
|
34
|
+
...readGeneralFields(form),
|
|
35
|
+
name: name.trim()
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async function persistGeneralUpdate(c, project, slug, fields) {
|
|
39
|
+
try {
|
|
40
|
+
await new ProjectModel(getStore().db).update(project.id, {
|
|
41
|
+
name: fields.name,
|
|
42
|
+
gitRepository: fields.gitRepository ?? void 0,
|
|
43
|
+
gitDefaultBranch: fields.gitDefaultBranch ?? void 0,
|
|
44
|
+
pixelThreshold: fields.pixelThreshold ? Number(fields.pixelThreshold) : void 0,
|
|
45
|
+
maxDiffRatio: fields.maxDiffRatio ? Number(fields.maxDiffRatio) : void 0,
|
|
46
|
+
publicBranchRegex: fields.publicBranchRegex === "" ? null : fields.publicBranchRegex ?? void 0
|
|
47
|
+
});
|
|
48
|
+
return hxRedirect(c, `/projects/${slug}/settings`);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
const message = error instanceof Error ? error.message : "Failed to update";
|
|
51
|
+
return c.html(await renderSettingsPage(c, "general", { globalError: message }) ?? "", 400);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function handleTestsUpdate(c) {
|
|
55
|
+
const slug = c.req.param("slug") ?? "";
|
|
56
|
+
const project = await findProject(slug);
|
|
57
|
+
const { executePlay, playTimeoutMs } = readTestsFields(await c.req.formData());
|
|
58
|
+
const rangeError = playTimeoutError(playTimeoutMs);
|
|
59
|
+
if (rangeError) return c.html(await renderSettingsPage(c, "tests", { globalError: rangeError }) ?? "", 400);
|
|
60
|
+
return await persistTestsUpdate(c, project, slug, executePlay, playTimeoutMs);
|
|
61
|
+
}
|
|
62
|
+
/** Read the tests-tab form fields. */
|
|
63
|
+
function readTestsFields(form) {
|
|
64
|
+
const playTimeoutMsRaw = asString(form.get("playTimeoutMs"));
|
|
65
|
+
return {
|
|
66
|
+
executePlay: form.get("executePlay") === "true",
|
|
67
|
+
playTimeoutMs: playTimeoutMsRaw ? Number(playTimeoutMsRaw) : void 0
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** Range error for the play timeout, or null when valid. */
|
|
71
|
+
function playTimeoutError(playTimeoutMs) {
|
|
72
|
+
if (playTimeoutMs !== void 0 && (Number.isNaN(playTimeoutMs) || playTimeoutMs < 1e3 || playTimeoutMs > 3e4)) return "Play timeout must be between 1000 and 30000";
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
async function persistTestsUpdate(c, project, slug, executePlay, playTimeoutMs) {
|
|
76
|
+
try {
|
|
77
|
+
await new ProjectModel(getStore().db).update(project.id, {
|
|
78
|
+
executePlay,
|
|
79
|
+
playTimeoutMs: playTimeoutMs ?? void 0
|
|
80
|
+
});
|
|
81
|
+
return hxRedirect(c, `/projects/${slug}/settings/tests`);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
const message = error instanceof Error ? error.message : "Failed to update";
|
|
84
|
+
return c.html(await renderSettingsPage(c, "tests", { globalError: message }) ?? "", 400);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function handleDeleteProject(c) {
|
|
88
|
+
const project = await findProject(c.req.param("slug") ?? "");
|
|
89
|
+
await new ProjectModel(getStore().db).remove(project.id);
|
|
90
|
+
return hxRedirect(c, "/projects");
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
export { registerGeneralSettings };
|
|
94
|
+
|
|
95
|
+
//# sourceMappingURL=settings-general.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-general.mjs","names":[],"sources":["../../src/routers/settings-general.ts"],"sourcesContent":["import type { Context } from \"hono\";\nimport type { ShelfApp } from \"../index.tsx\";\nimport { ProjectModel } from \"../models/project.ts\";\nimport type { Project } from \"../schema/project.ts\";\nimport { getStore } from \"../store.ts\";\nimport { hxRedirect } from \"./htmx.ts\";\nimport { asString, findProject, renderSettingsPage } from \"./settings.handlers.ts\";\n\n/** General project settings (name, repo, thresholds) and danger-zone delete. */\nexport function registerGeneralSettings(app: ShelfApp): void {\n app.get(\"/projects/:slug/settings\", async (c) => c.html(await renderSettingsPage(c, \"general\")));\n app.get(\"/projects/:slug/settings/tests\", async (c) =>\n c.html(await renderSettingsPage(c, \"tests\")),\n );\n app.post(\"/projects/:slug/settings\", handleGeneralUpdate);\n app.post(\"/projects/:slug/settings/tests\", handleTestsUpdate);\n app.post(\"/projects/:slug/delete\", handleDeleteProject);\n}\n\ninterface GeneralFields {\n name: string;\n gitRepository?: string;\n gitDefaultBranch?: string;\n pixelThreshold?: string;\n maxDiffRatio?: string;\n publicBranchRegex?: string;\n}\n\n/** Read the general-settings form fields (name validated by the caller). */\nfunction readGeneralFields(form: FormData): Omit<GeneralFields, \"name\"> {\n return {\n gitRepository: asString(form.get(\"gitRepository\")),\n gitDefaultBranch: asString(form.get(\"gitDefaultBranch\")),\n pixelThreshold: asString(form.get(\"pixelThreshold\")),\n maxDiffRatio: asString(form.get(\"maxDiffRatio\")),\n publicBranchRegex: asString(form.get(\"publicBranchRegex\")),\n };\n}\n\nasync function handleGeneralUpdate(c: Context): Promise<Response> {\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(\n (await renderSettingsPage(c, \"general\", { errors: { name: \"Name is required\" } })) ?? \"\",\n 400,\n );\n }\n const fields: GeneralFields = { ...readGeneralFields(form), name: name.trim() };\n return await persistGeneralUpdate(c, project, slug, fields);\n}\n\nasync function persistGeneralUpdate(\n c: Context,\n project: Project,\n slug: string,\n fields: GeneralFields,\n): Promise<Response> {\n try {\n await new ProjectModel(getStore().db).update(project.id, {\n name: fields.name,\n gitRepository: fields.gitRepository ?? undefined,\n gitDefaultBranch: fields.gitDefaultBranch ?? undefined,\n pixelThreshold: fields.pixelThreshold ? Number(fields.pixelThreshold) : undefined,\n maxDiffRatio: fields.maxDiffRatio ? Number(fields.maxDiffRatio) : undefined,\n publicBranchRegex:\n fields.publicBranchRegex === \"\" ? null : (fields.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\nasync function handleTestsUpdate(c: Context): Promise<Response> {\n const slug = c.req.param(\"slug\") ?? \"\";\n const project = await findProject(slug);\n const form = await c.req.formData();\n const { executePlay, playTimeoutMs } = readTestsFields(form);\n const rangeError = playTimeoutError(playTimeoutMs);\n if (rangeError) {\n return c.html(\n (await renderSettingsPage(c, \"tests\", { globalError: rangeError })) ?? \"\",\n 400,\n );\n }\n return await persistTestsUpdate(c, project, slug, executePlay, playTimeoutMs);\n}\n\n/** Read the tests-tab form fields. */\nfunction readTestsFields(form: FormData): { executePlay: boolean; playTimeoutMs: number | undefined } {\n const playTimeoutMsRaw = asString(form.get(\"playTimeoutMs\"));\n return {\n executePlay: form.get(\"executePlay\") === \"true\",\n playTimeoutMs: playTimeoutMsRaw ? Number(playTimeoutMsRaw) : undefined,\n };\n}\n\n/** Range error for the play timeout, or null when valid. */\nfunction playTimeoutError(playTimeoutMs: number | undefined): string | null {\n if (\n playTimeoutMs !== undefined &&\n (Number.isNaN(playTimeoutMs) || playTimeoutMs < 1000 || playTimeoutMs > 30_000)\n ) {\n return \"Play timeout must be between 1000 and 30000\";\n }\n return null;\n}\n\nasync function persistTestsUpdate(\n c: Context,\n project: Project,\n slug: string,\n executePlay: boolean,\n playTimeoutMs: number | undefined,\n): Promise<Response> {\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\nasync function handleDeleteProject(c: Context): Promise<Response> {\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"],"mappings":";;;;;;;;;AASA,SAAgB,wBAAwB,KAAqB;CAC3D,IAAI,IAAI,4BAA4B,OAAO,MAAM,EAAE,KAAK,MAAM,mBAAmB,GAAG,SAAS,CAAC,CAAC;CAC/F,IAAI,IAAI,kCAAkC,OAAO,MAC/C,EAAE,KAAK,MAAM,mBAAmB,GAAG,OAAO,CAAC,CAC7C;CACA,IAAI,KAAK,4BAA4B,mBAAmB;CACxD,IAAI,KAAK,kCAAkC,iBAAiB;CAC5D,IAAI,KAAK,0BAA0B,mBAAmB;AACxD;;AAYA,SAAS,kBAAkB,MAA6C;CACtE,OAAO;EACL,eAAe,SAAS,KAAK,IAAI,eAAe,CAAC;EACjD,kBAAkB,SAAS,KAAK,IAAI,kBAAkB,CAAC;EACvD,gBAAgB,SAAS,KAAK,IAAI,gBAAgB,CAAC;EACnD,cAAc,SAAS,KAAK,IAAI,cAAc,CAAC;EAC/C,mBAAmB,SAAS,KAAK,IAAI,mBAAmB,CAAC;CAC3D;AACF;AAEA,eAAe,oBAAoB,GAA+B;CAChE,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM,KAAK;CACpC,MAAM,UAAU,MAAM,YAAY,IAAI;CACtC,MAAM,OAAO,MAAM,EAAE,IAAI,SAAS;CAClC,MAAM,OAAO,KAAK,IAAI,MAAM;CAC5B,IAAI,OAAO,SAAS,YAAY,KAAK,KAAK,MAAM,IAC9C,OAAO,EAAE,KACN,MAAM,mBAAmB,GAAG,WAAW,EAAE,QAAQ,EAAE,MAAM,mBAAmB,EAAE,CAAC,KAAM,IACtF,GACF;CAGF,OAAO,MAAM,qBAAqB,GAAG,SAAS,MAAM;EADpB,GAAG,kBAAkB,IAAI;EAAG,MAAM,KAAK,KAAK;CACnB,CAAC;AAC5D;AAEA,eAAe,qBACb,GACA,SACA,MACA,QACmB;CACnB,IAAI;EACF,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,QAAQ,IAAI;GACvD,MAAM,OAAO;GACb,eAAe,OAAO,iBAAiB,KAAA;GACvC,kBAAkB,OAAO,oBAAoB,KAAA;GAC7C,gBAAgB,OAAO,iBAAiB,OAAO,OAAO,cAAc,IAAI,KAAA;GACxE,cAAc,OAAO,eAAe,OAAO,OAAO,YAAY,IAAI,KAAA;GAClE,mBACE,OAAO,sBAAsB,KAAK,OAAQ,OAAO,qBAAqB,KAAA;EAC1E,CAAC;EACD,OAAO,WAAW,GAAG,aAAa,KAAK,UAAU;CACnD,SAAS,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;EACzD,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,WAAW,EAAE,aAAa,QAAQ,CAAC,KAAM,IAAI,GAAG;CAC7F;AACF;AAEA,eAAe,kBAAkB,GAA+B;CAC9D,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM,KAAK;CACpC,MAAM,UAAU,MAAM,YAAY,IAAI;CAEtC,MAAM,EAAE,aAAa,kBAAkB,gBAAgB,MADpC,EAAE,IAAI,SAAS,CACyB;CAC3D,MAAM,aAAa,iBAAiB,aAAa;CACjD,IAAI,YACF,OAAO,EAAE,KACN,MAAM,mBAAmB,GAAG,SAAS,EAAE,aAAa,WAAW,CAAC,KAAM,IACvE,GACF;CAEF,OAAO,MAAM,mBAAmB,GAAG,SAAS,MAAM,aAAa,aAAa;AAC9E;;AAGA,SAAS,gBAAgB,MAA6E;CACpG,MAAM,mBAAmB,SAAS,KAAK,IAAI,eAAe,CAAC;CAC3D,OAAO;EACL,aAAa,KAAK,IAAI,aAAa,MAAM;EACzC,eAAe,mBAAmB,OAAO,gBAAgB,IAAI,KAAA;CAC/D;AACF;;AAGA,SAAS,iBAAiB,eAAkD;CAC1E,IACE,kBAAkB,KAAA,MACjB,OAAO,MAAM,aAAa,KAAK,gBAAgB,OAAQ,gBAAgB,MAExE,OAAO;CAET,OAAO;AACT;AAEA,eAAe,mBACb,GACA,SACA,MACA,aACA,eACmB;CACnB,IAAI;EACF,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,QAAQ,IAAI;GACvD;GACA,eAAe,iBAAiB,KAAA;EAClC,CAAC;EACD,OAAO,WAAW,GAAG,aAAa,KAAK,gBAAgB;CACzD,SAAS,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;EACzD,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,SAAS,EAAE,aAAa,QAAQ,CAAC,KAAM,IAAI,GAAG;CAC3F;AACF;AAEA,eAAe,oBAAoB,GAA+B;CAChE,MAAM,UAAU,MAAM,YAAY,EAAE,IAAI,MAAM,MAAM,KAAK,EAAE;CAC3D,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,QAAQ,EAAE;CACvD,OAAO,WAAW,GAAG,WAAW;AAClC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { getStore } from "../store.mjs";
|
|
5
|
+
import { LabelModel } from "../models/label.mjs";
|
|
6
|
+
import { hxRedirect } from "./htmx.mjs";
|
|
7
|
+
import { asString, findProject, renderSettingsPage } from "./settings.handlers.mjs";
|
|
8
|
+
//#region src/routers/settings-labels.ts
|
|
9
|
+
/** Label-type settings (create custom types, delete removable ones). */
|
|
10
|
+
function registerLabelSettings(app) {
|
|
11
|
+
app.get("/projects/:slug/settings/labels", async (c) => c.html(await renderSettingsPage(c, "labels")));
|
|
12
|
+
app.post("/projects/:slug/settings/labels", handleCreateLabelType);
|
|
13
|
+
app.post("/projects/:slug/settings/labels/:key/delete", handleDeleteLabelType);
|
|
14
|
+
}
|
|
15
|
+
async function handleCreateLabelType(c) {
|
|
16
|
+
const project = await findProject(c.req.param("slug") ?? "");
|
|
17
|
+
const form = await c.req.formData();
|
|
18
|
+
const key = asString(form.get("key"));
|
|
19
|
+
const labelName = asString(form.get("labelName")) ?? asString(form.get("name"));
|
|
20
|
+
const linkTemplate = asString(form.get("linkTemplate"));
|
|
21
|
+
if (!key || !labelName) return c.html(await renderSettingsPage(c, "labels", { globalError: "Key and name are required" }) ?? "", 400);
|
|
22
|
+
return await persistLabelType(c, project.id, project.slug, key, labelName, linkTemplate);
|
|
23
|
+
}
|
|
24
|
+
async function persistLabelType(c, projectId, projectSlug, key, labelName, linkTemplate) {
|
|
25
|
+
try {
|
|
26
|
+
await new LabelModel(getStore().db).createType(projectId, {
|
|
27
|
+
key,
|
|
28
|
+
name: labelName,
|
|
29
|
+
linkTemplate
|
|
30
|
+
});
|
|
31
|
+
return hxRedirect(c, `/projects/${projectSlug}/settings/labels`);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
const message = error instanceof Error ? error.message : "Failed to create label";
|
|
34
|
+
return c.html(await renderSettingsPage(c, "labels", { globalError: message }) ?? "", 400);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function handleDeleteLabelType(c) {
|
|
38
|
+
const project = await findProject(c.req.param("slug") ?? "");
|
|
39
|
+
try {
|
|
40
|
+
await new LabelModel(getStore().db).removeType(project.id, c.req.param("key") ?? "");
|
|
41
|
+
} catch {
|
|
42
|
+
return c.html(await renderSettingsPage(c, "labels", { globalError: "Cannot delete built-in label" }) ?? "", 400);
|
|
43
|
+
}
|
|
44
|
+
return hxRedirect(c, `/projects/${project.slug}/settings/labels`);
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { registerLabelSettings };
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=settings-labels.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-labels.mjs","names":[],"sources":["../../src/routers/settings-labels.ts"],"sourcesContent":["import type { Context } from \"hono\";\nimport type { ShelfApp } from \"../index.tsx\";\nimport { LabelModel } from \"../models/label.ts\";\nimport { getStore } from \"../store.ts\";\nimport { hxRedirect } from \"./htmx.ts\";\nimport { asString, findProject, renderSettingsPage } from \"./settings.handlers.ts\";\n\n/** Label-type settings (create custom types, delete removable ones). */\nexport function registerLabelSettings(app: ShelfApp): void {\n app.get(\"/projects/:slug/settings/labels\", async (c) =>\n c.html(await renderSettingsPage(c, \"labels\")),\n );\n app.post(\"/projects/:slug/settings/labels\", handleCreateLabelType);\n app.post(\"/projects/:slug/settings/labels/:key/delete\", handleDeleteLabelType);\n}\n\nasync function handleCreateLabelType(c: Context): Promise<Response> {\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(\n (await renderSettingsPage(c, \"labels\", { globalError: \"Key and name are required\" })) ?? \"\",\n 400,\n );\n }\n return await persistLabelType(c, project.id, project.slug, key, labelName, linkTemplate);\n}\n\nasync function persistLabelType(\n c: Context,\n projectId: string,\n projectSlug: string,\n key: string,\n labelName: string,\n linkTemplate: string | undefined,\n): Promise<Response> {\n try {\n await new LabelModel(getStore().db).createType(projectId, {\n key,\n name: labelName,\n linkTemplate,\n });\n return hxRedirect(c, `/projects/${projectSlug}/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\nasync function handleDeleteLabelType(c: Context): Promise<Response> {\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(\n (await renderSettingsPage(c, \"labels\", { globalError: \"Cannot delete built-in label\" })) ??\n \"\",\n 400,\n );\n }\n return hxRedirect(c, `/projects/${project.slug}/settings/labels`);\n}\n"],"mappings":";;;;;;;;;AAQA,SAAgB,sBAAsB,KAAqB;CACzD,IAAI,IAAI,mCAAmC,OAAO,MAChD,EAAE,KAAK,MAAM,mBAAmB,GAAG,QAAQ,CAAC,CAC9C;CACA,IAAI,KAAK,mCAAmC,qBAAqB;CACjE,IAAI,KAAK,+CAA+C,qBAAqB;AAC/E;AAEA,eAAe,sBAAsB,GAA+B;CAClE,MAAM,UAAU,MAAM,YAAY,EAAE,IAAI,MAAM,MAAM,KAAK,EAAE;CAC3D,MAAM,OAAO,MAAM,EAAE,IAAI,SAAS;CAClC,MAAM,MAAM,SAAS,KAAK,IAAI,KAAK,CAAC;CACpC,MAAM,YAAY,SAAS,KAAK,IAAI,WAAW,CAAC,KAAK,SAAS,KAAK,IAAI,MAAM,CAAC;CAC9E,MAAM,eAAe,SAAS,KAAK,IAAI,cAAc,CAAC;CACtD,IAAI,CAAC,OAAO,CAAC,WACX,OAAO,EAAE,KACN,MAAM,mBAAmB,GAAG,UAAU,EAAE,aAAa,4BAA4B,CAAC,KAAM,IACzF,GACF;CAEF,OAAO,MAAM,iBAAiB,GAAG,QAAQ,IAAI,QAAQ,MAAM,KAAK,WAAW,YAAY;AACzF;AAEA,eAAe,iBACb,GACA,WACA,aACA,KACA,WACA,cACmB;CACnB,IAAI;EACF,MAAM,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,WAAW,WAAW;GACxD;GACA,MAAM;GACN;EACF,CAAC;EACD,OAAO,WAAW,GAAG,aAAa,YAAY,iBAAiB;CACjE,SAAS,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;EACzD,OAAO,EAAE,KAAM,MAAM,mBAAmB,GAAG,UAAU,EAAE,aAAa,QAAQ,CAAC,KAAM,IAAI,GAAG;CAC5F;AACF;AAEA,eAAe,sBAAsB,GAA+B;CAClE,MAAM,UAAU,MAAM,YAAY,EAAE,IAAI,MAAM,MAAM,KAAK,EAAE;CAC3D,IAAI;EACF,MAAM,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,WAAW,QAAQ,IAAI,EAAE,IAAI,MAAM,KAAK,KAAK,EAAE;CACrF,QAAQ;EACN,OAAO,EAAE,KACN,MAAM,mBAAmB,GAAG,UAAU,EAAE,aAAa,+BAA+B,CAAC,KACpF,IACF,GACF;CACF;CACA,OAAO,WAAW,GAAG,aAAa,QAAQ,KAAK,iBAAiB;AAClE"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { getStore } from "../store.mjs";
|
|
5
|
+
import { MemberModel } from "../models/member.mjs";
|
|
6
|
+
import { hxRedirect } from "./htmx.mjs";
|
|
7
|
+
import { asString, findProject, renderSettingsPage } from "./settings.handlers.mjs";
|
|
8
|
+
//#region src/routers/settings-members.ts
|
|
9
|
+
/** Project member settings (assign roles, remove members). */
|
|
10
|
+
function registerMemberSettings(app) {
|
|
11
|
+
app.get("/projects/:slug/settings/members", async (c) => c.html(await renderSettingsPage(c, "members")));
|
|
12
|
+
app.post("/projects/:slug/settings/members", async (c) => {
|
|
13
|
+
const project = await findProject(c.req.param("slug") ?? "");
|
|
14
|
+
const form = await c.req.formData();
|
|
15
|
+
const userId = asString(form.get("userId"));
|
|
16
|
+
const role = asString(form.get("role"));
|
|
17
|
+
if (!userId || !role) return c.html(await renderSettingsPage(c, "members", { globalError: "User and role are required" }) ?? "", 400);
|
|
18
|
+
await new MemberModel(getStore().db).set(project.id, userId, role);
|
|
19
|
+
return hxRedirect(c, `/projects/${project.slug}/settings/members`);
|
|
20
|
+
});
|
|
21
|
+
app.post("/projects/:slug/settings/members/:userId/remove", async (c) => {
|
|
22
|
+
const project = await findProject(c.req.param("slug") ?? "");
|
|
23
|
+
await new MemberModel(getStore().db).remove(project.id, c.req.param("userId") ?? "");
|
|
24
|
+
return hxRedirect(c, `/projects/${project.slug}/settings/members`);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { registerMemberSettings };
|
|
29
|
+
|
|
30
|
+
//# sourceMappingURL=settings-members.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-members.mjs","names":[],"sources":["../../src/routers/settings-members.ts"],"sourcesContent":["import type { ShelfApp } from \"../index.tsx\";\nimport { MemberModel } from \"../models/member.ts\";\nimport { getStore } from \"../store.ts\";\nimport { hxRedirect } from \"./htmx.ts\";\nimport { asString, findProject, renderSettingsPage } from \"./settings.handlers.ts\";\n\n/** Project member settings (assign roles, remove members). */\nexport function registerMemberSettings(app: ShelfApp): void {\n app.get(\"/projects/:slug/settings/members\", async (c) =>\n c.html(await renderSettingsPage(c, \"members\")),\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(\n (await renderSettingsPage(c, \"members\", { globalError: \"User and role are required\" })) ??\n \"\",\n 400,\n );\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"],"mappings":";;;;;;;;;AAOA,SAAgB,uBAAuB,KAAqB;CAC1D,IAAI,IAAI,oCAAoC,OAAO,MACjD,EAAE,KAAK,MAAM,mBAAmB,GAAG,SAAS,CAAC,CAC/C;CAEA,IAAI,KAAK,oCAAoC,OAAO,MAAM;EACxD,MAAM,UAAU,MAAM,YAAY,EAAE,IAAI,MAAM,MAAM,KAAK,EAAE;EAC3D,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,KACN,MAAM,mBAAmB,GAAG,WAAW,EAAE,aAAa,6BAA6B,CAAC,KACnF,IACF,GACF;EAEF,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,KAAK,EAAE;EAC3D,MAAM,IAAI,YAAY,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,QAAQ,IAAI,EAAE,IAAI,MAAM,QAAQ,KAAK,EAAE;EACnF,OAAO,WAAW,GAAG,aAAa,QAAQ,KAAK,kBAAkB;CACnE,CAAC;AACH"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { StatusConfigModel } from "../models/status-config.mjs";
|
|
5
|
+
import { getStore } from "../store.mjs";
|
|
6
|
+
import { hxRedirect } from "./htmx.mjs";
|
|
7
|
+
import { asString, findProject, renderSettingsPage } from "./settings.handlers.mjs";
|
|
8
|
+
//#region src/routers/settings-status.ts
|
|
9
|
+
/** Merge-gate status-config settings (provider configs + tokens, delete). */
|
|
10
|
+
function registerStatusSettings(app) {
|
|
11
|
+
app.get("/projects/:slug/settings/status", async (c) => c.html(await renderSettingsPage(c, "status")));
|
|
12
|
+
app.post("/projects/:slug/settings/status", handleCreateStatus);
|
|
13
|
+
app.post("/projects/:slug/settings/status/:id/delete", handleDeleteStatus);
|
|
14
|
+
}
|
|
15
|
+
async function unknownProviderResponse(c) {
|
|
16
|
+
return c.html(await renderSettingsPage(c, "status", { globalError: "Unknown git provider" }) ?? "", 400);
|
|
17
|
+
}
|
|
18
|
+
async function missingTokenResponse(c) {
|
|
19
|
+
return c.html(await renderSettingsPage(c, "status", { errors: { token: "Token is required" } }) ?? "", 400);
|
|
20
|
+
}
|
|
21
|
+
async function readStatusFields(c) {
|
|
22
|
+
const project = await findProject(c.req.param("slug") ?? "");
|
|
23
|
+
const form = await c.req.formData();
|
|
24
|
+
const provider = getStore().gitHosts.find((p) => p.metadata.kind === asString(form.get("provider")));
|
|
25
|
+
if (!provider) return await unknownProviderResponse(c);
|
|
26
|
+
const token = asString(form.get("token"));
|
|
27
|
+
if (!token) return await missingTokenResponse(c);
|
|
28
|
+
return {
|
|
29
|
+
project,
|
|
30
|
+
provider,
|
|
31
|
+
token,
|
|
32
|
+
configRaw: asString(form.get("config")) ?? "{}"
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async function handleCreateStatus(c) {
|
|
36
|
+
const fields = await readStatusFields(c);
|
|
37
|
+
if (fields instanceof Response) return fields;
|
|
38
|
+
return await createStatusFromForm(c, fields.project, fields.provider, fields.token, fields.configRaw);
|
|
39
|
+
}
|
|
40
|
+
async function createStatusFromForm(c, project, provider, token, configRaw) {
|
|
41
|
+
const config = parseJsonConfig(configRaw);
|
|
42
|
+
if (config === void 0) return c.html(await renderSettingsPage(c, "status", { errors: { config: "Config must be valid JSON" } }) ?? "", 400);
|
|
43
|
+
const parsed = provider.metadata.schema.safeParse(config);
|
|
44
|
+
if (!parsed.success) return c.html(await renderSettingsPage(c, "status", { errors: { config: parsed.error.message } }) ?? "", 400);
|
|
45
|
+
return await persistStatusConfig(c, project, provider, token, parsed.data);
|
|
46
|
+
}
|
|
47
|
+
async function persistStatusConfig(c, project, provider, token, config) {
|
|
48
|
+
const { db, config: shelfConfig } = getStore();
|
|
49
|
+
await new StatusConfigModel(db, shelfConfig.secret).create(project.id, {
|
|
50
|
+
provider: provider.metadata.kind,
|
|
51
|
+
config,
|
|
52
|
+
token
|
|
53
|
+
});
|
|
54
|
+
return hxRedirect(c, `/projects/${project.slug}/settings/status`);
|
|
55
|
+
}
|
|
56
|
+
/** Parse the provider config JSON, or undefined when invalid. */
|
|
57
|
+
function parseJsonConfig(raw) {
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(raw);
|
|
60
|
+
} catch {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async function handleDeleteStatus(c) {
|
|
65
|
+
const project = await findProject(c.req.param("slug") ?? "");
|
|
66
|
+
const { db, config: shelfConfig } = getStore();
|
|
67
|
+
await new StatusConfigModel(db, shelfConfig.secret).remove(project.id, c.req.param("id") ?? "");
|
|
68
|
+
return hxRedirect(c, `/projects/${project.slug}/settings/status`);
|
|
69
|
+
}
|
|
70
|
+
//#endregion
|
|
71
|
+
export { registerStatusSettings };
|
|
72
|
+
|
|
73
|
+
//# sourceMappingURL=settings-status.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-status.mjs","names":[],"sources":["../../src/routers/settings-status.ts"],"sourcesContent":["import type { Context } from \"hono\";\nimport type { ShelfApp } from \"../index.tsx\";\nimport type { GitHostProvider } from \"../adapters/git-host/index.ts\";\nimport { StatusConfigModel } from \"../models/status-config.ts\";\nimport type { Project } from \"../schema/project.ts\";\nimport { getStore } from \"../store.ts\";\nimport { hxRedirect } from \"./htmx.ts\";\nimport { asString, findProject, renderSettingsPage } from \"./settings.handlers.ts\";\n\n/** Merge-gate status-config settings (provider configs + tokens, delete). */\nexport function registerStatusSettings(app: ShelfApp): void {\n app.get(\"/projects/:slug/settings/status\", async (c) =>\n c.html(await renderSettingsPage(c, \"status\")),\n );\n app.post(\"/projects/:slug/settings/status\", handleCreateStatus);\n app.post(\"/projects/:slug/settings/status/:id/delete\", handleDeleteStatus);\n}\n\nasync function unknownProviderResponse(c: Context): Promise<Response> {\n return c.html(\n (await renderSettingsPage(c, \"status\", { globalError: \"Unknown git provider\" })) ?? \"\",\n 400,\n );\n}\n\nasync function missingTokenResponse(c: Context): Promise<Response> {\n return c.html(\n (await renderSettingsPage(c, \"status\", { errors: { token: \"Token is required\" } })) ?? \"\",\n 400,\n );\n}\n\ninterface StatusFields {\n project: Project;\n provider: GitHostProvider;\n token: string;\n configRaw: string;\n}\n\nasync function readStatusFields(c: Context): Promise<StatusFields | Response> {\n const project = await findProject(c.req.param(\"slug\") ?? \"\");\n const form = await c.req.formData();\n const provider = getStore().gitHosts.find((p) => p.metadata.kind === asString(form.get(\"provider\")));\n if (!provider) {\n return await unknownProviderResponse(c);\n }\n const token = asString(form.get(\"token\"));\n if (!token) {\n return await missingTokenResponse(c);\n }\n return { project, provider, token, configRaw: asString(form.get(\"config\")) ?? \"{}\" };\n}\n\nasync function handleCreateStatus(c: Context): Promise<Response> {\n const fields = await readStatusFields(c);\n if (fields instanceof Response) {\n return fields;\n }\n return await createStatusFromForm(c, fields.project, fields.provider, fields.token, fields.configRaw);\n}\n\nasync function createStatusFromForm(\n c: Context,\n project: Project,\n provider: GitHostProvider,\n token: string,\n configRaw: string,\n): Promise<Response> {\n const config = parseJsonConfig(configRaw);\n if (config === undefined) {\n return c.html(\n (await renderSettingsPage(c, \"status\", {\n errors: { config: \"Config must be valid JSON\" },\n })) ?? \"\",\n 400,\n );\n }\n const parsed = provider.metadata.schema.safeParse(config);\n if (!parsed.success) {\n return c.html(\n (await renderSettingsPage(c, \"status\", { errors: { config: parsed.error.message } })) ?? \"\",\n 400,\n );\n }\n return await persistStatusConfig(c, project, provider, token, parsed.data);\n}\n\nasync function persistStatusConfig(\n c: Context,\n project: Project,\n provider: GitHostProvider,\n token: string,\n config: unknown,\n): Promise<Response> {\n const { db, config: shelfConfig } = getStore();\n await new StatusConfigModel(db, shelfConfig.secret).create(project.id, {\n provider: provider.metadata.kind,\n config,\n token,\n });\n return hxRedirect(c, `/projects/${project.slug}/settings/status`);\n}\n\n/** Parse the provider config JSON, or undefined when invalid. */\nfunction parseJsonConfig(raw: string): unknown {\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n return undefined;\n }\n}\n\nasync function handleDeleteStatus(c: Context): Promise<Response> {\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"],"mappings":";;;;;;;;;AAUA,SAAgB,uBAAuB,KAAqB;CAC1D,IAAI,IAAI,mCAAmC,OAAO,MAChD,EAAE,KAAK,MAAM,mBAAmB,GAAG,QAAQ,CAAC,CAC9C;CACA,IAAI,KAAK,mCAAmC,kBAAkB;CAC9D,IAAI,KAAK,8CAA8C,kBAAkB;AAC3E;AAEA,eAAe,wBAAwB,GAA+B;CACpE,OAAO,EAAE,KACN,MAAM,mBAAmB,GAAG,UAAU,EAAE,aAAa,uBAAuB,CAAC,KAAM,IACpF,GACF;AACF;AAEA,eAAe,qBAAqB,GAA+B;CACjE,OAAO,EAAE,KACN,MAAM,mBAAmB,GAAG,UAAU,EAAE,QAAQ,EAAE,OAAO,oBAAoB,EAAE,CAAC,KAAM,IACvF,GACF;AACF;AASA,eAAe,iBAAiB,GAA8C;CAC5E,MAAM,UAAU,MAAM,YAAY,EAAE,IAAI,MAAM,MAAM,KAAK,EAAE;CAC3D,MAAM,OAAO,MAAM,EAAE,IAAI,SAAS;CAClC,MAAM,WAAW,SAAS,CAAC,CAAC,SAAS,MAAM,MAAM,EAAE,SAAS,SAAS,SAAS,KAAK,IAAI,UAAU,CAAC,CAAC;CACnG,IAAI,CAAC,UACH,OAAO,MAAM,wBAAwB,CAAC;CAExC,MAAM,QAAQ,SAAS,KAAK,IAAI,OAAO,CAAC;CACxC,IAAI,CAAC,OACH,OAAO,MAAM,qBAAqB,CAAC;CAErC,OAAO;EAAE;EAAS;EAAU;EAAO,WAAW,SAAS,KAAK,IAAI,QAAQ,CAAC,KAAK;CAAK;AACrF;AAEA,eAAe,mBAAmB,GAA+B;CAC/D,MAAM,SAAS,MAAM,iBAAiB,CAAC;CACvC,IAAI,kBAAkB,UACpB,OAAO;CAET,OAAO,MAAM,qBAAqB,GAAG,OAAO,SAAS,OAAO,UAAU,OAAO,OAAO,OAAO,SAAS;AACtG;AAEA,eAAe,qBACb,GACA,SACA,UACA,OACA,WACmB;CACnB,MAAM,SAAS,gBAAgB,SAAS;CACxC,IAAI,WAAW,KAAA,GACb,OAAO,EAAE,KACN,MAAM,mBAAmB,GAAG,UAAU,EACrC,QAAQ,EAAE,QAAQ,4BAA4B,EAChD,CAAC,KAAM,IACP,GACF;CAEF,MAAM,SAAS,SAAS,SAAS,OAAO,UAAU,MAAM;CACxD,IAAI,CAAC,OAAO,SACV,OAAO,EAAE,KACN,MAAM,mBAAmB,GAAG,UAAU,EAAE,QAAQ,EAAE,QAAQ,OAAO,MAAM,QAAQ,EAAE,CAAC,KAAM,IACzF,GACF;CAEF,OAAO,MAAM,oBAAoB,GAAG,SAAS,UAAU,OAAO,OAAO,IAAI;AAC3E;AAEA,eAAe,oBACb,GACA,SACA,UACA,OACA,QACmB;CACnB,MAAM,EAAE,IAAI,QAAQ,gBAAgB,SAAS;CAC7C,MAAM,IAAI,kBAAkB,IAAI,YAAY,MAAM,CAAC,CAAC,OAAO,QAAQ,IAAI;EACrE,UAAU,SAAS,SAAS;EAC5B;EACA;CACF,CAAC;CACD,OAAO,WAAW,GAAG,aAAa,QAAQ,KAAK,iBAAiB;AAClE;;AAGA,SAAS,gBAAgB,KAAsB;CAC7C,IAAI;EACF,OAAO,KAAK,MAAM,GAAG;CACvB,QAAQ;EACN;CACF;AACF;AAEA,eAAe,mBAAmB,GAA+B;CAC/D,MAAM,UAAU,MAAM,YAAY,EAAE,IAAI,MAAM,MAAM,KAAK,EAAE;CAC3D,MAAM,EAAE,IAAI,QAAQ,gBAAgB,SAAS;CAC7C,MAAM,IAAI,kBAAkB,IAAI,YAAY,MAAM,CAAC,CAAC,OAAO,QAAQ,IAAI,EAAE,IAAI,MAAM,IAAI,KAAK,EAAE;CAC9F,OAAO,WAAW,GAAG,aAAa,QAAQ,KAAK,iBAAiB;AAClE"}
|