@storyshelf/core 0.1.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. package/README.md +50 -46
  2. package/dist/adapter/auth.d.mts +4 -4
  3. package/dist/adapter/auth.mjs.map +1 -1
  4. package/dist/{adapters → adapter}/capture-queue.d.mts +6 -4
  5. package/dist/adapter/capture-queue.mjs +4 -0
  6. package/dist/adapter/capture-runner.d.mts +7 -7
  7. package/dist/adapter/database.d.mts +20 -9
  8. package/dist/adapter/database.mjs +60 -1
  9. package/dist/adapter/database.mjs.map +1 -0
  10. package/dist/adapter/git-host/comments.d.mts +41 -0
  11. package/dist/adapter/git-host/comments.mjs +54 -0
  12. package/dist/adapter/git-host/comments.mjs.map +1 -0
  13. package/dist/adapter/git-host/helpers.d.mts +1 -0
  14. package/dist/adapter/git-host/helpers.mjs +1 -0
  15. package/dist/adapter/git-host/helpers.mjs.map +1 -1
  16. package/dist/adapter/git-host.d.mts +3 -7
  17. package/dist/adapter/init.d.mts +58 -0
  18. package/dist/adapter/init.mjs +93 -0
  19. package/dist/adapter/init.mjs.map +1 -0
  20. package/dist/adapter/metadata.d.mts +83 -0
  21. package/dist/adapter/metadata.mjs +4 -0
  22. package/dist/adapter/storage.d.mts +15 -4
  23. package/dist/adapters/webhook-events.mjs +20 -4
  24. package/dist/adapters/webhook-events.mjs.map +1 -1
  25. package/dist/capture/adapter.d.mts +7 -1
  26. package/dist/capture/adapter.mjs +3 -0
  27. package/dist/capture/adapter.mjs.map +1 -1
  28. package/dist/capture/dispatch.mjs +150 -0
  29. package/dist/capture/dispatch.mjs.map +1 -0
  30. package/dist/capture/orchestrator.d.mts +9 -0
  31. package/dist/capture/orchestrator.mjs +11 -44
  32. package/dist/capture/orchestrator.mjs.map +1 -1
  33. package/dist/capture/pipeline.d.mts +4 -1
  34. package/dist/capture/pipeline.mjs +6 -6
  35. package/dist/capture/pipeline.mjs.map +1 -1
  36. package/dist/capture/queue.d.mts +4 -1
  37. package/dist/capture/queue.mjs +3 -1
  38. package/dist/capture/queue.mjs.map +1 -1
  39. package/dist/capture/skip-checks.mjs +76 -0
  40. package/dist/capture/skip-checks.mjs.map +1 -0
  41. package/dist/capture/statics.mjs +124 -0
  42. package/dist/capture/statics.mjs.map +1 -0
  43. package/dist/capture/status-fanout.mjs +1 -0
  44. package/dist/capture/status-fanout.mjs.map +1 -1
  45. package/dist/capture/storybook.d.mts +1 -0
  46. package/dist/capture/storybook.mjs +1 -0
  47. package/dist/capture/storybook.mjs.map +1 -1
  48. package/dist/capture.d.mts +8 -0
  49. package/dist/capture.mjs +9 -0
  50. package/dist/config.d.mts +37 -22
  51. package/dist/config.mjs +28 -2
  52. package/dist/config.mjs.map +1 -1
  53. package/dist/ddl.d.mts +2 -1
  54. package/dist/ddl.mjs +2 -1
  55. package/dist/ddl.mjs.map +1 -1
  56. package/dist/diff/engine.d.mts +8 -0
  57. package/dist/diff/engine.mjs +8 -0
  58. package/dist/diff/engine.mjs.map +1 -1
  59. package/dist/diff/options.d.mts +3 -1
  60. package/dist/diff.d.mts +5 -0
  61. package/dist/diff.mjs +6 -0
  62. package/dist/index.d.mts +34 -24
  63. package/dist/index.mjs +211 -252
  64. package/dist/index.mjs.map +1 -1
  65. package/dist/logger.d.mts +3 -3
  66. package/dist/logger.mjs.map +1 -1
  67. package/dist/middleware/auth-gate.mjs +36 -0
  68. package/dist/middleware/auth-gate.mjs.map +1 -0
  69. package/dist/middleware/csrf.mjs +1 -0
  70. package/dist/middleware/csrf.mjs.map +1 -1
  71. package/dist/middleware/rate-limit.mjs +1 -0
  72. package/dist/middleware/rate-limit.mjs.map +1 -1
  73. package/dist/middleware/request-log.mjs +28 -0
  74. package/dist/middleware/request-log.mjs.map +1 -0
  75. package/dist/middleware/store-scope.mjs +29 -0
  76. package/dist/middleware/store-scope.mjs.map +1 -0
  77. package/dist/models/baseline.mjs +9 -5
  78. package/dist/models/baseline.mjs.map +1 -1
  79. package/dist/models/build.mjs +5 -1
  80. package/dist/models/build.mjs.map +1 -1
  81. package/dist/models/comment.mjs +4 -1
  82. package/dist/models/comment.mjs.map +1 -1
  83. package/dist/models/label.mjs +3 -1
  84. package/dist/models/label.mjs.map +1 -1
  85. package/dist/models/member.mjs +2 -1
  86. package/dist/models/member.mjs.map +1 -1
  87. package/dist/models/project.mjs +2 -1
  88. package/dist/models/project.mjs.map +1 -1
  89. package/dist/models/snapshot.mjs +2 -1
  90. package/dist/models/snapshot.mjs.map +1 -1
  91. package/dist/models/status-config.mjs +2 -1
  92. package/dist/models/status-config.mjs.map +1 -1
  93. package/dist/models/token.mjs +2 -1
  94. package/dist/models/token.mjs.map +1 -1
  95. package/dist/models/webhook.mjs +12 -3
  96. package/dist/models/webhook.mjs.map +1 -1
  97. package/dist/pages/build-detail.mjs +10 -4
  98. package/dist/pages/build-detail.mjs.map +1 -1
  99. package/dist/pages/build-diff-comments.mjs +113 -0
  100. package/dist/pages/build-diff-comments.mjs.map +1 -0
  101. package/dist/pages/build-diff-header.mjs +163 -0
  102. package/dist/pages/build-diff-header.mjs.map +1 -0
  103. package/dist/pages/build-diff-nav.mjs +66 -0
  104. package/dist/pages/build-diff-nav.mjs.map +1 -0
  105. package/dist/pages/build-diff-viewer.mjs +197 -0
  106. package/dist/pages/build-diff-viewer.mjs.map +1 -0
  107. package/dist/pages/build-diff.mjs +66 -413
  108. package/dist/pages/build-diff.mjs.map +1 -1
  109. package/dist/pages/compute-jobs.mjs +3 -1
  110. package/dist/pages/compute-jobs.mjs.map +1 -1
  111. package/dist/pages/label-detail.mjs +4 -3
  112. package/dist/pages/label-detail.mjs.map +1 -1
  113. package/dist/pages/login.mjs +1 -0
  114. package/dist/pages/login.mjs.map +1 -1
  115. package/dist/pages/project-builds.mjs +6 -3
  116. package/dist/pages/project-builds.mjs.map +1 -1
  117. package/dist/pages/project-create.mjs +1 -0
  118. package/dist/pages/project-create.mjs.map +1 -1
  119. package/dist/pages/project-settings.mjs +1 -0
  120. package/dist/pages/project-settings.mjs.map +1 -1
  121. package/dist/pages/projects.mjs +12 -6
  122. package/dist/pages/projects.mjs.map +1 -1
  123. package/dist/pages/root.mjs +7 -3
  124. package/dist/pages/root.mjs.map +1 -1
  125. package/dist/pages/settings-general.mjs +1 -0
  126. package/dist/pages/settings-general.mjs.map +1 -1
  127. package/dist/pages/settings-labels.mjs +2 -1
  128. package/dist/pages/settings-labels.mjs.map +1 -1
  129. package/dist/pages/settings-members.mjs +2 -1
  130. package/dist/pages/settings-members.mjs.map +1 -1
  131. package/dist/pages/settings-status.mjs +3 -1
  132. package/dist/pages/settings-status.mjs.map +1 -1
  133. package/dist/pages/settings-tests.mjs +4 -3
  134. package/dist/pages/settings-tests.mjs.map +1 -1
  135. package/dist/pages/settings-tokens.mjs +1 -0
  136. package/dist/pages/settings-tokens.mjs.map +1 -1
  137. package/dist/pages/settings-webhooks.mjs +1 -0
  138. package/dist/pages/settings-webhooks.mjs.map +1 -1
  139. package/dist/pages/storybook.mjs +38 -1
  140. package/dist/pages/storybook.mjs.map +1 -1
  141. package/dist/{utils/paths.d.mts → paths.d.mts} +5 -0
  142. package/dist/{utils/paths.mjs → paths.mjs} +5 -0
  143. package/dist/paths.mjs.map +1 -0
  144. package/dist/retention/purge.mjs +2 -1
  145. package/dist/retention/purge.mjs.map +1 -1
  146. package/dist/routers/admin.mjs +2 -1
  147. package/dist/routers/admin.mjs.map +1 -1
  148. package/dist/routers/assets.mjs +1 -0
  149. package/dist/routers/assets.mjs.map +1 -1
  150. package/dist/routers/auth.mjs +1 -0
  151. package/dist/routers/auth.mjs.map +1 -1
  152. package/dist/routers/builds.handlers.mjs +114 -19
  153. package/dist/routers/builds.handlers.mjs.map +1 -1
  154. package/dist/routers/builds.mjs +55 -34
  155. package/dist/routers/builds.mjs.map +1 -1
  156. package/dist/routers/comments.mjs +1 -0
  157. package/dist/routers/comments.mjs.map +1 -1
  158. package/dist/routers/health.mjs +137 -0
  159. package/dist/routers/health.mjs.map +1 -0
  160. package/dist/routers/helpers.mjs +8 -2
  161. package/dist/routers/helpers.mjs.map +1 -1
  162. package/dist/routers/htmx.mjs +2 -0
  163. package/dist/routers/htmx.mjs.map +1 -1
  164. package/dist/routers/labels.mjs +2 -1
  165. package/dist/routers/labels.mjs.map +1 -1
  166. package/dist/routers/media.mjs +3 -2
  167. package/dist/routers/media.mjs.map +1 -1
  168. package/dist/routers/members.mjs +1 -0
  169. package/dist/routers/members.mjs.map +1 -1
  170. package/dist/routers/projects.mjs +2 -1
  171. package/dist/routers/projects.mjs.map +1 -1
  172. package/dist/routers/schemas.mjs +25 -0
  173. package/dist/routers/schemas.mjs.map +1 -1
  174. package/dist/routers/settings-general.mjs +95 -0
  175. package/dist/routers/settings-general.mjs.map +1 -0
  176. package/dist/routers/settings-labels.mjs +49 -0
  177. package/dist/routers/settings-labels.mjs.map +1 -0
  178. package/dist/routers/settings-members.mjs +30 -0
  179. package/dist/routers/settings-members.mjs.map +1 -0
  180. package/dist/routers/settings-status.mjs +73 -0
  181. package/dist/routers/settings-status.mjs.map +1 -0
  182. package/dist/routers/settings-tokens.mjs +40 -0
  183. package/dist/routers/settings-tokens.mjs.map +1 -0
  184. package/dist/routers/settings-webhooks.mjs +63 -0
  185. package/dist/routers/settings-webhooks.mjs.map +1 -0
  186. package/dist/routers/settings.handlers.mjs +93 -0
  187. package/dist/routers/settings.handlers.mjs.map +1 -0
  188. package/dist/routers/settings.mjs +14 -246
  189. package/dist/routers/settings.mjs.map +1 -1
  190. package/dist/routers/snapshots.mjs +2 -1
  191. package/dist/routers/snapshots.mjs.map +1 -1
  192. package/dist/routers/status-configs.mjs +2 -1
  193. package/dist/routers/status-configs.mjs.map +1 -1
  194. package/dist/routers/storybook.mjs +12 -4
  195. package/dist/routers/storybook.mjs.map +1 -1
  196. package/dist/routers/tokens.mjs +2 -1
  197. package/dist/routers/tokens.mjs.map +1 -1
  198. package/dist/routers/ui.mjs +5 -4
  199. package/dist/routers/ui.mjs.map +1 -1
  200. package/dist/routers/webhooks.mjs +7 -6
  201. package/dist/routers/webhooks.mjs.map +1 -1
  202. package/dist/schema/baseline.d.mts +18 -0
  203. package/dist/schema/baseline.mjs +22 -0
  204. package/dist/schema/baseline.mjs.map +1 -0
  205. package/dist/schema/build.d.mts +26 -0
  206. package/dist/schema/build.mjs +29 -0
  207. package/dist/schema/build.mjs.map +1 -0
  208. package/dist/schema/comment.d.mts +19 -0
  209. package/dist/schema/comment.mjs +26 -0
  210. package/dist/schema/comment.mjs.map +1 -0
  211. package/dist/schema/label.d.mts +25 -0
  212. package/dist/schema/label.mjs +30 -0
  213. package/dist/schema/label.mjs.map +1 -0
  214. package/dist/schema/member.d.mts +15 -0
  215. package/dist/schema/member.mjs +19 -0
  216. package/dist/schema/member.mjs.map +1 -0
  217. package/dist/schema/project.d.mts +34 -0
  218. package/dist/schema/project.mjs +25 -0
  219. package/dist/schema/project.mjs.map +1 -0
  220. package/dist/schema/snapshot.d.mts +30 -0
  221. package/dist/schema/snapshot.mjs +34 -0
  222. package/dist/schema/snapshot.mjs.map +1 -0
  223. package/dist/schema/status-config.d.mts +16 -0
  224. package/dist/schema/status-config.mjs +20 -0
  225. package/dist/schema/status-config.mjs.map +1 -0
  226. package/dist/schema/token.d.mts +15 -0
  227. package/dist/schema/token.mjs +19 -0
  228. package/dist/schema/token.mjs.map +1 -0
  229. package/dist/schema/user.d.mts +17 -0
  230. package/dist/schema/user.mjs +19 -0
  231. package/dist/schema/user.mjs.map +1 -0
  232. package/dist/schema/webhook.d.mts +16 -0
  233. package/dist/schema/webhook.mjs +20 -0
  234. package/dist/schema/webhook.mjs.map +1 -0
  235. package/dist/schema.d.mts +52 -4433
  236. package/dist/schema.mjs +37 -139
  237. package/dist/schema.mjs.map +1 -1
  238. package/dist/store.mjs +2 -0
  239. package/dist/store.mjs.map +1 -1
  240. package/dist/types.d.mts +13 -2
  241. package/dist/types.mjs +8 -0
  242. package/dist/types.mjs.map +1 -1
  243. package/dist/ui/document.mjs +1 -0
  244. package/dist/ui/document.mjs.map +1 -1
  245. package/dist/ui/feedback.mjs +24 -0
  246. package/dist/ui/feedback.mjs.map +1 -0
  247. package/dist/ui/{components.mjs → forms.mjs} +6 -16
  248. package/dist/ui/forms.mjs.map +1 -0
  249. package/dist/ui/styles.mjs +1 -0
  250. package/dist/ui/styles.mjs.map +1 -1
  251. package/dist/ui/theme.mjs +2 -0
  252. package/dist/ui/theme.mjs.map +1 -1
  253. package/dist/urls.d.mts +8 -0
  254. package/dist/urls.mjs +7 -0
  255. package/dist/urls.mjs.map +1 -1
  256. package/dist/utils/hash.mjs +3 -0
  257. package/dist/utils/hash.mjs.map +1 -1
  258. package/dist/utils/ulid.mjs +2 -0
  259. package/dist/utils/ulid.mjs.map +1 -1
  260. package/package.json +47 -45
  261. package/dist/adapters/metadata.d.mts +0 -24
  262. package/dist/models/baseline.d.mts +0 -20
  263. package/dist/models/build.d.mts +0 -50
  264. package/dist/models/comment.d.mts +0 -20
  265. package/dist/models/label.d.mts +0 -49
  266. package/dist/models/member.d.mts +0 -27
  267. package/dist/models/project.d.mts +0 -42
  268. package/dist/models/snapshot.d.mts +0 -46
  269. package/dist/models/token.d.mts +0 -33
  270. package/dist/models/webhook.d.mts +0 -37
  271. package/dist/retention/purge.d.mts +0 -28
  272. package/dist/ui/components.mjs.map +0 -1
  273. package/dist/ui/document.d.mts +0 -8
  274. package/dist/utils/paths.mjs.map +0 -1
  275. package/dist/utils/ulid.d.mts +0 -8
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.mjs","names":[],"sources":["../../src/routers/health.ts"],"sourcesContent":["/**\n * Two-tier health: lightweight liveness (GET, no auth) and deep readiness\n * (POST, site-admin). Neither is gated by adapter init — liveness must\n * answer mid-migration so orchestrators don't kill the machine, and deep\n * reports the init settlement state instead of being masked by the gate.\n */\nimport type { AdapterInitResult, AdapterInitSources } from \"../adapters/init.ts\";\nimport type { AdapterHealth, AdapterLifecycle, AdapterMetadata } from \"../adapters/metadata.ts\";\nimport type { ShelfApp } from \"../index.tsx\";\nimport { requireSiteAdmin } from \"./helpers.ts\";\n\n/** Dependencies for the health routes (no adapter I/O on the GET path). */\nexport interface HealthDeps {\n sources: AdapterInitSources;\n getSettled: () => AdapterInitResult | null;\n bootTimeMs: number;\n version: string;\n}\n\n/** Liveness body: process-level only, safe for unauthenticated probes. */\nexport interface LivenessBody {\n status: \"ok\";\n uptimeSecs: number;\n version: string;\n}\n\ntype AdapterState = \"ok\" | \"starting\" | \"failed\";\n\n/** Per-adapter readiness entry (error text sanitized — no secrets). */\nexport interface AdapterReport {\n category: string;\n kind: string;\n name: string;\n state: AdapterState;\n latencyMs?: number;\n detail?: string;\n}\n\n/** Deep readiness body. */\nexport interface ReadinessBody {\n status: \"ok\" | \"starting\" | \"degraded\";\n adapters: AdapterReport[];\n}\n\ninterface ProbeTarget {\n readonly metadata: AdapterMetadata;\n readonly lifecycle?: AdapterLifecycle;\n}\n\nconst PROBE_TIMEOUT_MS = 2000;\nconst DETAIL_MAX_LENGTH = 500;\n\nfunction pushTarget(targets: ProbeTarget[], adapter: ProbeTarget | undefined): void {\n if (adapter) {\n targets.push(adapter);\n }\n}\n\nfunction targetsOf(sources: AdapterInitSources): ProbeTarget[] {\n const targets: ProbeTarget[] = [];\n pushTarget(targets, sources.database);\n pushTarget(targets, sources.storage);\n pushTarget(targets, sources.captureRunner);\n pushTarget(targets, sources.captureQueue);\n pushTarget(targets, sources.auth);\n for (const provider of sources.gitHosts ?? []) {\n pushTarget(targets, provider);\n }\n return targets;\n}\n\nfunction sanitize(detail: string): string {\n return detail.length > DETAIL_MAX_LENGTH ? detail.slice(0, DETAIL_MAX_LENGTH) : detail;\n}\n\nfunction messageOf(reason: unknown): string {\n const message = reason instanceof Error ? reason.message : String(reason);\n return sanitize(message);\n}\n\nasync function timeoutError(): Promise<never> {\n await new Promise<void>((resolve) => {\n setTimeout(resolve, PROBE_TIMEOUT_MS);\n });\n throw new Error(\"health probe timed out\");\n}\n\nfunction okReport(\n target: ProbeTarget,\n latencyMs: number,\n detail: string | undefined,\n): AdapterReport {\n const { category, kind, name } = target.metadata;\n return {\n category,\n kind,\n name,\n state: \"ok\",\n latencyMs,\n ...(detail === undefined ? {} : { detail }),\n };\n}\n\nfunction failedReport(target: ProbeTarget, latencyMs: number, detail: string): AdapterReport {\n const { category, kind, name } = target.metadata;\n return { category, kind, name, state: \"failed\", latencyMs, detail };\n}\n\nfunction detailOf(result: AdapterHealth, fallback: string): string {\n return result.detail === undefined ? fallback : sanitize(result.detail);\n}\n\nasync function raceProbe(\n target: ProbeTarget,\n probe: () => Promise<AdapterHealth>,\n started: number,\n): Promise<AdapterReport> {\n try {\n const result = await Promise.race([probe(), timeoutError()]);\n const latencyMs = Date.now() - started;\n if (!result.ok) {\n return failedReport(target, latencyMs, detailOf(result, \"probe reported failure\"));\n }\n return okReport(\n target,\n latencyMs,\n result.detail === undefined ? undefined : sanitize(result.detail),\n );\n } catch (error) {\n return failedReport(target, Date.now() - started, messageOf(error));\n }\n}\n\nasync function probeHealth(target: ProbeTarget): Promise<AdapterReport> {\n const probe = target.lifecycle?.health;\n if (!probe) {\n const { category, kind, name } = target.metadata;\n return { category, kind, name, state: \"ok\" };\n }\n return await raceProbe(target, probe, Date.now());\n}\n\nasync function probeTarget(\n target: ProbeTarget,\n initErrors: ReadonlyMap<string, string>,\n starting: boolean,\n): Promise<AdapterReport> {\n const { category, kind, name } = target.metadata;\n const initError = initErrors.get(`${category}/${kind}`);\n if (initError !== undefined) {\n return { category, kind, name, state: \"failed\", detail: sanitize(initError) };\n }\n if (starting) {\n return { category, kind, name, state: \"starting\" };\n }\n return await probeHealth(target);\n}\n\nfunction collectInitErrors(settled: AdapterInitResult | null): ReadonlyMap<string, string> {\n const initErrors = new Map<string, string>();\n if (settled) {\n for (const failure of settled.failures) {\n initErrors.set(`${failure.category}/${failure.kind}`, failure.error);\n }\n }\n return initErrors;\n}\n\nfunction overallStatus(reports: AdapterReport[]): ReadinessBody[\"status\"] {\n if (reports.some((report) => report.state === \"failed\")) {\n return \"degraded\";\n }\n if (reports.some((report) => report.state === \"starting\")) {\n return \"starting\";\n }\n return \"ok\";\n}\n\n/** Register `GET /api/v1/health` (open liveness) and `POST` (authed deep readiness). */\nexport function registerHealth(app: ShelfApp, deps: HealthDeps): void {\n app.get(\"/api/v1/health\", (c) => {\n const body: LivenessBody = {\n status: \"ok\",\n uptimeSecs: Math.floor((Date.now() - deps.bootTimeMs) / 1000),\n version: deps.version,\n };\n c.header(\"Cache-Control\", \"no-store\");\n return c.json(body);\n });\n\n app.post(\"/api/v1/health\", async (c) => {\n requireSiteAdmin();\n const settled = deps.getSettled();\n const initErrors = collectInitErrors(settled);\n const targets = targetsOf(deps.sources);\n const reports = await Promise.all(\n targets.map(async (target) => await probeTarget(target, initErrors, settled === null)),\n );\n const body: ReadinessBody = { status: overallStatus(reports), adapters: reports };\n c.header(\"Cache-Control\", \"no-store\");\n return c.json(body, body.status === \"ok\" ? 200 : 503);\n });\n}\n"],"mappings":"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"}
@@ -1,29 +1,34 @@
1
1
  import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
- import { projects } from "../schema.mjs";
4
+ import { projects } from "../schema/project.mjs";
5
5
  import { ProjectModel } from "../models/project.mjs";
6
+ import { sha256 } from "../utils/hash.mjs";
6
7
  import { getStore } from "../store.mjs";
7
8
  import { MemberModel } from "../models/member.mjs";
8
9
  import { TokenModel } from "../models/token.mjs";
9
- import { sha256 } from "../utils/hash.mjs";
10
10
  import { eq } from "drizzle-orm";
11
11
  import { HTTPException } from "hono/http-exception";
12
12
  //#region src/routers/helpers.ts
13
+ /** Throw a 401 Unauthorized error for unauthenticated requests. */
13
14
  function unauthorized() {
14
15
  throw new HTTPException(401, { message: "Unauthorized" });
15
16
  }
17
+ /** Throw a 403 Forbidden error for requests lacking the required role. */
16
18
  function forbidden() {
17
19
  throw new HTTPException(403, { message: "Forbidden" });
18
20
  }
21
+ /** Throw a 404 Not Found error with a custom message. */
19
22
  function notFound(message = "Not found") {
20
23
  throw new HTTPException(404, { message });
21
24
  }
25
+ /** Resolve the current session user's effective role on a project. */
22
26
  async function currentProjectRole(projectId) {
23
27
  const { db, user } = getStore();
24
28
  if (!user) return null;
25
29
  return await new MemberModel(db).effectiveRole(user.role, projectId, user.id);
26
30
  }
31
+ /** Look up a project by its URL slug, returning null when absent. */
27
32
  async function findProjectBySlug(slug) {
28
33
  const { db } = getStore();
29
34
  return (await db.list(projects, {
@@ -56,6 +61,7 @@ async function assertRole(projectId, ...minRoles) {
56
61
  const role = await currentProjectRole(projectId);
57
62
  if (!role || !minRoles.includes(role)) forbidden();
58
63
  }
64
+ /** Resolve a project by slug and enforce the caller's minimum role. */
59
65
  async function resolveAuthorizedProject(c, slug, ...minRoles) {
60
66
  const project = await resolveProjectByToken(c, slug);
61
67
  if (project) return project;
@@ -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\";\n\nimport { MemberModel } from \"../models/member.ts\";\nimport { ProjectModel } from \"../models/project.ts\";\nimport { TokenModel } from \"../models/token.ts\";\nimport { projects, type Project } from \"../schema.ts\";\nimport { getStore } from \"../store.ts\";\nimport type { ProjectRole } from \"../types.ts\";\nimport { sha256 } from \"../utils/hash.ts\";\n\nexport function json(c: Context, data: unknown, status: ContentfulStatusCode = 200): Response {\n return c.json(data, status);\n}\n\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\nexport function unauthorized(): never {\n throw new HTTPException(401, { message: \"Unauthorized\" });\n}\n\nexport function forbidden(): never {\n throw new HTTPException(403, { message: \"Forbidden\" });\n}\n\nexport function notFound(message = \"Not found\"): never {\n throw new HTTPException(404, { message });\n}\n\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\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\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\nexport async function resolveProject(c: Context, slug: string): Promise<Project> {\n const project = await resolveProjectByToken(c, slug);\n if (project) {return project;}\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\nexport async function resolveAuthorizedProject(c: Context, slug: string, ...minRoles: ProjectRole[]): Promise<Project> {\n const project = await resolveProjectByToken(c, slug);\n if (project) {return project;}\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(projectId: string, ...minRoles: ProjectRole[]): Promise<void> {\n await assertRole(projectId, ...minRoles);\n}\n"],"mappings":";;;;;;;;;;;;AA0BA,SAAgB,eAAsB;CACpC,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,eAAe,CAAC;AAC1D;AAEA,SAAgB,YAAmB;CACjC,MAAM,IAAI,cAAc,KAAK,EAAE,SAAS,YAAY,CAAC;AACvD;AAEA,SAAgB,SAAS,UAAU,aAAoB;CACrD,MAAM,IAAI,cAAc,KAAK,EAAE,QAAQ,CAAC;AAC1C;AAEA,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;AAgBA,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;;;;;;;;;AAoBA,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;AAEA,eAAsB,yBAAyB,GAAY,MAAc,GAAG,UAA2C;CACrH,MAAM,UAAU,MAAM,sBAAsB,GAAG,IAAI;CACnD,IAAI,SAAU,OAAO;CACrB,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
+ {"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"}
@@ -2,9 +2,11 @@ import "node:path";
2
2
  import "node:url";
3
3
  import.meta.url;
4
4
  //#region src/routers/htmx.ts
5
+ /** Check whether the request came from HTMX (HX-Request header). */
5
6
  function isHxRequest(c) {
6
7
  return c.req.header("HX-Request") === "true";
7
8
  }
9
+ /** Redirect HTMX requests via HX-Redirect and plain requests via 302. */
8
10
  function hxRedirect(c, url) {
9
11
  if (isHxRequest(c)) {
10
12
  c.header("HX-Redirect", url);
@@ -1 +1 @@
1
- {"version":3,"file":"htmx.mjs","names":[],"sources":["../../src/routers/htmx.ts"],"sourcesContent":["import type { Context } from \"hono\";\n\nexport function isHxRequest(c: Context): boolean {\n return c.req.header(\"HX-Request\") === \"true\";\n}\n\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":";;;;AAEA,SAAgB,YAAY,GAAqB;CAC/C,OAAO,EAAE,IAAI,OAAO,YAAY,MAAM;AACxC;AAEA,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"}
@@ -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";
@@ -73,6 +73,7 @@ const updateLabelRoute = createRoute({
73
73
  ...notFound
74
74
  }
75
75
  });
76
+ /** Register the project label-type list, create, update, and delete endpoints. */
76
77
  function registerLabels(app) {
77
78
  app.openapi(listLabelsRoute, async (c) => {
78
79
  const project = await resolveAuthorizedProject(c, c.req.valid("param").slug, ...VIEW_ROLES);
@@ -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\";\n\nimport { LabelModel } from \"../models/label.ts\";\nimport { getStore } from \"../store.ts\";\nimport type { ProjectRole } from \"../types.ts\";\nimport { resolveAuthorizedProject } from \"./helpers.ts\";\nimport { labelTypeCreateSchema, labelTypeSchema, labelTypeUpdateSchema, notFound, unauthorized } 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: { content: { \"application/json\": { schema: labelTypeSchema.array() } }, description: \"List label types\" },\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: { content: { \"application/json\": { schema: labelTypeSchema } }, description: \"Created label type\" },\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: { content: { \"application/json\": { schema: labelTypeSchema } }, description: \"Updated label type\" },\n ...notFound,\n },\n});\n\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, { message: error instanceof Error ? error.message : \"Cannot update label type\" });\n }\n });\n}"],"mappings":"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"}
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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"}
@@ -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",
@@ -20,6 +20,7 @@ function imageResponse(buffer) {
20
20
  "cache-control": "private, max-age=3600"
21
21
  } });
22
22
  }
23
+ /** Register the PNG image, diff, and baseline media endpoints for snapshots. */
23
24
  function registerMedia(app) {
24
25
  app.get("/api/v1/projects/:slug/builds/:buildId/snapshots/:snapshotId/image", async (c) => {
25
26
  const project = await resolveAuthorizedProject(c, c.req.param("slug"), ...VIEW_ROLES);
@@ -1 +1 @@
1
- {"version":3,"file":"media.mjs","names":[],"sources":["../../src/routers/media.ts"],"sourcesContent":["import type { ShelfApp } from \"../index.tsx\";\n\nimport { BaselineModel } from \"../models/baseline.ts\";\nimport { BuildModel } from \"../models/build.ts\";\nimport { SnapshotModel } from \"../models/snapshot.ts\";\nimport { getStore } from \"../store.ts\";\nimport type { Snapshot } from \"../schema.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), { headers: { \"content-type\": PNG, \"cache-control\": \"private, max-age=3600\" } });\n}\n\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(c.req.param(\"buildId\"), c.req.param(\"snapshotId\"), project.id);\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(c.req.param(\"buildId\"), c.req.param(\"snapshotId\"), project.id);\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(c.req.param(\"buildId\"), c.req.param(\"snapshotId\"), project.id);\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(project.id, snapshot.storyId, snapshot.viewportName, build.gitBranch, project.gitDefaultBranch);\n if (!baseline) {\n notFound(\"No baseline for this snapshot\");\n }\n return imageResponse(await baselines.read(baseline));\n });\n}\n\nasync function findSnapshot(buildId: string, snapshotId: string, projectId: string): 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}"],"mappings":";;;;;;;;;AAUA,MAAM,aAAqC;CAAC;CAAU;CAAa;CAAY;AAAO;AAEtF,MAAM,MAAM;AAEZ,SAAS,cAAc,QAA0B;CAC/C,OAAO,IAAI,SAAS,IAAI,WAAW,MAAM,GAAG,EAAE,SAAS;EAAE,gBAAgB;EAAK,iBAAiB;CAAwB,EAAE,CAAC;AAC5H;AAEA,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,aAAa,EAAE,IAAI,MAAM,SAAS,GAAG,EAAE,IAAI,MAAM,YAAY,GAAG,QAAQ,EAAE;EACjG,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,aAAa,EAAE,IAAI,MAAM,SAAS,GAAG,EAAE,IAAI,MAAM,YAAY,GAAG,QAAQ,EAAE;EACjG,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,aAAa,EAAE,IAAI,MAAM,SAAS,GAAG,EAAE,IAAI,MAAM,YAAY,GAAG,QAAQ,EAAE;EACjG,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,QAAQ,QAAQ,IAAI,SAAS,SAAS,SAAS,cAAc,MAAM,WAAW,QAAQ,gBAAgB;EACvI,IAAI,CAAC,UACH,SAAS,+BAA+B;EAE1C,OAAO,cAAc,MAAM,UAAU,KAAK,QAAQ,CAAC;CACrD,CAAC;AACH;AAEA,eAAe,aAAa,SAAiB,YAAoB,WAAsC;CACrG,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
+ {"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"}
@@ -72,6 +72,7 @@ const deleteMemberRoute = createRoute({
72
72
  ...notFound
73
73
  }
74
74
  });
75
+ /** Register the project member list, upsert, and removal endpoints. */
75
76
  function registerMembers(app) {
76
77
  app.openapi(listMembersRoute, async (c) => {
77
78
  const project = await resolveAuthorizedProject(c, c.req.valid("param").slug, ...VIEW_ROLES);
@@ -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\";\n\nimport { MemberModel } from \"../models/member.ts\";\nimport { getStore } from \"../store.ts\";\nimport type { ProjectRole } from \"../types.ts\";\nimport { resolveAuthorizedProject } from \"./helpers.ts\";\nimport { memberRoleSchema, memberSchema, memberSetSchema, notFound, unauthorized } 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: { content: { \"application/json\": { schema: memberSchema.array() } }, description: \"List project members\" },\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: { content: { \"application/json\": { schema: memberSchema } }, description: \"Added or updated member\" },\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: { content: { \"application/json\": { schema: memberSchema } }, description: \"Updated member role\" },\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\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(await new MemberModel(getStore().db).set(project.id, body.userId, body.role), 201);\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}"],"mappings":"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"}
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";
@@ -83,6 +83,7 @@ const deleteProjectRoute = createRoute({
83
83
  ...notFound
84
84
  }
85
85
  });
86
+ /** Register the project list, create, fetch, update, and delete endpoints. */
86
87
  function registerProjects(app) {
87
88
  app.openapi(listProjectsRoute, async (c) => {
88
89
  requireSessionUser();
@@ -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;AAED,SAAgB,iBAAiB,KAAqB;CACpD,IAAI,QAAQ,mBAAmB,OAAO,MAAM;EAC1C,mBAAmB;EACnB,MAAM,WAAW,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE;EAC/C,OAAO,EAAE,KAAK,MAAM,SAAS,KAAK,CAAC;CACrC,CAAC;CAED,IAAI,QAAQ,oBAAoB,OAAO,MAAM;EAC3C,iBAAiB;EACjB,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;EAE/B,MAAM,UAAU,MAAM,IADD,aAAa,SAAS,CAAC,CAAC,EAChB,CAAC,CAAC,OAAO,IAAI;EAC1C,MAAM,IAAI,WAAW,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,QAAQ,EAAE;EACtD,OAAO,EAAE,KAAK,SAAS,GAAG;CAC5B,CAAC;CAED,IAAI,QAAQ,iBAAiB,OAAO,MAAM;EACxC,MAAM,EAAE,SAAS,EAAE,IAAI,MAAM,OAAO;EACpC,MAAM,UAAU,MAAM,yBAAyB,GAAG,MAAM,GAAG,UAAU;EACrE,OAAO,EAAE,KAAK,OAAO;CACvB,CAAC;CAED,IAAI,QAAQ,oBAAoB,OAAO,MAAM;EAC3C,MAAM,EAAE,SAAS,EAAE,IAAI,MAAM,OAAO;EACpC,MAAM,UAAU,MAAM,yBAAyB,GAAG,MAAM,GAAG,WAAW;EACtE,MAAM,UAAU,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,QAAQ,IAAI,EAAE,IAAI,MAAM,MAAM,CAAC;EAC5F,OAAO,EAAE,KAAK,OAAO;CACvB,CAAC;CAED,IAAI,QAAQ,oBAAoB,OAAO,MAAM;EAC3C,iBAAiB;EACjB,MAAM,EAAE,SAAS,EAAE,IAAI,MAAM,OAAO;EACpC,MAAM,UAAU,MAAM,yBAAyB,GAAG,MAAM,GAAG,UAAU;EACrE,MAAM,IAAI,aAAa,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,QAAQ,EAAE;EACvD,OAAO,EAAE,KAAK,MAAM,GAAG;CACzB,CAAC;AACH"}
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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"}
@@ -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(),
@@ -20,6 +21,7 @@ const projectSchema = z.object({
20
21
  createdAt: z.string(),
21
22
  updatedAt: z.string()
22
23
  }).openapi("Project");
24
+ /** OpenAPI schema for creating a project. */
23
25
  const projectCreateSchema = z.object({
24
26
  name: z.string().min(1),
25
27
  gitRepository: z.string().optional(),
@@ -28,6 +30,7 @@ const projectCreateSchema = z.object({
28
30
  executePlay: z.boolean().optional(),
29
31
  playTimeoutMs: z.number().int().min(1e3).max(3e4).optional()
30
32
  }).openapi("ProjectCreateInput");
33
+ /** OpenAPI schema for patching a project. */
31
34
  const projectUpdateSchema = z.object({
32
35
  name: z.string().optional(),
33
36
  gitRepository: z.string().optional(),
@@ -39,6 +42,7 @@ const projectUpdateSchema = z.object({
39
42
  executePlay: z.boolean().optional(),
40
43
  playTimeoutMs: z.number().int().min(1e3).max(3e4).optional()
41
44
  }).openapi("ProjectUpdateInput");
45
+ /** OpenAPI schema for a build record. */
42
46
  const buildSchema = z.object({
43
47
  id: z.string(),
44
48
  projectId: z.string(),
@@ -57,6 +61,7 @@ const buildSchema = z.object({
57
61
  createdAt: z.string(),
58
62
  updatedAt: z.string()
59
63
  }).openapi("Build");
64
+ /** OpenAPI schema for a snapshot record. */
60
65
  const snapshotSchema = z.object({
61
66
  id: z.string(),
62
67
  projectId: z.string(),
@@ -79,6 +84,7 @@ const snapshotSchema = z.object({
79
84
  createdAt: z.string(),
80
85
  updatedAt: z.string()
81
86
  }).openapi("Snapshot");
87
+ /** OpenAPI schema for a build comment. */
82
88
  const commentSchema = z.object({
83
89
  id: z.string(),
84
90
  projectId: z.string(),
@@ -91,11 +97,13 @@ const commentSchema = z.object({
91
97
  createdAt: z.string(),
92
98
  updatedAt: z.string()
93
99
  }).openapi("Comment");
100
+ /** OpenAPI schema for creating a build comment. */
94
101
  const commentCreateSchema = z.object({
95
102
  body: z.string().min(1),
96
103
  snapshotId: z.string().optional(),
97
104
  parentId: z.string().optional()
98
105
  }).openapi("CommentCreateInput");
106
+ /** OpenAPI schema for a project label type. */
99
107
  const labelTypeSchema = z.object({
100
108
  id: z.string(),
101
109
  projectId: z.string(),
@@ -105,17 +113,20 @@ const labelTypeSchema = z.object({
105
113
  color: z.string().nullable(),
106
114
  createdAt: z.string()
107
115
  }).openapi("LabelType");
116
+ /** OpenAPI schema for creating a label type. */
108
117
  const labelTypeCreateSchema = z.object({
109
118
  key: z.string().min(1),
110
119
  name: z.string().min(1),
111
120
  linkTemplate: z.string().optional(),
112
121
  color: z.string().optional()
113
122
  }).openapi("LabelTypeCreateInput");
123
+ /** OpenAPI schema for patching a label type. */
114
124
  const labelTypeUpdateSchema = z.object({
115
125
  name: z.string().min(1).optional(),
116
126
  linkTemplate: z.string().nullable().optional(),
117
127
  color: z.string().nullable().optional()
118
128
  }).openapi("LabelTypeUpdateInput");
129
+ /** OpenAPI schema for a public API token (hash omitted). */
119
130
  const tokenPublicSchema = z.object({
120
131
  id: z.string(),
121
132
  projectId: z.string(),
@@ -123,11 +134,14 @@ const tokenPublicSchema = z.object({
123
134
  lastUsedAt: z.string().nullable(),
124
135
  createdAt: z.string()
125
136
  }).openapi("Token");
137
+ /** OpenAPI schema for creating an API token. */
126
138
  const tokenCreateSchema = z.object({ name: z.string().min(1) }).openapi("TokenCreateInput");
139
+ /** OpenAPI schema for a newly created token including its secret value. */
127
140
  const tokenCreatedSchema = z.object({
128
141
  name: z.string(),
129
142
  token: z.string()
130
143
  }).openapi("TokenCreated");
144
+ /** OpenAPI schema for a project member record. */
131
145
  const memberSchema = z.object({
132
146
  id: z.string(),
133
147
  projectId: z.string(),
@@ -135,26 +149,32 @@ const memberSchema = z.object({
135
149
  role: z.enum(PROJECT_ROLES),
136
150
  createdAt: z.string()
137
151
  }).openapi("ProjectMember");
152
+ /** OpenAPI schema for updating a member's role. */
138
153
  const memberRoleSchema = z.object({ role: z.enum(PROJECT_ROLES) }).openapi("MemberRoleInput");
154
+ /** OpenAPI schema for adding a member to a project. */
139
155
  const memberSetSchema = z.object({
140
156
  userId: z.string().min(1),
141
157
  role: z.enum(PROJECT_ROLES)
142
158
  }).openapi("MemberSetInput");
159
+ /** OpenAPI schema for a public webhook (secret omitted). */
143
160
  const webhookPublicSchema = z.object({
144
161
  id: z.string(),
145
162
  url: z.string(),
146
163
  events: z.array(z.string())
147
164
  }).openapi("Webhook");
165
+ /** OpenAPI schema for creating a webhook. */
148
166
  const webhookCreateSchema = z.object({
149
167
  url: z.string().url(),
150
168
  events: z.array(z.string().min(1)).optional()
151
169
  }).openapi("WebhookCreateInput");
170
+ /** OpenAPI schema for a newly created webhook including its signing secret. */
152
171
  const webhookCreatedSchema = z.object({
153
172
  id: z.string(),
154
173
  url: z.string(),
155
174
  events: z.array(z.string()),
156
175
  secret: z.string()
157
176
  }).openapi("WebhookCreated");
177
+ /** OpenAPI schema for a git status-check configuration. */
158
178
  const statusConfigSchema = z.object({
159
179
  id: z.string(),
160
180
  projectId: z.string(),
@@ -164,14 +184,19 @@ const statusConfigSchema = z.object({
164
184
  createdAt: z.string(),
165
185
  updatedAt: z.string()
166
186
  }).openapi("StatusConfig");
187
+ /** OpenAPI schema for creating a git status-check configuration. */
167
188
  const statusConfigCreateSchema = z.object({
168
189
  provider: z.string().min(1),
169
190
  config: z.record(z.string(), z.unknown()),
170
191
  token: z.string().min(1)
171
192
  }).openapi("StatusConfigCreateInput");
193
+ /** OpenAPI schema for a generic `{ ok: true }` acknowledgement. */
172
194
  const okSchema = z.object({ ok: z.boolean() }).openapi("Ok");
195
+ /** OpenAPI schema for an error response message. */
173
196
  const errorSchema = z.object({ message: z.string() }).openapi("Error");
197
+ /** OpenAPI schema for the retention purge result. */
174
198
  const purgeSchema = z.object({ removedBuilds: z.number() }).openapi("PurgeResult");
199
+ /** OpenAPI schema for the retention purge input. */
175
200
  const purgeInputSchema = z.object({ ttlDays: z.number().optional() }).openapi("PurgeInput");
176
201
  /** Shared response spec for a 400 Bad Request. */
177
202
  const badRequest = { 400: {
@@ -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 executePlay: z.boolean(),\n playTimeoutMs: z.number(),\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 executePlay: z.boolean().optional(),\n playTimeoutMs: z.number().int().min(1000).max(30_000).optional(),\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 executePlay: z.boolean().optional(),\n playTimeoutMs: z.number().int().min(1000).max(30_000).optional(),\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 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\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 // 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\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":";;;;;;AAIA,MAAM,sBAAsB,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;AAElF,MAAa,gBAAgB,EAAE,OAAO;CACpC,IAAI,EAAE,OAAO;CACb,MAAM,EAAE,OAAO;CACf,MAAM,EAAE,OAAO;CACf,eAAe,EAAE,OAAO,CAAC,CAAC,SAAS;CACnC,kBAAkB,EAAE,OAAO;CAC3B,gBAAgB,EAAE,OAAO;CACzB,cAAc,EAAE,OAAO;CACvB,mBAAmB,EAAE,OAAO,CAAC,CAAC,SAAS;CACvC,eAAe,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;CAC9C,aAAa,EAAE,QAAQ;CACvB,eAAe,EAAE,OAAO;CACxB,WAAW,EAAE,OAAO;CACpB,WAAW,EAAE,OAAO;AACtB,CAAC,CAAC,CAAC,QAAQ,SAAS;AAEpB,MAAa,sBAAsB,EAAE,OAAO;CAC1C,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACtB,eAAe,EAAE,OAAO,CAAC,CAAC,SAAS;CACnC,kBAAkB,EAAE,OAAO,CAAC,CAAC,SAAS;CACtC,eAAe;CACf,aAAa,EAAE,QAAQ,CAAC,CAAC,SAAS;CAClC,eAAe,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,GAAI,CAAC,CAAC,IAAI,GAAM,CAAC,CAAC,SAAS;AACjE,CAAC,CAAC,CAAC,QAAQ,oBAAoB;AAE/B,MAAa,sBAAsB,EAAE,OAAO;CAC1C,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS;CAC1B,eAAe,EAAE,OAAO,CAAC,CAAC,SAAS;CACnC,kBAAkB,EAAE,OAAO,CAAC,CAAC,SAAS;CACtC,gBAAgB,EAAE,OAAO,CAAC,CAAC,SAAS;CACpC,cAAc,EAAE,OAAO,CAAC,CAAC,SAAS;CAClC,mBAAmB,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;CAClD,eAAe;CACf,aAAa,EAAE,QAAQ,CAAC,CAAC,SAAS;CAClC,eAAe,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,GAAI,CAAC,CAAC,IAAI,GAAM,CAAC,CAAC,SAAS;AACjE,CAAC,CAAC,CAAC,QAAQ,oBAAoB;AAE/B,MAAa,cAAc,EAAE,OAAO;CAClC,IAAI,EAAE,OAAO;CACb,WAAW,EAAE,OAAO;CACpB,QAAQ,EAAE,OAAO;CACjB,WAAW,EAAE,OAAO;CACpB,WAAW,EAAE,QAAQ;CACrB,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;CACjC,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,QAAQ,EAAE,QAAQ;CAClB,QAAQ,EAAE,KAAK,cAAc;CAC7B,eAAe,EAAE,OAAO;CACxB,cAAc,EAAE,OAAO;CACvB,eAAe,EAAE,OAAO;CACxB,eAAe,EAAE,OAAO;CACxB,WAAW,EAAE,OAAO;CACpB,WAAW,EAAE,OAAO;AACtB,CAAC,CAAC,CAAC,QAAQ,OAAO;AAElB,MAAa,iBAAiB,EAAE,OAAO;CACrC,IAAI,EAAE,OAAO;CACb,WAAW,EAAE,OAAO;CACpB,SAAS,EAAE,OAAO;CAClB,SAAS,EAAE,OAAO;CAClB,WAAW,EAAE,OAAO;CACpB,YAAY,EAAE,OAAO;CACrB,iBAAiB,EAAE,OAAO,CAAC,CAAC,SAAS;CACrC,cAAc,EAAE,OAAO;CACvB,eAAe,EAAE,OAAO;CACxB,gBAAgB,EAAE,OAAO;CACzB,gBAAgB,EAAE,OAAO;CACzB,UAAU,EAAE,OAAO,CAAC,CAAC,SAAS;CAC9B,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;CAC/B,YAAY,EAAE,QAAQ,CAAC,CAAC,SAAS;CACjC,QAAQ,EAAE,KAAK,iBAAiB;CAChC,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,WAAW,EAAE,OAAO;CACpB,WAAW,EAAE,OAAO;AACtB,CAAC,CAAC,CAAC,QAAQ,UAAU;AAErB,MAAa,gBAAgB,EAAE,OAAO;CACpC,IAAI,EAAE,OAAO;CACb,WAAW,EAAE,OAAO;CACpB,SAAS,EAAE,OAAO;CAClB,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,QAAQ,EAAE,OAAO;CACjB,MAAM,EAAE,OAAO;CACf,UAAU,EAAE,OAAO,CAAC,CAAC,SAAS;CAC9B,UAAU,EAAE,QAAQ;CACpB,WAAW,EAAE,OAAO;CACpB,WAAW,EAAE,OAAO;AACtB,CAAC,CAAC,CAAC,QAAQ,SAAS;AAEpB,MAAa,sBAAsB,EAAE,OAAO;CAC1C,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACtB,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,UAAU,EAAE,OAAO,CAAC,CAAC,SAAS;AAChC,CAAC,CAAC,CAAC,QAAQ,oBAAoB;AAE/B,MAAa,kBAAkB,EAAE,OAAO;CACtC,IAAI,EAAE,OAAO;CACb,WAAW,EAAE,OAAO;CACpB,KAAK,EAAE,OAAO;CACd,MAAM,EAAE,OAAO;CACf,cAAc,EAAE,OAAO,CAAC,CAAC,SAAS;CAClC,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;CAC3B,WAAW,EAAE,OAAO;AACtB,CAAC,CAAC,CAAC,QAAQ,WAAW;AAEtB,MAAa,wBAAwB,EAAE,OAAO;CAC5C,KAAK,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACrB,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACtB,cAAc,EAAE,OAAO,CAAC,CAAC,SAAS;CAClC,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;AAC7B,CAAC,CAAC,CAAC,QAAQ,sBAAsB;AAEjC,MAAa,wBAAwB,EAAE,OAAO;CAC5C,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;CACjC,cAAc,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;CAC7C,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;AACxC,CAAC,CAAC,CAAC,QAAQ,sBAAsB;AAEjC,MAAa,oBAAoB,EAAE,OAAO;CACxC,IAAI,EAAE,OAAO;CACb,WAAW,EAAE,OAAO;CACpB,MAAM,EAAE,OAAO;CACf,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,WAAW,EAAE,OAAO;AACtB,CAAC,CAAC,CAAC,QAAQ,OAAO;AAElB,MAAa,oBAAoB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,kBAAkB;AAEjG,MAAa,qBAAqB,EAAE,OAAO;CACzC,MAAM,EAAE,OAAO;CACf,OAAO,EAAE,OAAO;AAClB,CAAC,CAAC,CAAC,QAAQ,cAAc;AAEzB,MAAa,eAAe,EAAE,OAAO;CACnC,IAAI,EAAE,OAAO;CACb,WAAW,EAAE,OAAO;CACpB,QAAQ,EAAE,OAAO;CACjB,MAAM,EAAE,KAAK,aAAa;CAC1B,WAAW,EAAE,OAAO;AACtB,CAAC,CAAC,CAAC,QAAQ,eAAe;AAE1B,MAAa,mBAAmB,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,CAAC,CAAC,CAAC,QAAQ,iBAAiB;AAEnG,MAAa,kBAAkB,EAAE,OAAO;CACtC,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACxB,MAAM,EAAE,KAAK,aAAa;AAC5B,CAAC,CAAC,CAAC,QAAQ,gBAAgB;AAE3B,MAAa,sBAAsB,EAAE,OAAO;CAC1C,IAAI,EAAE,OAAO;CACb,KAAK,EAAE,OAAO;CACd,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC;AAC5B,CAAC,CAAC,CAAC,QAAQ,SAAS;AAEpB,MAAa,sBAAsB,EAAE,OAAO;CAE1C,KAAK,EAAE,OAAO,CAAC,CAAC,IAAI;CACpB,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;AAC9C,CAAC,CAAC,CAAC,QAAQ,oBAAoB;AAE/B,MAAa,uBAAuB,EAAE,OAAO;CAC3C,IAAI,EAAE,OAAO;CACb,KAAK,EAAE,OAAO;CACd,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC;CAC1B,QAAQ,EAAE,OAAO;AACnB,CAAC,CAAC,CAAC,QAAQ,gBAAgB;AAE3B,MAAa,qBAAqB,EAAE,OAAO;CACzC,IAAI,EAAE,OAAO;CACb,WAAW,EAAE,OAAO;CACpB,UAAU,EAAE,OAAO;CACnB,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;CACxC,UAAU,EAAE,QAAQ;CACpB,WAAW,EAAE,OAAO;CACpB,WAAW,EAAE,OAAO;AACtB,CAAC,CAAC,CAAC,QAAQ,cAAc;AAEzB,MAAa,2BAA2B,EAAE,OAAO;CAC/C,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CAC1B,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;CACxC,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;AACzB,CAAC,CAAC,CAAC,QAAQ,yBAAyB;AAEpC,MAAa,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI;AAElE,MAAa,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,QAAQ,OAAO;AAE5E,MAAa,cAAc,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,QAAQ,aAAa;AAExF,MAAa,mBAAmB,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,QAAQ,YAAY;;AAGjG,MAAa,aAAa,EACxB,KAAK;CAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,YAAY,EAAE;CAAG,aAAa;AAAc,EAC9F;;AAGA,MAAa,eAAe,EAC1B,KAAK;CAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,YAAY,EAAE;CAAG,aAAa;AAAe,EAC/F;;AAGA,MAAa,YAAY,EACvB,KAAK;CAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,YAAY,EAAE;CAAG,aAAa;AAAY,EAC5F;;AAGA,MAAa,WAAW,EACtB,KAAK;CAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,YAAY,EAAE;CAAG,aAAa;AAAY,EAC5F"}
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":";;;;;;AAGA,MAAM,sBAAsB,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;;AAGlF,MAAa,gBAAgB,EAC1B,OAAO;CACN,IAAI,EAAE,OAAO;CACb,MAAM,EAAE,OAAO;CACf,MAAM,EAAE,OAAO;CACf,eAAe,EAAE,OAAO,CAAC,CAAC,SAAS;CACnC,kBAAkB,EAAE,OAAO;CAC3B,gBAAgB,EAAE,OAAO;CACzB,cAAc,EAAE,OAAO;CACvB,mBAAmB,EAAE,OAAO,CAAC,CAAC,SAAS;CACvC,eAAe,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;CAC9C,aAAa,EAAE,QAAQ;CACvB,eAAe,EAAE,OAAO;CACxB,WAAW,EAAE,OAAO;CACpB,WAAW,EAAE,OAAO;AACtB,CAAC,CAAC,CACD,QAAQ,SAAS;;AAGpB,MAAa,sBAAsB,EAChC,OAAO;CACN,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACtB,eAAe,EAAE,OAAO,CAAC,CAAC,SAAS;CACnC,kBAAkB,EAAE,OAAO,CAAC,CAAC,SAAS;CACtC,eAAe;CACf,aAAa,EAAE,QAAQ,CAAC,CAAC,SAAS;CAClC,eAAe,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,GAAI,CAAC,CAAC,IAAI,GAAM,CAAC,CAAC,SAAS;AACjE,CAAC,CAAC,CACD,QAAQ,oBAAoB;;AAG/B,MAAa,sBAAsB,EAChC,OAAO;CACN,MAAM,EAAE,OAAO,CAAC,CAAC,SAAS;CAC1B,eAAe,EAAE,OAAO,CAAC,CAAC,SAAS;CACnC,kBAAkB,EAAE,OAAO,CAAC,CAAC,SAAS;CACtC,gBAAgB,EAAE,OAAO,CAAC,CAAC,SAAS;CACpC,cAAc,EAAE,OAAO,CAAC,CAAC,SAAS;CAClC,mBAAmB,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;CAClD,eAAe;CACf,aAAa,EAAE,QAAQ,CAAC,CAAC,SAAS;CAClC,eAAe,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,GAAI,CAAC,CAAC,IAAI,GAAM,CAAC,CAAC,SAAS;AACjE,CAAC,CAAC,CACD,QAAQ,oBAAoB;;AAG/B,MAAa,cAAc,EACxB,OAAO;CACN,IAAI,EAAE,OAAO;CACb,WAAW,EAAE,OAAO;CACpB,QAAQ,EAAE,OAAO;CACjB,WAAW,EAAE,OAAO;CACpB,WAAW,EAAE,QAAQ;CACrB,aAAa,EAAE,OAAO,CAAC,CAAC,SAAS;CACjC,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS;CAC7B,QAAQ,EAAE,QAAQ;CAClB,QAAQ,EAAE,KAAK,cAAc;CAC7B,eAAe,EAAE,OAAO;CACxB,cAAc,EAAE,OAAO;CACvB,eAAe,EAAE,OAAO;CACxB,eAAe,EAAE,OAAO;CACxB,WAAW,EAAE,OAAO;CACpB,WAAW,EAAE,OAAO;AACtB,CAAC,CAAC,CACD,QAAQ,OAAO;;AAGlB,MAAa,iBAAiB,EAC3B,OAAO;CACN,IAAI,EAAE,OAAO;CACb,WAAW,EAAE,OAAO;CACpB,SAAS,EAAE,OAAO;CAClB,SAAS,EAAE,OAAO;CAClB,WAAW,EAAE,OAAO;CACpB,YAAY,EAAE,OAAO;CACrB,iBAAiB,EAAE,OAAO,CAAC,CAAC,SAAS;CACrC,cAAc,EAAE,OAAO;CACvB,eAAe,EAAE,OAAO;CACxB,gBAAgB,EAAE,OAAO;CACzB,gBAAgB,EAAE,OAAO;CACzB,UAAU,EAAE,OAAO,CAAC,CAAC,SAAS;CAC9B,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,WAAW,EAAE,OAAO,CAAC,CAAC,SAAS;CAC/B,YAAY,EAAE,QAAQ,CAAC,CAAC,SAAS;CACjC,QAAQ,EAAE,KAAK,iBAAiB;CAChC,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,WAAW,EAAE,OAAO;CACpB,WAAW,EAAE,OAAO;AACtB,CAAC,CAAC,CACD,QAAQ,UAAU;;AAGrB,MAAa,gBAAgB,EAC1B,OAAO;CACN,IAAI,EAAE,OAAO;CACb,WAAW,EAAE,OAAO;CACpB,SAAS,EAAE,OAAO;CAClB,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,QAAQ,EAAE,OAAO;CACjB,MAAM,EAAE,OAAO;CACf,UAAU,EAAE,OAAO,CAAC,CAAC,SAAS;CAC9B,UAAU,EAAE,QAAQ;CACpB,WAAW,EAAE,OAAO;CACpB,WAAW,EAAE,OAAO;AACtB,CAAC,CAAC,CACD,QAAQ,SAAS;;AAGpB,MAAa,sBAAsB,EAChC,OAAO;CACN,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACtB,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,UAAU,EAAE,OAAO,CAAC,CAAC,SAAS;AAChC,CAAC,CAAC,CACD,QAAQ,oBAAoB;;AAG/B,MAAa,kBAAkB,EAC5B,OAAO;CACN,IAAI,EAAE,OAAO;CACb,WAAW,EAAE,OAAO;CACpB,KAAK,EAAE,OAAO;CACd,MAAM,EAAE,OAAO;CACf,cAAc,EAAE,OAAO,CAAC,CAAC,SAAS;CAClC,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;CAC3B,WAAW,EAAE,OAAO;AACtB,CAAC,CAAC,CACD,QAAQ,WAAW;;AAGtB,MAAa,wBAAwB,EAClC,OAAO;CACN,KAAK,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACrB,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACtB,cAAc,EAAE,OAAO,CAAC,CAAC,SAAS;CAClC,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS;AAC7B,CAAC,CAAC,CACD,QAAQ,sBAAsB;;AAGjC,MAAa,wBAAwB,EAClC,OAAO;CACN,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;CACjC,cAAc,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;CAC7C,OAAO,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS;AACxC,CAAC,CAAC,CACD,QAAQ,sBAAsB;;AAGjC,MAAa,oBAAoB,EAC9B,OAAO;CACN,IAAI,EAAE,OAAO;CACb,WAAW,EAAE,OAAO;CACpB,MAAM,EAAE,OAAO;CACf,YAAY,EAAE,OAAO,CAAC,CAAC,SAAS;CAChC,WAAW,EAAE,OAAO;AACtB,CAAC,CAAC,CACD,QAAQ,OAAO;;AAGlB,MAAa,oBAAoB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,kBAAkB;;AAGjG,MAAa,qBAAqB,EAC/B,OAAO;CACN,MAAM,EAAE,OAAO;CACf,OAAO,EAAE,OAAO;AAClB,CAAC,CAAC,CACD,QAAQ,cAAc;;AAGzB,MAAa,eAAe,EACzB,OAAO;CACN,IAAI,EAAE,OAAO;CACb,WAAW,EAAE,OAAO;CACpB,QAAQ,EAAE,OAAO;CACjB,MAAM,EAAE,KAAK,aAAa;CAC1B,WAAW,EAAE,OAAO;AACtB,CAAC,CAAC,CACD,QAAQ,eAAe;;AAG1B,MAAa,mBAAmB,EAC7B,OAAO,EAAE,MAAM,EAAE,KAAK,aAAa,EAAE,CAAC,CAAC,CACvC,QAAQ,iBAAiB;;AAG5B,MAAa,kBAAkB,EAC5B,OAAO;CACN,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CACxB,MAAM,EAAE,KAAK,aAAa;AAC5B,CAAC,CAAC,CACD,QAAQ,gBAAgB;;AAG3B,MAAa,sBAAsB,EAChC,OAAO;CACN,IAAI,EAAE,OAAO;CACb,KAAK,EAAE,OAAO;CACd,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC;AAC5B,CAAC,CAAC,CACD,QAAQ,SAAS;;AAGpB,MAAa,sBAAsB,EAChC,OAAO;CAEN,KAAK,EAAE,OAAO,CAAC,CAAC,IAAI;CACpB,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;AAC9C,CAAC,CAAC,CACD,QAAQ,oBAAoB;;AAG/B,MAAa,uBAAuB,EACjC,OAAO;CACN,IAAI,EAAE,OAAO;CACb,KAAK,EAAE,OAAO;CACd,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC;CAC1B,QAAQ,EAAE,OAAO;AACnB,CAAC,CAAC,CACD,QAAQ,gBAAgB;;AAG3B,MAAa,qBAAqB,EAC/B,OAAO;CACN,IAAI,EAAE,OAAO;CACb,WAAW,EAAE,OAAO;CACpB,UAAU,EAAE,OAAO;CACnB,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;CACxC,UAAU,EAAE,QAAQ;CACpB,WAAW,EAAE,OAAO;CACpB,WAAW,EAAE,OAAO;AACtB,CAAC,CAAC,CACD,QAAQ,cAAc;;AAGzB,MAAa,2BAA2B,EACrC,OAAO;CACN,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;CAC1B,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;CACxC,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;AACzB,CAAC,CAAC,CACD,QAAQ,yBAAyB;;AAGpC,MAAa,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI;;AAGlE,MAAa,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,QAAQ,OAAO;;AAG5E,MAAa,cAAc,EAAE,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,QAAQ,aAAa;;AAGxF,MAAa,mBAAmB,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,QAAQ,YAAY;;AAGjG,MAAa,aAAa,EACxB,KAAK;CAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,YAAY,EAAE;CAAG,aAAa;AAAc,EAC9F;;AAGA,MAAa,eAAe,EAC1B,KAAK;CAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,YAAY,EAAE;CAAG,aAAa;AAAe,EAC/F;;AAGA,MAAa,YAAY,EACvB,KAAK;CAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,YAAY,EAAE;CAAG,aAAa;AAAY,EAC5F;;AAGA,MAAa,WAAW,EACtB,KAAK;CAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,YAAY,EAAE;CAAG,aAAa;AAAY,EAC5F"}