@setzkasten-cms/astro-admin 1.4.0 → 1.4.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 (153) hide show
  1. package/dist/api-routes/_auth-guard.d.ts +47 -0
  2. package/dist/api-routes/_auth-guard.js +18 -0
  3. package/dist/api-routes/_commit-trailers.d.ts +8 -0
  4. package/dist/api-routes/_commit-trailers.js +8 -0
  5. package/dist/api-routes/_feature-gate.d.ts +23 -0
  6. package/dist/api-routes/_feature-gate.js +7 -0
  7. package/dist/api-routes/_github-cache.d.ts +4 -0
  8. package/dist/api-routes/_github-cache.js +8 -0
  9. package/dist/api-routes/_github-token.d.ts +27 -0
  10. package/dist/api-routes/_github-token.js +8 -0
  11. package/dist/api-routes/_license-tier.d.ts +22 -0
  12. package/dist/api-routes/_license-tier.js +6 -0
  13. package/dist/api-routes/_pages-meta-store.d.ts +32 -0
  14. package/dist/api-routes/_pages-meta-store.js +9 -0
  15. package/dist/api-routes/_role-resolver.d.ts +15 -0
  16. package/dist/api-routes/_role-resolver.js +13 -0
  17. package/dist/api-routes/_session-cookie.d.ts +18 -0
  18. package/dist/api-routes/_session-cookie.js +6 -0
  19. package/dist/api-routes/_storage-config.d.ts +60 -0
  20. package/dist/api-routes/_storage-config.js +10 -0
  21. package/dist/api-routes/_vercel-origin.d.ts +16 -0
  22. package/dist/api-routes/_vercel-origin.js +6 -0
  23. package/dist/api-routes/_webhook-dispatcher.d.ts +13 -0
  24. package/dist/api-routes/_webhook-dispatcher.js +97 -0
  25. package/dist/api-routes/_webhook-signing.d.ts +11 -0
  26. package/dist/api-routes/_webhook-signing.js +6 -0
  27. package/dist/api-routes/_webhook-status-store.d.ts +19 -0
  28. package/dist/api-routes/_webhook-status-store.js +10 -0
  29. package/dist/api-routes/_website-resolver.d.ts +49 -0
  30. package/dist/api-routes/_website-resolver.js +14 -0
  31. package/dist/api-routes/_websites-store.d.ts +30 -0
  32. package/dist/api-routes/_websites-store.js +11 -0
  33. package/dist/api-routes/asset-proxy.d.ts +12 -0
  34. package/dist/api-routes/asset-proxy.js +67 -0
  35. package/dist/api-routes/auth-callback.d.ts +9 -0
  36. package/dist/api-routes/auth-callback.js +68 -0
  37. package/dist/api-routes/auth-login.d.ts +11 -0
  38. package/dist/api-routes/auth-login.js +27 -0
  39. package/dist/api-routes/auth-logout.d.ts +10 -0
  40. package/dist/api-routes/auth-logout.js +13 -0
  41. package/dist/api-routes/auth-session.d.ts +9 -0
  42. package/dist/api-routes/auth-session.js +31 -0
  43. package/dist/api-routes/auth-setzkasten-login.d.ts +18 -0
  44. package/dist/api-routes/auth-setzkasten-login.js +74 -0
  45. package/dist/api-routes/catalog-add.d.ts +14 -0
  46. package/dist/api-routes/catalog-add.js +153 -0
  47. package/dist/api-routes/catalog-export.d.ts +13 -0
  48. package/dist/api-routes/catalog-export.js +71 -0
  49. package/dist/api-routes/catalog-helpers.d.ts +41 -0
  50. package/dist/api-routes/catalog-helpers.js +11 -0
  51. package/dist/api-routes/catalog-list.d.ts +11 -0
  52. package/dist/api-routes/catalog-list.js +12 -0
  53. package/dist/api-routes/config.d.ts +12 -0
  54. package/dist/api-routes/config.js +43 -0
  55. package/dist/api-routes/deploy-hook.d.ts +14 -0
  56. package/dist/api-routes/deploy-hook.js +52 -0
  57. package/dist/api-routes/editors.d.ts +29 -0
  58. package/dist/api-routes/editors.js +18 -0
  59. package/dist/api-routes/github-proxy.d.ts +12 -0
  60. package/dist/api-routes/github-proxy.js +82 -0
  61. package/dist/api-routes/global-config.d.ts +20 -0
  62. package/dist/api-routes/global-config.js +19 -0
  63. package/dist/api-routes/history-rollback.d.ts +22 -0
  64. package/dist/api-routes/history-rollback.js +111 -0
  65. package/dist/api-routes/history-version.d.ts +11 -0
  66. package/dist/api-routes/history-version.js +57 -0
  67. package/dist/api-routes/history.d.ts +13 -0
  68. package/dist/api-routes/history.js +85 -0
  69. package/dist/api-routes/icons-local.d.ts +28 -0
  70. package/dist/api-routes/icons-local.js +115 -0
  71. package/dist/api-routes/init-add-section.d.ts +23 -0
  72. package/dist/api-routes/init-add-section.js +396 -0
  73. package/dist/api-routes/init-apply.d.ts +11 -0
  74. package/dist/api-routes/init-apply.js +266 -0
  75. package/dist/api-routes/init-migrate.d.ts +16 -0
  76. package/dist/api-routes/init-migrate.js +205 -0
  77. package/dist/api-routes/init-scan-page.d.ts +39 -0
  78. package/dist/api-routes/init-scan-page.js +260 -0
  79. package/dist/api-routes/init-scan.d.ts +11 -0
  80. package/dist/api-routes/init-scan.js +128 -0
  81. package/dist/api-routes/migrate-to-multi.d.ts +26 -0
  82. package/dist/api-routes/migrate-to-multi.js +188 -0
  83. package/dist/api-routes/pages.d.ts +39 -0
  84. package/dist/api-routes/pages.js +88 -0
  85. package/dist/api-routes/section-add.d.ts +18 -0
  86. package/dist/api-routes/section-add.js +173 -0
  87. package/dist/api-routes/section-commit-pending.d.ts +18 -0
  88. package/dist/api-routes/section-commit-pending.js +207 -0
  89. package/dist/api-routes/section-delete.d.ts +15 -0
  90. package/dist/api-routes/section-delete.js +149 -0
  91. package/dist/api-routes/section-duplicate.d.ts +15 -0
  92. package/dist/api-routes/section-duplicate.js +143 -0
  93. package/dist/api-routes/section-management.d.ts +41 -0
  94. package/dist/api-routes/section-management.js +14 -0
  95. package/dist/api-routes/section-prepare-copy.d.ts +25 -0
  96. package/dist/api-routes/section-prepare-copy.js +69 -0
  97. package/dist/api-routes/section-prepare.d.ts +18 -0
  98. package/dist/api-routes/section-prepare.js +104 -0
  99. package/dist/api-routes/setup-github-app-bounce.d.ts +13 -0
  100. package/dist/api-routes/setup-github-app-bounce.js +45 -0
  101. package/dist/api-routes/setup-github-app-branches.d.ts +14 -0
  102. package/dist/api-routes/setup-github-app-branches.js +58 -0
  103. package/dist/api-routes/setup-github-app-callback.d.ts +15 -0
  104. package/dist/api-routes/setup-github-app-callback.js +45 -0
  105. package/dist/api-routes/setup-github-app-installed.d.ts +15 -0
  106. package/dist/api-routes/setup-github-app-installed.js +33 -0
  107. package/dist/api-routes/setup-github-app-repos.d.ts +17 -0
  108. package/dist/api-routes/setup-github-app-repos.js +41 -0
  109. package/dist/api-routes/setup-github-app.d.ts +15 -0
  110. package/dist/api-routes/setup-github-app.js +41 -0
  111. package/dist/api-routes/updater-check.d.ts +10 -0
  112. package/dist/api-routes/updater-check.js +37 -0
  113. package/dist/api-routes/updater-register.d.ts +14 -0
  114. package/dist/api-routes/updater-register.js +71 -0
  115. package/dist/api-routes/updater-transfer.d.ts +11 -0
  116. package/dist/api-routes/updater-transfer.js +37 -0
  117. package/dist/api-routes/updater-unbind.d.ts +17 -0
  118. package/dist/api-routes/updater-unbind.js +35 -0
  119. package/dist/api-routes/webhooks-status.d.ts +12 -0
  120. package/dist/api-routes/webhooks-status.js +22 -0
  121. package/dist/api-routes/webhooks-test.d.ts +13 -0
  122. package/dist/api-routes/webhooks-test.js +124 -0
  123. package/dist/api-routes/webhooks.d.ts +6 -0
  124. package/dist/api-routes/webhooks.js +148 -0
  125. package/dist/api-routes/websites-add.d.ts +15 -0
  126. package/dist/api-routes/websites-add.js +92 -0
  127. package/dist/api-routes/websites-list.d.ts +12 -0
  128. package/dist/api-routes/websites-list.js +35 -0
  129. package/dist/api-routes/websites-remove.d.ts +15 -0
  130. package/dist/api-routes/websites-remove.js +69 -0
  131. package/dist/chunk-35S35OIV.js +80 -0
  132. package/dist/chunk-45ARVNT3.js +25 -0
  133. package/dist/chunk-5PIMDP4N.js +25 -0
  134. package/dist/chunk-5ZFTG4BW.js +10 -0
  135. package/dist/chunk-6UIKVKED.js +51 -0
  136. package/dist/chunk-737TIZRU.js +9 -0
  137. package/dist/chunk-AM4DZXXM.js +120 -0
  138. package/dist/chunk-FXNOTESI.js +87 -0
  139. package/dist/chunk-GHNK2GFE.js +48 -0
  140. package/dist/chunk-GRG3LNKH.js +37 -0
  141. package/dist/chunk-INIWFKQ3.js +236 -0
  142. package/dist/chunk-JHY6XTLL.js +24 -0
  143. package/dist/chunk-K22A4ZBS.js +1574 -0
  144. package/dist/chunk-KH22FJO5.js +17 -0
  145. package/dist/chunk-NKDATSPA.js +43 -0
  146. package/dist/chunk-RHJONMLK.js +1267 -0
  147. package/dist/chunk-TJNJKPUL.js +11 -0
  148. package/dist/chunk-V6IMPVF3.js +120 -0
  149. package/dist/chunk-W3QHY5GW.js +19 -0
  150. package/dist/chunk-ZQDGGWJP.js +43 -0
  151. package/package.json +249 -53
  152. package/src/api-routes/__tests__/route-registry.test.ts +7 -1
  153. package/tsconfig.json +0 -9
@@ -0,0 +1,148 @@
1
+ import {
2
+ parseSession,
3
+ requireAdmin
4
+ } from "../chunk-INIWFKQ3.js";
5
+ import {
6
+ resolveStorageConfigForRequest
7
+ } from "../chunk-6UIKVKED.js";
8
+ import {
9
+ gateFeature
10
+ } from "../chunk-5PIMDP4N.js";
11
+ import {
12
+ cachedFetch,
13
+ invalidateCache
14
+ } from "../chunk-45ARVNT3.js";
15
+ import {
16
+ resolveGitHubTokenForRequest
17
+ } from "../chunk-NKDATSPA.js";
18
+ import "../chunk-TJNJKPUL.js";
19
+ import {
20
+ withTrailers
21
+ } from "../chunk-KH22FJO5.js";
22
+
23
+ // src/api-routes/webhooks.ts
24
+ import {
25
+ parseWebhooksFile,
26
+ validateWebhookConfig
27
+ } from "@setzkasten-cms/core";
28
+ var WEBHOOKS_FILE = (contentPath) => `${contentPath}/_webhooks.json`;
29
+ var GET = async ({ request, cookies }) => {
30
+ const denied = requireAdmin(cookies.get("setzkasten_session")?.value);
31
+ if (denied) return denied;
32
+ const tokenResult = await resolveGitHubTokenForRequest(request);
33
+ if (!tokenResult.ok) return new Response(tokenResult.error.message, { status: 500 });
34
+ const storage = await resolveStorageConfigForRequest(request);
35
+ if (!storage) {
36
+ return Response.json({ error: "Could not resolve owner/repo" }, { status: 400 });
37
+ }
38
+ const { owner, repo, branch } = storage;
39
+ const contentPath = resolveContentPath();
40
+ const cacheKey = `webhooks:${owner}/${repo}:${branch}`;
41
+ const webhooks = await cachedFetch(cacheKey, 6e4, async () => {
42
+ const res = await fetch(
43
+ `https://api.github.com/repos/${owner}/${repo}/contents/${WEBHOOKS_FILE(contentPath)}?ref=${branch}`,
44
+ {
45
+ headers: {
46
+ Authorization: `Bearer ${tokenResult.value}`,
47
+ Accept: "application/vnd.github+json",
48
+ "X-GitHub-Api-Version": "2022-11-28"
49
+ }
50
+ }
51
+ );
52
+ if (res.status === 404) return [];
53
+ if (!res.ok) return null;
54
+ const data = await res.json();
55
+ const raw = data.encoding === "base64" ? Buffer.from(data.content, "base64").toString("utf-8") : data.content;
56
+ const parsed = parseWebhooksFile(raw);
57
+ return parsed.ok ? parsed.value.webhooks : null;
58
+ });
59
+ if (webhooks === null) {
60
+ return Response.json({ error: "Could not read webhooks file" }, { status: 502 });
61
+ }
62
+ return Response.json({ webhooks });
63
+ };
64
+ var PUT = async ({ request, cookies }) => {
65
+ const denied = requireAdmin(cookies.get("setzkasten_session")?.value);
66
+ if (denied) return denied;
67
+ const gate = gateFeature("webhooks");
68
+ if (gate) return gate;
69
+ const session = parseSession(cookies.get("setzkasten_session")?.value);
70
+ if (!session) return new Response("Unauthorized", { status: 401 });
71
+ let body;
72
+ try {
73
+ body = await request.json();
74
+ } catch {
75
+ return Response.json({ error: "Invalid JSON" }, { status: 400 });
76
+ }
77
+ if (!Array.isArray(body.webhooks)) {
78
+ return Response.json({ error: "webhooks must be an array" }, { status: 400 });
79
+ }
80
+ const validated = [];
81
+ const seenIds = /* @__PURE__ */ new Set();
82
+ for (let i = 0; i < body.webhooks.length; i++) {
83
+ const result = validateWebhookConfig(body.webhooks[i]);
84
+ if (!result.ok) {
85
+ return Response.json(
86
+ { error: result.error.message, index: i },
87
+ { status: 400 }
88
+ );
89
+ }
90
+ if (seenIds.has(result.value.id)) {
91
+ return Response.json(
92
+ { error: `Duplicate webhook id: ${result.value.id}`, index: i },
93
+ { status: 400 }
94
+ );
95
+ }
96
+ seenIds.add(result.value.id);
97
+ validated.push(result.value);
98
+ }
99
+ const tokenResult = await resolveGitHubTokenForRequest(request);
100
+ if (!tokenResult.ok) return new Response(tokenResult.error.message, { status: 500 });
101
+ const storage = await resolveStorageConfigForRequest(request);
102
+ if (!storage) {
103
+ return Response.json({ error: "Could not resolve owner/repo" }, { status: 400 });
104
+ }
105
+ const { owner, repo, branch } = storage;
106
+ const contentPath = resolveContentPath();
107
+ const filePath = WEBHOOKS_FILE(contentPath);
108
+ const headers = {
109
+ Authorization: `Bearer ${tokenResult.value}`,
110
+ Accept: "application/vnd.github+json",
111
+ "X-GitHub-Api-Version": "2022-11-28",
112
+ "Content-Type": "application/json"
113
+ };
114
+ let currentSha = null;
115
+ const existingRes = await fetch(
116
+ `https://api.github.com/repos/${owner}/${repo}/contents/${filePath}?ref=${branch}`,
117
+ { headers }
118
+ );
119
+ if (existingRes.ok) {
120
+ const data = await existingRes.json();
121
+ currentSha = data.sha;
122
+ }
123
+ const fileBody = JSON.stringify({ version: 1, webhooks: validated }, null, 2);
124
+ const putBody = {
125
+ message: withTrailers("chore(webhooks): update webhook configuration", session.user.email),
126
+ content: Buffer.from(fileBody).toString("base64"),
127
+ branch
128
+ };
129
+ if (currentSha) putBody.sha = currentSha;
130
+ const putRes = await fetch(
131
+ `https://api.github.com/repos/${owner}/${repo}/contents/${filePath}`,
132
+ { method: "PUT", headers, body: JSON.stringify(putBody) }
133
+ );
134
+ if (!putRes.ok) {
135
+ const text = await putRes.text();
136
+ return Response.json({ error: `Webhook write failed: ${text}` }, { status: 502 });
137
+ }
138
+ invalidateCache(`webhooks:${owner}/${repo}:${branch}`);
139
+ return Response.json({ ok: true, webhooks: validated });
140
+ };
141
+ function resolveContentPath() {
142
+ const serverConfig = globalThis.__SETZKASTEN_CONFIG__;
143
+ return serverConfig?.storage?.contentPath ?? "content";
144
+ }
145
+ export {
146
+ GET,
147
+ PUT
148
+ };
@@ -0,0 +1,15 @@
1
+ import { APIRoute } from 'astro';
2
+
3
+ /**
4
+ * POST /api/setzkasten/websites/add
5
+ *
6
+ * Body: { entry: WebsiteEntry }
7
+ *
8
+ * Reads the current websites.json from the config-repo, adds the entry
9
+ * (validated via parseWebsitesRegistry to share one truth on what makes
10
+ * a valid WebsiteEntry), and commits the new file back via the GitHub
11
+ * Contents API.
12
+ */
13
+ declare const POST: APIRoute;
14
+
15
+ export { POST };
@@ -0,0 +1,92 @@
1
+ import {
2
+ readWebsitesRegistryFromGitHub,
3
+ resolveConfigRepoTargetFromGlobals,
4
+ writeWebsitesRegistryToGitHub
5
+ } from "../chunk-35S35OIV.js";
6
+ import {
7
+ requireAdmin
8
+ } from "../chunk-INIWFKQ3.js";
9
+ import "../chunk-6UIKVKED.js";
10
+ import "../chunk-5PIMDP4N.js";
11
+ import "../chunk-45ARVNT3.js";
12
+ import {
13
+ resolveConfigRepoToken
14
+ } from "../chunk-NKDATSPA.js";
15
+ import {
16
+ resolveLicenseTier
17
+ } from "../chunk-TJNJKPUL.js";
18
+ import "../chunk-KH22FJO5.js";
19
+
20
+ // src/api-routes/websites-add.ts
21
+ import {
22
+ addWebsiteToRegistry,
23
+ canAddWebsite,
24
+ parseWebsitesRegistry
25
+ } from "@setzkasten-cms/core";
26
+ var POST = async ({ request, cookies }) => {
27
+ const denied = requireAdmin(cookies.get("setzkasten_session")?.value);
28
+ if (denied) return denied;
29
+ let parsed = {};
30
+ try {
31
+ parsed = await request.json();
32
+ } catch {
33
+ return new Response(JSON.stringify({ error: "Invalid JSON body" }), { status: 400 });
34
+ }
35
+ if (!parsed.entry || typeof parsed.entry !== "object") {
36
+ return new Response(JSON.stringify({ error: 'Missing "entry" in body' }), { status: 400 });
37
+ }
38
+ const entry = parsed.entry;
39
+ if (entry.githubApp && !entry.githubApp.appId) {
40
+ const envAppId = process.env.GITHUB_APP_ID;
41
+ if (envAppId) entry.githubApp.appId = envAppId;
42
+ }
43
+ const validation = parseWebsitesRegistry(JSON.stringify({ websites: [parsed.entry] }));
44
+ if (!validation.ok) {
45
+ return new Response(JSON.stringify({ error: validation.error.message }), { status: 400 });
46
+ }
47
+ const tokenResult = await resolveConfigRepoToken();
48
+ if (!tokenResult.ok) {
49
+ return new Response(JSON.stringify({ error: tokenResult.error.message }), { status: 500 });
50
+ }
51
+ const target = resolveConfigRepoTargetFromGlobals(tokenResult.value);
52
+ if (!target) {
53
+ return new Response(
54
+ JSON.stringify({
55
+ error: 'Multi-mode storage not configured (storage.kind must be "multi").'
56
+ }),
57
+ { status: 400 }
58
+ );
59
+ }
60
+ const current = await readWebsitesRegistryFromGitHub(target);
61
+ if (!current.ok) {
62
+ return new Response(JSON.stringify({ error: current.error.message }), { status: 502 });
63
+ }
64
+ const tier = resolveLicenseTier();
65
+ const allowed = canAddWebsite(tier, current.value.registry.websites.length);
66
+ if (!allowed.ok) {
67
+ return new Response(
68
+ JSON.stringify({ error: allowed.reason, tier: allowed.tier, limit: allowed.limit }),
69
+ { status: 402, headers: { "Content-Type": "application/json" } }
70
+ );
71
+ }
72
+ const merged = addWebsiteToRegistry(current.value.registry, parsed.entry);
73
+ if (!merged.ok) {
74
+ return new Response(JSON.stringify({ error: merged.error.message }), { status: 409 });
75
+ }
76
+ const written = await writeWebsitesRegistryToGitHub(
77
+ target,
78
+ merged.value,
79
+ current.value.sha,
80
+ `feat(websites): add "${parsed.entry.id}" to registry`
81
+ );
82
+ if (!written.ok) {
83
+ return new Response(JSON.stringify({ error: written.error.message }), { status: 502 });
84
+ }
85
+ return new Response(JSON.stringify({ ok: true, websites: merged.value.websites }), {
86
+ status: 200,
87
+ headers: { "Content-Type": "application/json" }
88
+ });
89
+ };
90
+ export {
91
+ POST
92
+ };
@@ -0,0 +1,12 @@
1
+ import { APIRoute } from 'astro';
2
+
3
+ /**
4
+ * GET /api/setzkasten/websites
5
+ *
6
+ * Returns the list of managed websites visible to the admin SPA.
7
+ * Strips private fields (`githubApp` installation refs, `allowedEmails`)
8
+ * — the client only needs ids/names/repos for the switcher.
9
+ */
10
+ declare const GET: APIRoute;
11
+
12
+ export { GET };
@@ -0,0 +1,35 @@
1
+ import {
2
+ listAllWebsites
3
+ } from "../chunk-V6IMPVF3.js";
4
+
5
+ // src/api-routes/websites-list.ts
6
+ var GET = async ({ cookies }) => {
7
+ const session = cookies.get("setzkasten_session")?.value;
8
+ if (!session) {
9
+ return new Response(JSON.stringify({ authenticated: false }), {
10
+ status: 401,
11
+ headers: { "Content-Type": "application/json" }
12
+ });
13
+ }
14
+ const result = await listAllWebsites();
15
+ if (!result.ok) {
16
+ return new Response(JSON.stringify({ error: result.error.message }), {
17
+ status: 500,
18
+ headers: { "Content-Type": "application/json" }
19
+ });
20
+ }
21
+ const websites = result.value.map((entry) => ({
22
+ id: entry.id,
23
+ name: entry.name,
24
+ repo: entry.repo,
25
+ branch: entry.branch,
26
+ previewOrigin: entry.previewOrigin
27
+ }));
28
+ return new Response(JSON.stringify({ websites }), {
29
+ status: 200,
30
+ headers: { "Content-Type": "application/json" }
31
+ });
32
+ };
33
+ export {
34
+ GET
35
+ };
@@ -0,0 +1,15 @@
1
+ import { APIRoute } from 'astro';
2
+
3
+ /**
4
+ * POST /api/setzkasten/websites/remove
5
+ *
6
+ * Body: { id: string }
7
+ *
8
+ * Drops the matching entry from websites.json and commits the new file.
9
+ * Admin-only — editors must not be able to detach websites from the
10
+ * registry. Returns 404 when the id is not present, 502 when GitHub I/O
11
+ * fails.
12
+ */
13
+ declare const POST: APIRoute;
14
+
15
+ export { POST };
@@ -0,0 +1,69 @@
1
+ import {
2
+ readWebsitesRegistryFromGitHub,
3
+ resolveConfigRepoTargetFromGlobals,
4
+ writeWebsitesRegistryToGitHub
5
+ } from "../chunk-35S35OIV.js";
6
+ import {
7
+ requireAdmin
8
+ } from "../chunk-INIWFKQ3.js";
9
+ import "../chunk-6UIKVKED.js";
10
+ import "../chunk-5PIMDP4N.js";
11
+ import "../chunk-45ARVNT3.js";
12
+ import {
13
+ resolveConfigRepoToken
14
+ } from "../chunk-NKDATSPA.js";
15
+ import "../chunk-TJNJKPUL.js";
16
+ import "../chunk-KH22FJO5.js";
17
+
18
+ // src/api-routes/websites-remove.ts
19
+ import { removeWebsiteFromRegistry } from "@setzkasten-cms/core";
20
+ var POST = async ({ request, cookies }) => {
21
+ const denied = requireAdmin(cookies.get("setzkasten_session")?.value);
22
+ if (denied) return denied;
23
+ let body = {};
24
+ try {
25
+ body = await request.json();
26
+ } catch {
27
+ return new Response(JSON.stringify({ error: "Invalid JSON body" }), { status: 400 });
28
+ }
29
+ if (!body.id || typeof body.id !== "string") {
30
+ return new Response(JSON.stringify({ error: 'Missing "id" in body' }), { status: 400 });
31
+ }
32
+ const tokenResult = await resolveConfigRepoToken();
33
+ if (!tokenResult.ok) {
34
+ return new Response(JSON.stringify({ error: tokenResult.error.message }), { status: 500 });
35
+ }
36
+ const target = resolveConfigRepoTargetFromGlobals(tokenResult.value);
37
+ if (!target) {
38
+ return new Response(
39
+ JSON.stringify({
40
+ error: 'Multi-mode storage not configured (storage.kind must be "multi").'
41
+ }),
42
+ { status: 400 }
43
+ );
44
+ }
45
+ const current = await readWebsitesRegistryFromGitHub(target);
46
+ if (!current.ok) {
47
+ return new Response(JSON.stringify({ error: current.error.message }), { status: 502 });
48
+ }
49
+ const next = removeWebsiteFromRegistry(current.value.registry, body.id);
50
+ if (!next.ok) {
51
+ return new Response(JSON.stringify({ error: next.error.message }), { status: 404 });
52
+ }
53
+ const written = await writeWebsitesRegistryToGitHub(
54
+ target,
55
+ next.value,
56
+ current.value.sha,
57
+ `chore(websites): remove "${body.id}" from registry`
58
+ );
59
+ if (!written.ok) {
60
+ return new Response(JSON.stringify({ error: written.error.message }), { status: 502 });
61
+ }
62
+ return new Response(JSON.stringify({ ok: true, websites: next.value.websites }), {
63
+ status: 200,
64
+ headers: { "Content-Type": "application/json" }
65
+ });
66
+ };
67
+ export {
68
+ POST
69
+ };
@@ -0,0 +1,80 @@
1
+ import {
2
+ withTrailers
3
+ } from "./chunk-KH22FJO5.js";
4
+
5
+ // src/api-routes/_websites-store.ts
6
+ import {
7
+ err,
8
+ networkError,
9
+ ok,
10
+ parseWebsitesRegistry
11
+ } from "@setzkasten-cms/core";
12
+ var githubHeaders = (token) => ({
13
+ Authorization: `Bearer ${token}`,
14
+ Accept: "application/vnd.github+json",
15
+ "X-GitHub-Api-Version": "2022-11-28",
16
+ "Content-Type": "application/json"
17
+ });
18
+ async function readWebsitesRegistryFromGitHub(target) {
19
+ try {
20
+ const url = `https://api.github.com/repos/${target.owner}/${target.repo}/contents/${target.path}?ref=${target.branch}`;
21
+ const response = await fetch(url, { headers: githubHeaders(target.token) });
22
+ if (response.status === 404) {
23
+ return ok({ registry: { websites: [] }, sha: null });
24
+ }
25
+ if (!response.ok) {
26
+ return err(networkError(`GitHub returned ${response.status} reading ${target.path}`));
27
+ }
28
+ const data = await response.json();
29
+ const decoded = Buffer.from(data.content, "base64").toString("utf-8");
30
+ const parsed = parseWebsitesRegistry(decoded);
31
+ if (!parsed.ok) return parsed;
32
+ return ok({ registry: parsed.value, sha: data.sha });
33
+ } catch (cause) {
34
+ const message = cause instanceof Error ? cause.message : "Unknown error";
35
+ return err(networkError(`Failed to read ${target.path}: ${message}`, cause));
36
+ }
37
+ }
38
+ async function writeWebsitesRegistryToGitHub(target, registry, previousSha, commitMessage) {
39
+ const body = {
40
+ message: withTrailers(commitMessage),
41
+ content: Buffer.from(JSON.stringify(registry, null, 2)).toString("base64"),
42
+ branch: target.branch
43
+ };
44
+ if (previousSha) body.sha = previousSha;
45
+ try {
46
+ const response = await fetch(
47
+ `https://api.github.com/repos/${target.owner}/${target.repo}/contents/${target.path}`,
48
+ { method: "PUT", headers: githubHeaders(target.token), body: JSON.stringify(body) }
49
+ );
50
+ if (!response.ok) {
51
+ const text = await response.text();
52
+ return err(networkError(`GitHub PUT failed: ${response.status} ${text}`));
53
+ }
54
+ return ok(void 0);
55
+ } catch (cause) {
56
+ const message = cause instanceof Error ? cause.message : "Unknown error";
57
+ return err(networkError(`Failed to write ${target.path}: ${message}`, cause));
58
+ }
59
+ }
60
+ function resolveConfigRepoTargetFromGlobals(token) {
61
+ const fullConfig = globalThis.__SETZKASTEN_FULL_CONFIG__;
62
+ const storage = fullConfig?.storage;
63
+ if (!storage || storage.kind !== "multi" && storage.kind !== "standalone") return null;
64
+ if (!storage.configRepo) return null;
65
+ const [owner, repo] = storage.configRepo.split("/");
66
+ if (!owner || !repo) return null;
67
+ return {
68
+ owner,
69
+ repo,
70
+ branch: storage.configBranch ?? "main",
71
+ path: "websites.json",
72
+ token
73
+ };
74
+ }
75
+
76
+ export {
77
+ readWebsitesRegistryFromGitHub,
78
+ writeWebsitesRegistryToGitHub,
79
+ resolveConfigRepoTargetFromGlobals
80
+ };
@@ -0,0 +1,25 @@
1
+ // src/api-routes/_github-cache.ts
2
+ var cache = /* @__PURE__ */ new Map();
3
+ async function cachedFetch(key, ttlMs, fetcher) {
4
+ const entry = cache.get(key);
5
+ const now = Date.now();
6
+ if (entry !== void 0 && now - entry.fetchedAt < ttlMs) {
7
+ return entry.value;
8
+ }
9
+ try {
10
+ const value = await fetcher();
11
+ cache.set(key, { value, fetchedAt: now });
12
+ return value;
13
+ } catch (err) {
14
+ if (entry !== void 0) return entry.value;
15
+ throw err;
16
+ }
17
+ }
18
+ function invalidateCache(key) {
19
+ cache.delete(key);
20
+ }
21
+
22
+ export {
23
+ cachedFetch,
24
+ invalidateCache
25
+ };
@@ -0,0 +1,25 @@
1
+ import {
2
+ resolveLicenseTier
3
+ } from "./chunk-TJNJKPUL.js";
4
+
5
+ // src/api-routes/_feature-gate.ts
6
+ import { checkFeature } from "@setzkasten-cms/core";
7
+ function gateFeature(feature) {
8
+ const tier = resolveLicenseTier();
9
+ const result = checkFeature(feature, tier);
10
+ if (result.ok) return null;
11
+ return new Response(
12
+ JSON.stringify({
13
+ error: result.reason,
14
+ code: "feature-locked",
15
+ feature: result.feature,
16
+ requiredTier: result.requiredTier,
17
+ currentTier: tier
18
+ }),
19
+ { status: 403, headers: { "Content-Type": "application/json" } }
20
+ );
21
+ }
22
+
23
+ export {
24
+ gateFeature
25
+ };
@@ -0,0 +1,10 @@
1
+ // src/api-routes/_vercel-origin.ts
2
+ function getPublicOrigin(request) {
3
+ const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host") ?? "localhost";
4
+ const proto = request.headers.get("x-forwarded-proto")?.split(",")[0]?.trim() ?? "https";
5
+ return `${proto}://${host}`;
6
+ }
7
+
8
+ export {
9
+ getPublicOrigin
10
+ };
@@ -0,0 +1,51 @@
1
+ // src/api-routes/_storage-config.ts
2
+ function resolveStorageConfig(body) {
3
+ const buildConfig = typeof __SETZKASTEN_STORAGE__ !== "undefined" ? __SETZKASTEN_STORAGE__ : null;
4
+ const serverConfig = globalThis.__SETZKASTEN_CONFIG__;
5
+ const owner = body?.owner || buildConfig?.owner || serverConfig?.storage?.owner;
6
+ const repo = body?.repo || buildConfig?.repo || serverConfig?.storage?.repo;
7
+ const branch = body?.branch || buildConfig?.branch || serverConfig?.storage?.branch || "main";
8
+ const projectPrefix = buildConfig?.projectPrefix ?? "";
9
+ if (!owner || !repo) return null;
10
+ return { owner, repo, branch, projectPrefix };
11
+ }
12
+ function prefixPath(filePath, projectPrefix) {
13
+ if (!projectPrefix) return filePath;
14
+ return `${projectPrefix}/${filePath}`;
15
+ }
16
+ async function resolveStorageConfigForRequest(request, body) {
17
+ const buildConfig = typeof __SETZKASTEN_STORAGE__ !== "undefined" ? __SETZKASTEN_STORAGE__ : null;
18
+ const inheritPrefix = (owner, repo) => {
19
+ if (!buildConfig?.projectPrefix) return "";
20
+ if (buildConfig.owner === owner && buildConfig.repo === repo) return buildConfig.projectPrefix;
21
+ return "";
22
+ };
23
+ if (body?.owner && body.repo) {
24
+ return {
25
+ owner: body.owner,
26
+ repo: body.repo,
27
+ branch: body.branch ?? "main",
28
+ projectPrefix: inheritPrefix(body.owner, body.repo)
29
+ };
30
+ }
31
+ const { resolveCurrentWebsite } = await import("./api-routes/_website-resolver.js");
32
+ const resolved = await resolveCurrentWebsite(request);
33
+ if (resolved.ok) {
34
+ const [owner, repo] = resolved.value.repo.split("/");
35
+ if (owner && repo) {
36
+ return {
37
+ owner,
38
+ repo,
39
+ branch: resolved.value.branch,
40
+ projectPrefix: inheritPrefix(owner, repo)
41
+ };
42
+ }
43
+ }
44
+ return resolveStorageConfig(body);
45
+ }
46
+
47
+ export {
48
+ resolveStorageConfig,
49
+ prefixPath,
50
+ resolveStorageConfigForRequest
51
+ };
@@ -0,0 +1,9 @@
1
+ // src/api-routes/_webhook-signing.ts
2
+ import { createHmac } from "crypto";
3
+ function signPayload(body, secret) {
4
+ return createHmac("sha256", secret).update(body, "utf8").digest("hex");
5
+ }
6
+
7
+ export {
8
+ signPayload
9
+ };