@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,11 @@
1
+ import {
2
+ readWebsitesRegistryFromGitHub,
3
+ resolveConfigRepoTargetFromGlobals,
4
+ writeWebsitesRegistryToGitHub
5
+ } from "../chunk-35S35OIV.js";
6
+ import "../chunk-KH22FJO5.js";
7
+ export {
8
+ readWebsitesRegistryFromGitHub,
9
+ resolveConfigRepoTargetFromGlobals,
10
+ writeWebsitesRegistryToGitHub
11
+ };
@@ -0,0 +1,12 @@
1
+ import { APIRoute } from 'astro';
2
+
3
+ /**
4
+ * Asset proxy – serves images from the private GitHub repo.
5
+ * Used by the admin UI to display image thumbnails without exposing the GitHub token.
6
+ *
7
+ * GET /api/setzkasten/asset/public/images/about/LP_Logo.png
8
+ * → fetches from GitHub API and returns the raw binary with correct Content-Type.
9
+ */
10
+ declare const GET: APIRoute;
11
+
12
+ export { GET };
@@ -0,0 +1,67 @@
1
+ import {
2
+ resolveGitHubTokenForRequest
3
+ } from "../chunk-NKDATSPA.js";
4
+
5
+ // src/api-routes/asset-proxy.ts
6
+ var GET = async ({ params, request, cookies }) => {
7
+ const session = cookies.get("setzkasten_session")?.value;
8
+ if (!session) {
9
+ return new Response("Unauthorized", { status: 401 });
10
+ }
11
+ const assetPath = params.path;
12
+ if (!assetPath) {
13
+ return new Response("Missing path", { status: 400 });
14
+ }
15
+ const tokenResult = await resolveGitHubTokenForRequest(request);
16
+ if (!tokenResult.ok) {
17
+ return new Response(tokenResult.error.message, { status: 500 });
18
+ }
19
+ const githubToken = tokenResult.value;
20
+ const config = globalThis.__SETZKASTEN_CONFIG__;
21
+ if (!config?.storage) {
22
+ return new Response("Storage not configured", { status: 500 });
23
+ }
24
+ const { owner, repo, branch } = config.storage;
25
+ const githubUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${assetPath}?ref=${branch}`;
26
+ try {
27
+ const response = await fetch(githubUrl, {
28
+ headers: {
29
+ Authorization: `Bearer ${githubToken}`,
30
+ Accept: "application/vnd.github.raw+json",
31
+ "X-GitHub-Api-Version": "2022-11-28"
32
+ }
33
+ });
34
+ if (!response.ok) {
35
+ return new Response("Asset not found", { status: response.status });
36
+ }
37
+ const contentType = guessMimeType(assetPath);
38
+ const body = await response.arrayBuffer();
39
+ return new Response(body, {
40
+ status: 200,
41
+ headers: {
42
+ "Content-Type": contentType,
43
+ "Cache-Control": "private, max-age=300"
44
+ }
45
+ });
46
+ } catch (error) {
47
+ console.error("[setzkasten] Asset proxy error:", error);
48
+ return new Response("Failed to fetch asset", { status: 502 });
49
+ }
50
+ };
51
+ function guessMimeType(path) {
52
+ const ext = path.split(".").pop()?.toLowerCase();
53
+ const types = {
54
+ jpg: "image/jpeg",
55
+ jpeg: "image/jpeg",
56
+ png: "image/png",
57
+ gif: "image/gif",
58
+ webp: "image/webp",
59
+ avif: "image/avif",
60
+ svg: "image/svg+xml",
61
+ pdf: "application/pdf"
62
+ };
63
+ return types[ext ?? ""] ?? "application/octet-stream";
64
+ }
65
+ export {
66
+ GET
67
+ };
@@ -0,0 +1,9 @@
1
+ import { APIRoute } from 'astro';
2
+
3
+ /**
4
+ * GitHub OAuth callback handler.
5
+ * Google uses the GIS flow (POST /api/setzkasten/auth/google) — no callback needed there.
6
+ */
7
+ declare const GET: APIRoute;
8
+
9
+ export { GET };
@@ -0,0 +1,68 @@
1
+ import {
2
+ sessionCookieOptions
3
+ } from "../chunk-JHY6XTLL.js";
4
+ import {
5
+ makeRoleResolver
6
+ } from "../chunk-ZQDGGWJP.js";
7
+ import "../chunk-INIWFKQ3.js";
8
+ import "../chunk-6UIKVKED.js";
9
+ import "../chunk-5PIMDP4N.js";
10
+ import "../chunk-45ARVNT3.js";
11
+ import "../chunk-NKDATSPA.js";
12
+ import "../chunk-TJNJKPUL.js";
13
+ import "../chunk-KH22FJO5.js";
14
+
15
+ // src/api-routes/auth-callback.ts
16
+ import { createGitHubAuth } from "@setzkasten-cms/auth";
17
+ var GET = async ({ request, url, cookies, redirect }) => {
18
+ const code = url.searchParams.get("code");
19
+ const state = url.searchParams.get("state");
20
+ if (!code) {
21
+ return new Response("Missing authorization code", { status: 400 });
22
+ }
23
+ const storedRaw = cookies.get("setzkasten_oauth_state")?.value;
24
+ let storedState;
25
+ if (storedRaw) {
26
+ try {
27
+ storedState = JSON.parse(storedRaw).state;
28
+ } catch {
29
+ storedState = storedRaw;
30
+ }
31
+ }
32
+ if (!storedState || state !== storedState) {
33
+ return new Response("Invalid state parameter", { status: 400 });
34
+ }
35
+ cookies.delete("setzkasten_oauth_state", { path: "/" });
36
+ const config = globalThis.__SETZKASTEN_CONFIG__;
37
+ const adminPath = config?.adminPath ?? "/admin";
38
+ const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host") ?? url.host;
39
+ const protocol = request.headers.get("x-forwarded-proto") ?? (url.protocol === "https:" ? "https" : "http");
40
+ const redirectUri = `${protocol}://${host}/api/setzkasten/auth/callback`;
41
+ const allowedEmailsRaw = import.meta.env.SETZKASTEN_ALLOWED_EMAILS ?? process.env.SETZKASTEN_ALLOWED_EMAILS ?? "";
42
+ const allowedEmails = allowedEmailsRaw ? allowedEmailsRaw.split(",").map((e) => e.trim()) : void 0;
43
+ const auth = createGitHubAuth({
44
+ clientId: import.meta.env.GITHUB_CLIENT_ID ?? process.env.GITHUB_CLIENT_ID ?? "",
45
+ clientSecret: import.meta.env.GITHUB_CLIENT_SECRET ?? process.env.GITHUB_CLIENT_SECRET ?? "",
46
+ redirectUri,
47
+ allowedEmails,
48
+ resolveRole: makeRoleResolver("github", allowedEmails)
49
+ });
50
+ try {
51
+ const sessionResult = await auth.handleCallback(code);
52
+ if (!sessionResult.ok) {
53
+ return new Response(`Authentication failed: ${sessionResult.error.message}`, { status: 403 });
54
+ }
55
+ const session = sessionResult.value;
56
+ cookies.set(
57
+ "setzkasten_session",
58
+ JSON.stringify({ user: session.user, expiresAt: session.expiresAt }),
59
+ sessionCookieOptions(import.meta.env.PROD)
60
+ );
61
+ return redirect(adminPath);
62
+ } catch {
63
+ return new Response("Authentication failed", { status: 500 });
64
+ }
65
+ };
66
+ export {
67
+ GET
68
+ };
@@ -0,0 +1,11 @@
1
+ import { APIRoute } from 'astro';
2
+
3
+ /**
4
+ * Login initiation – redirects the user to GitHub OAuth.
5
+ * Google uses GIS (POST /api/setzkasten/auth/google) instead of this redirect flow.
6
+ *
7
+ * GET /api/setzkasten/auth/login?provider=github
8
+ */
9
+ declare const GET: APIRoute;
10
+
11
+ export { GET };
@@ -0,0 +1,27 @@
1
+ // src/api-routes/auth-login.ts
2
+ import { createGitHubAuth } from "@setzkasten-cms/auth";
3
+ var GET = async ({ request, url, cookies, redirect }) => {
4
+ const config = globalThis.__SETZKASTEN_CONFIG__;
5
+ const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host") ?? url.host;
6
+ const protocol = request.headers.get("x-forwarded-proto") ?? (url.protocol === "https:" ? "https" : "http");
7
+ const origin = `${protocol}://${host}`;
8
+ const redirectUri = `${origin}/api/setzkasten/auth/callback`;
9
+ const ghClientId = import.meta.env.GITHUB_CLIENT_ID ?? process.env.GITHUB_CLIENT_ID ?? "";
10
+ const ghClientSecret = import.meta.env.GITHUB_CLIENT_SECRET ?? process.env.GITHUB_CLIENT_SECRET ?? "";
11
+ if (!ghClientId || !ghClientSecret) {
12
+ return new Response("GitHub OAuth not configured", { status: 500 });
13
+ }
14
+ const auth = createGitHubAuth({ clientId: ghClientId, clientSecret: ghClientSecret, redirectUri });
15
+ const { url: loginUrl, state } = auth.getLoginUrl();
16
+ cookies.set("setzkasten_oauth_state", JSON.stringify({ state, provider: "github" }), {
17
+ httpOnly: true,
18
+ secure: true,
19
+ sameSite: "lax",
20
+ path: "/",
21
+ maxAge: 600
22
+ });
23
+ return redirect(loginUrl);
24
+ };
25
+ export {
26
+ GET
27
+ };
@@ -0,0 +1,10 @@
1
+ import { APIRoute } from 'astro';
2
+
3
+ /**
4
+ * Logout – clears the session cookie and redirects to home.
5
+ * Mirrors the same `domain` attribute used when the cookie was set so the
6
+ * browser actually deletes it on subdomains in standalone-admin setups.
7
+ */
8
+ declare const GET: APIRoute;
9
+
10
+ export { GET };
@@ -0,0 +1,13 @@
1
+ import {
2
+ sessionCookieOptions
3
+ } from "../chunk-JHY6XTLL.js";
4
+
5
+ // src/api-routes/auth-logout.ts
6
+ var GET = async ({ cookies, redirect }) => {
7
+ const opts = sessionCookieOptions(false);
8
+ cookies.delete("setzkasten_session", { path: "/", domain: opts.domain });
9
+ return redirect("/");
10
+ };
11
+ export {
12
+ GET
13
+ };
@@ -0,0 +1,9 @@
1
+ import { APIRoute } from 'astro';
2
+
3
+ /**
4
+ * Session check – returns current user info or 401.
5
+ * Used by the admin SPA to check if the user is logged in.
6
+ */
7
+ declare const GET: APIRoute;
8
+
9
+ export { GET };
@@ -0,0 +1,31 @@
1
+ // src/api-routes/auth-session.ts
2
+ var GET = async ({ cookies }) => {
3
+ const session = cookies.get("setzkasten_session")?.value;
4
+ if (!session) {
5
+ return new Response(JSON.stringify({ authenticated: false }), {
6
+ status: 401,
7
+ headers: { "Content-Type": "application/json" }
8
+ });
9
+ }
10
+ try {
11
+ const parsed = JSON.parse(session);
12
+ if (parsed.expiresAt < Date.now()) {
13
+ cookies.delete("setzkasten_session", { path: "/" });
14
+ return new Response(JSON.stringify({ authenticated: false, reason: "expired" }), {
15
+ status: 401,
16
+ headers: { "Content-Type": "application/json" }
17
+ });
18
+ }
19
+ return new Response(JSON.stringify({ authenticated: true, user: parsed.user }), {
20
+ headers: { "Content-Type": "application/json" }
21
+ });
22
+ } catch {
23
+ return new Response(JSON.stringify({ authenticated: false }), {
24
+ status: 401,
25
+ headers: { "Content-Type": "application/json" }
26
+ });
27
+ }
28
+ };
29
+ export {
30
+ GET
31
+ };
@@ -0,0 +1,18 @@
1
+ import { APIRoute } from 'astro';
2
+
3
+ /**
4
+ * POST /api/setzkasten/auth/setzkasten-login
5
+ * Body: { idToken: string } — Firebase ID token from signInWithPopup
6
+ *
7
+ * Verifies the Firebase JWT against Firebase's public JWKS (no secret needed).
8
+ * Access is gated exclusively by _editors.json (fail-closed).
9
+ *
10
+ * Editors live in the build-time-configured repo regardless of which website
11
+ * the request is targeting — in single-mode that's the website's repo, in
12
+ * multi-mode it's the config-repo. The per-request resolver and X-SK-Website
13
+ * header are intentionally NOT consulted here, because login predates any
14
+ * website selection.
15
+ */
16
+ declare const POST: APIRoute;
17
+
18
+ export { POST };
@@ -0,0 +1,74 @@
1
+ import {
2
+ readGlobalConfig
3
+ } from "../chunk-AM4DZXXM.js";
4
+ import {
5
+ sessionCookieOptions
6
+ } from "../chunk-JHY6XTLL.js";
7
+ import {
8
+ makeRoleResolver
9
+ } from "../chunk-ZQDGGWJP.js";
10
+ import {
11
+ readEditorsFile
12
+ } from "../chunk-INIWFKQ3.js";
13
+ import {
14
+ resolveStorageConfig
15
+ } from "../chunk-6UIKVKED.js";
16
+ import {
17
+ gateFeature
18
+ } from "../chunk-5PIMDP4N.js";
19
+ import "../chunk-45ARVNT3.js";
20
+ import {
21
+ resolveConfigRepoToken
22
+ } from "../chunk-NKDATSPA.js";
23
+ import "../chunk-TJNJKPUL.js";
24
+ import "../chunk-KH22FJO5.js";
25
+
26
+ // src/api-routes/auth-setzkasten-login.ts
27
+ import { verifyFirebaseJwt } from "@setzkasten-cms/auth";
28
+ var POST = async ({ request, cookies }) => {
29
+ const gate = gateFeature("google-auth");
30
+ if (gate) return gate;
31
+ const body = await request.json().catch(() => null);
32
+ const idToken = body?.idToken;
33
+ if (!idToken) {
34
+ return new Response("Missing idToken", { status: 400 });
35
+ }
36
+ const storage = resolveStorageConfig();
37
+ if (!storage) {
38
+ return new Response("Storage not configured", { status: 500 });
39
+ }
40
+ const { owner, repo, branch } = storage;
41
+ const serverConfig = globalThis.__SETZKASTEN_CONFIG__;
42
+ const contentPath = serverConfig?.storage?.contentPath ?? "content";
43
+ const tokenResult = await resolveConfigRepoToken();
44
+ if (!tokenResult.ok) {
45
+ return new Response(`GitHub token unavailable: ${tokenResult.error.message}`, { status: 503 });
46
+ }
47
+ const globalCfg = await readGlobalConfig().catch(() => null);
48
+ if (!globalCfg?.firebaseConfig) {
49
+ return new Response("SetzKastenLogin not configured", { status: 500 });
50
+ }
51
+ const editors = await readEditorsFile(owner, repo, branch, contentPath, tokenResult.value);
52
+ if (editors === null) {
53
+ return new Response("Editors list unavailable \u2014 no access granted", { status: 503 });
54
+ }
55
+ const allowedEmails = editors.map((e) => e.email);
56
+ const result = await verifyFirebaseJwt(
57
+ idToken,
58
+ allowedEmails,
59
+ makeRoleResolver("google", allowedEmails)
60
+ );
61
+ if (!result.ok) {
62
+ return new Response(result.error.message, { status: 403 });
63
+ }
64
+ const session = result.value;
65
+ cookies.set(
66
+ "setzkasten_session",
67
+ JSON.stringify({ user: session.user, expiresAt: session.expiresAt }),
68
+ sessionCookieOptions(import.meta.env.PROD)
69
+ );
70
+ return Response.json({ ok: true });
71
+ };
72
+ export {
73
+ POST
74
+ };
@@ -0,0 +1,14 @@
1
+ import { APIRoute } from 'astro';
2
+
3
+ /**
4
+ * POST /api/setzkasten/catalog/add
5
+ *
6
+ * Adds a catalog template to a page:
7
+ * - Creates content JSON with template's defaultContent (_sections/{key}.json)
8
+ * - Appends new entry to the page config (pages/_{pageKey}.json)
9
+ *
10
+ * Body: { templateName, pageKey, sectionKey? (override), owner?, repo?, branch?, contentPath? }
11
+ */
12
+ declare const POST: APIRoute;
13
+
14
+ export { POST };
@@ -0,0 +1,153 @@
1
+ import {
2
+ addToPageConfig,
3
+ generateAddKey
4
+ } from "../chunk-GHNK2GFE.js";
5
+ import {
6
+ buildCatalogAddCommit,
7
+ validateCatalogAddBody
8
+ } from "../chunk-GRG3LNKH.js";
9
+ import {
10
+ guardPageAccess,
11
+ parseSession
12
+ } from "../chunk-INIWFKQ3.js";
13
+ import {
14
+ prefixPath,
15
+ resolveStorageConfigForRequest
16
+ } from "../chunk-6UIKVKED.js";
17
+ import "../chunk-5PIMDP4N.js";
18
+ import "../chunk-45ARVNT3.js";
19
+ import {
20
+ resolveGitHubTokenForRequest
21
+ } from "../chunk-NKDATSPA.js";
22
+ import "../chunk-TJNJKPUL.js";
23
+ import {
24
+ withTrailers
25
+ } from "../chunk-KH22FJO5.js";
26
+
27
+ // src/api-routes/catalog-add.ts
28
+ import { registry } from "@setzkasten-cms/catalog";
29
+ var POST = async ({ request, cookies }) => {
30
+ const session = cookies.get("setzkasten_session")?.value;
31
+ if (!session) return new Response("Unauthorized", { status: 401 });
32
+ const tokenResult = await resolveGitHubTokenForRequest(request);
33
+ if (!tokenResult.ok) {
34
+ return new Response(tokenResult.error.message, { status: 500 });
35
+ }
36
+ const githubToken = tokenResult.value;
37
+ try {
38
+ const body = await request.json();
39
+ let validated;
40
+ try {
41
+ validated = validateCatalogAddBody(body);
42
+ } catch (e) {
43
+ return Response.json({ error: e instanceof Error ? e.message : "Invalid request" }, { status: 400 });
44
+ }
45
+ const { templateName, pageKey } = validated;
46
+ const storage = await resolveStorageConfigForRequest(request, body);
47
+ if (!storage) return Response.json({ error: "Could not resolve owner/repo" }, { status: 400 });
48
+ const { owner, repo, branch, projectPrefix } = storage;
49
+ const serverConfig = globalThis.__SETZKASTEN_CONFIG__;
50
+ const fullConfig = globalThis.__SETZKASTEN_FULL_CONFIG__;
51
+ const contentPath = body.contentPath || serverConfig?.storage?.contentPath || "content";
52
+ const denied = await guardPageAccess(parseSession(cookies.get("setzkasten_session")?.value), pageKey, fullConfig, request);
53
+ if (denied) return denied;
54
+ const headers = {
55
+ Authorization: `Bearer ${githubToken}`,
56
+ Accept: "application/vnd.github+json",
57
+ "X-GitHub-Api-Version": "2022-11-28",
58
+ "Content-Type": "application/json"
59
+ };
60
+ const configKey = "_" + pageKey.replace(/\//g, "_");
61
+ const rawPageConfigPath = `${contentPath}/pages/${configKey}.json`;
62
+ const pageConfigPath = prefixPath(rawPageConfigPath, projectPrefix);
63
+ const pageConfigRaw = await fetchFileContent(owner, repo, branch, pageConfigPath, githubToken);
64
+ if (!pageConfigRaw) return Response.json({ error: "Page config not found" }, { status: 404 });
65
+ const pageConfig = JSON.parse(pageConfigRaw);
66
+ const existingKeys = (pageConfig.sections ?? []).map((s) => s.key);
67
+ const sectionKey = validated.sectionKey ?? generateAddKey(existingKeys, templateName);
68
+ if (existingKeys.includes(sectionKey)) {
69
+ return Response.json({ error: `Key "${sectionKey}" already exists on this page` }, { status: 409 });
70
+ }
71
+ const template = registry.get(templateName);
72
+ const { sectionJsonPath } = buildCatalogAddCommit({
73
+ contentPath,
74
+ projectPrefix,
75
+ pageKey,
76
+ sectionKey,
77
+ templateName,
78
+ pageConfigPath: rawPageConfigPath
79
+ });
80
+ const updatedConfig = addToPageConfig(pageConfig, sectionKey, templateName);
81
+ const commitResult = await batchCommit(
82
+ owner,
83
+ repo,
84
+ branch,
85
+ [
86
+ { path: pageConfigPath, content: JSON.stringify(updatedConfig, null, 2) },
87
+ { path: sectionJsonPath, content: JSON.stringify(template.defaultContent, null, 2) }
88
+ ],
89
+ withTrailers(
90
+ `content: add catalog template "${templateName}" as "${sectionKey}" to ${pageKey}`,
91
+ parseSession(cookies.get("setzkasten_session")?.value)?.user?.email
92
+ ),
93
+ headers
94
+ );
95
+ if (!commitResult.ok) return Response.json({ error: commitResult.error }, { status: 500 });
96
+ return Response.json({ success: true, sectionKey, commitSha: commitResult.sha });
97
+ } catch (error) {
98
+ console.error("[setzkasten] catalog-add error:", error);
99
+ return Response.json(
100
+ { error: error instanceof Error ? error.message : "Catalog add failed" },
101
+ { status: 500 }
102
+ );
103
+ }
104
+ };
105
+ async function fetchFileContent(owner, repo, branch, path, token) {
106
+ try {
107
+ const res = await fetch(
108
+ `https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`,
109
+ { headers: { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" } }
110
+ );
111
+ if (!res.ok) return null;
112
+ const data = await res.json();
113
+ return data.encoding === "base64" ? Buffer.from(data.content, "base64").toString("utf-8") : data.content;
114
+ } catch {
115
+ return null;
116
+ }
117
+ }
118
+ async function batchCommit(owner, repo, branch, files, message, headers) {
119
+ try {
120
+ const refRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, { headers });
121
+ if (!refRes.ok) return { ok: false, error: `Failed to get HEAD: ${refRes.status}` };
122
+ const { object: { sha: headSha } } = await refRes.json();
123
+ const commitRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/commits/${headSha}`, { headers });
124
+ if (!commitRes.ok) return { ok: false, error: `Failed to get commit: ${commitRes.status}` };
125
+ const { tree: { sha: baseSha } } = await commitRes.json();
126
+ const treeRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees`, {
127
+ method: "POST",
128
+ headers,
129
+ body: JSON.stringify({ base_tree: baseSha, tree: files.map((f) => ({ path: f.path, mode: "100644", type: "blob", content: f.content })) })
130
+ });
131
+ if (!treeRes.ok) return { ok: false, error: `Failed to create tree: ${treeRes.status}` };
132
+ const { sha: treeSha } = await treeRes.json();
133
+ const newCommitRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/commits`, {
134
+ method: "POST",
135
+ headers,
136
+ body: JSON.stringify({ tree: treeSha, parents: [headSha], message })
137
+ });
138
+ if (!newCommitRes.ok) return { ok: false, error: `Failed to create commit: ${newCommitRes.status}` };
139
+ const { sha: newSha } = await newCommitRes.json();
140
+ const updateRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`, {
141
+ method: "PATCH",
142
+ headers,
143
+ body: JSON.stringify({ sha: newSha })
144
+ });
145
+ if (!updateRes.ok) return { ok: false, error: `Failed to update ref: ${updateRes.status}` };
146
+ return { ok: true, sha: newSha };
147
+ } catch (error) {
148
+ return { ok: false, error: error instanceof Error ? error.message : "Commit failed" };
149
+ }
150
+ }
151
+ export {
152
+ POST
153
+ };
@@ -0,0 +1,13 @@
1
+ import { APIRoute } from 'astro';
2
+
3
+ /**
4
+ * POST /api/setzkasten/catalog/export
5
+ *
6
+ * Exports a managed section as a `.setzkasten-template` JSON string.
7
+ * The caller can save this to a file or upload it to a shared catalog.
8
+ *
9
+ * Body: { sectionKey, owner?, repo?, branch?, contentPath? }
10
+ */
11
+ declare const POST: APIRoute;
12
+
13
+ export { POST };
@@ -0,0 +1,71 @@
1
+ import {
2
+ prefixPath,
3
+ resolveStorageConfigForRequest
4
+ } from "../chunk-6UIKVKED.js";
5
+ import {
6
+ resolveGitHubTokenForRequest
7
+ } from "../chunk-NKDATSPA.js";
8
+
9
+ // src/api-routes/catalog-export.ts
10
+ import { exportTemplate } from "@setzkasten-cms/catalog";
11
+ var POST = async ({ request, cookies }) => {
12
+ const session = cookies.get("setzkasten_session")?.value;
13
+ if (!session) return new Response("Unauthorized", { status: 401 });
14
+ const tokenResult = await resolveGitHubTokenForRequest(request);
15
+ if (!tokenResult.ok) {
16
+ return new Response(tokenResult.error.message, { status: 500 });
17
+ }
18
+ const githubToken = tokenResult.value;
19
+ try {
20
+ const body = await request.json();
21
+ if (!body.sectionKey) {
22
+ return Response.json({ error: "sectionKey is required" }, { status: 400 });
23
+ }
24
+ const storage = await resolveStorageConfigForRequest(request, body);
25
+ if (!storage) return Response.json({ error: "Could not resolve owner/repo" }, { status: 400 });
26
+ const { owner, repo, branch, projectPrefix } = storage;
27
+ const serverConfig = globalThis.__SETZKASTEN_CONFIG__;
28
+ const fullConfig = globalThis.__SETZKASTEN_FULL_CONFIG__;
29
+ const contentPath = body.contentPath || serverConfig?.storage?.contentPath || "content";
30
+ const { sectionKey } = body;
31
+ const sectionJsonPath = prefixPath(`${contentPath}/_sections/${sectionKey}.json`, projectPrefix);
32
+ const contentRaw = await fetchFileContent(owner, repo, branch, sectionJsonPath, githubToken);
33
+ if (!contentRaw) return Response.json({ error: `Section content not found: ${sectionKey}` }, { status: 404 });
34
+ const content = JSON.parse(contentRaw);
35
+ const sectionDef = findSectionDef(fullConfig, sectionKey);
36
+ if (!sectionDef) {
37
+ return Response.json({ error: `Section definition not found for key: ${sectionKey}` }, { status: 404 });
38
+ }
39
+ const templateJson = exportTemplate(sectionKey, sectionDef, content);
40
+ return Response.json({ success: true, template: templateJson });
41
+ } catch (error) {
42
+ console.error("[setzkasten] catalog-export error:", error);
43
+ return Response.json(
44
+ { error: error instanceof Error ? error.message : "Export failed" },
45
+ { status: 500 }
46
+ );
47
+ }
48
+ };
49
+ function findSectionDef(fullConfig, sectionKey) {
50
+ if (!fullConfig?.products) return null;
51
+ for (const product of Object.values(fullConfig.products)) {
52
+ if (product?.sections?.[sectionKey]) return product.sections[sectionKey];
53
+ }
54
+ return null;
55
+ }
56
+ async function fetchFileContent(owner, repo, branch, path, token) {
57
+ try {
58
+ const res = await fetch(
59
+ `https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`,
60
+ { headers: { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" } }
61
+ );
62
+ if (!res.ok) return null;
63
+ const data = await res.json();
64
+ return data.encoding === "base64" ? Buffer.from(data.content, "base64").toString("utf-8") : data.content;
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+ export {
70
+ POST
71
+ };
@@ -0,0 +1,41 @@
1
+ import * as _setzkasten_cms_catalog from '@setzkasten-cms/catalog';
2
+
3
+ /**
4
+ * Pure helper functions for catalog API routes.
5
+ * No GitHub API, no Astro runtime — fully unit-testable.
6
+ */
7
+ /** Returns the full catalog list (all built-in templates) for the API response. */
8
+ declare function buildCatalogResponse(): _setzkasten_cms_catalog.TemplateFile[];
9
+ interface CatalogAddBody {
10
+ templateName?: unknown;
11
+ pageKey?: unknown;
12
+ sectionKey?: unknown;
13
+ [key: string]: unknown;
14
+ }
15
+ /**
16
+ * Validates the request body for POST /api/setzkasten/catalog/add.
17
+ * Throws with a descriptive message on any validation error.
18
+ */
19
+ declare function validateCatalogAddBody(body: CatalogAddBody): {
20
+ templateName: string;
21
+ pageKey: string;
22
+ sectionKey?: string;
23
+ };
24
+ interface CatalogAddCommitOpts {
25
+ contentPath: string;
26
+ projectPrefix: string;
27
+ pageKey: string;
28
+ sectionKey: string;
29
+ templateName: string;
30
+ pageConfigPath: string;
31
+ }
32
+ interface CatalogAddCommitPaths {
33
+ sectionJsonPath: string;
34
+ pageConfigPath: string;
35
+ }
36
+ /**
37
+ * Builds the file paths that need to be committed when adding a catalog template to a page.
38
+ */
39
+ declare function buildCatalogAddCommit(opts: CatalogAddCommitOpts): CatalogAddCommitPaths;
40
+
41
+ export { type CatalogAddBody, type CatalogAddCommitOpts, type CatalogAddCommitPaths, buildCatalogAddCommit, buildCatalogResponse, validateCatalogAddBody };