@setzkasten-cms/astro-admin 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ buildCatalogAddCommit,
3
+ buildCatalogResponse,
4
+ validateCatalogAddBody
5
+ } from "../chunk-GRG3LNKH.js";
6
+ import "../chunk-6UIKVKED.js";
7
+ export {
8
+ buildCatalogAddCommit,
9
+ buildCatalogResponse,
10
+ validateCatalogAddBody
11
+ };
@@ -0,0 +1,11 @@
1
+ import { APIRoute } from 'astro';
2
+
3
+ /**
4
+ * GET /api/setzkasten/catalog
5
+ *
6
+ * Returns all available catalog templates (built-in registry).
7
+ * No authentication required — catalog is read-only metadata.
8
+ */
9
+ declare const GET: APIRoute;
10
+
11
+ export { GET };
@@ -0,0 +1,12 @@
1
+ import {
2
+ buildCatalogResponse
3
+ } from "../chunk-GRG3LNKH.js";
4
+ import "../chunk-6UIKVKED.js";
5
+
6
+ // src/api-routes/catalog-list.ts
7
+ var GET = async () => {
8
+ return Response.json({ templates: buildCatalogResponse() });
9
+ };
10
+ export {
11
+ GET
12
+ };
@@ -0,0 +1,12 @@
1
+ import { APIRoute } from 'astro';
2
+
3
+ /**
4
+ * GET /api/setzkasten/config
5
+ *
6
+ * Returns the full SetzKastenConfig as JSON. Fields from GlobalConfig
7
+ * (stored in _global_config.json) are merged over the static config so
8
+ * admins can change theme and Firebase settings without a code deployment.
9
+ */
10
+ declare const GET: APIRoute;
11
+
12
+ export { GET };
@@ -0,0 +1,43 @@
1
+ import {
2
+ readGlobalConfig
3
+ } from "../chunk-AM4DZXXM.js";
4
+ import "../chunk-INIWFKQ3.js";
5
+ import "../chunk-6UIKVKED.js";
6
+ import "../chunk-5PIMDP4N.js";
7
+ import "../chunk-45ARVNT3.js";
8
+ import "../chunk-NKDATSPA.js";
9
+ import "../chunk-TJNJKPUL.js";
10
+ import "../chunk-KH22FJO5.js";
11
+
12
+ // src/api-routes/config.ts
13
+ var GET = async () => {
14
+ const config = globalThis.__SETZKASTEN_FULL_CONFIG__ ?? {};
15
+ const ssrConfig = globalThis.__SETZKASTEN_CONFIG__;
16
+ const globalCfg = await readGlobalConfig().catch(() => null);
17
+ const staticTheme = config.theme ?? {};
18
+ const globalTheme = globalCfg?.theme ?? {};
19
+ const result = {
20
+ // Default fallback when no config is injected at build time. Real
21
+ // values are spread from `config` below.
22
+ storage: { kind: "local" },
23
+ auth: { providers: ["github"] },
24
+ products: {},
25
+ collections: {},
26
+ ...config,
27
+ // Global config theme overrides static config theme field by field
28
+ theme: { ...staticTheme, ...globalTheme },
29
+ // Include storage params so the client can create ProxyContentRepository
30
+ _storage: ssrConfig?.storage ?? void 0,
31
+ _hasGitHub: ssrConfig?.hasGitHub ?? false,
32
+ _hasGoogle: ssrConfig?.hasGoogle ?? false,
33
+ // SetzKastenLogin Firebase config (present only when license is valid)
34
+ _firebaseConfig: globalCfg?.firebaseConfig ?? null
35
+ };
36
+ return new Response(JSON.stringify(result), {
37
+ status: 200,
38
+ headers: { "Content-Type": "application/json" }
39
+ });
40
+ };
41
+ export {
42
+ GET
43
+ };
@@ -0,0 +1,14 @@
1
+ import { APIRoute } from 'astro';
2
+
3
+ /**
4
+ * Triggers the configured deploy hook URL after a content commit.
5
+ * Called by the CMS UI after every successful GitHub commit.
6
+ *
7
+ * The deploy hook URL is set via:
8
+ * setzkasten({ deployHook: { url: 'https://api.vercel.com/v1/integrations/deploy/...' } })
9
+ *
10
+ * Compatible with Vercel, Netlify, Cloudflare Pages and any URL that accepts a POST.
11
+ */
12
+ declare const POST: APIRoute;
13
+
14
+ export { POST };
@@ -0,0 +1,52 @@
1
+ // src/api-routes/deploy-hook.ts
2
+ var POST = async ({ cookies }) => {
3
+ const session = cookies.get("setzkasten_session")?.value;
4
+ if (!session) {
5
+ return new Response("Unauthorized", { status: 401 });
6
+ }
7
+ const config = globalThis.__SETZKASTEN_CONFIG__;
8
+ if (!config?.deployHook?.url) {
9
+ return new Response(JSON.stringify({ skipped: true, reason: "Kein deployHook konfiguriert" }), {
10
+ status: 200,
11
+ headers: { "Content-Type": "application/json" }
12
+ });
13
+ }
14
+ const { url, secret } = config.deployHook;
15
+ try {
16
+ const headers = {
17
+ "Content-Type": "application/json",
18
+ "User-Agent": "setzkasten-cms"
19
+ };
20
+ if (secret) {
21
+ headers["X-Setzkasten-Secret"] = secret;
22
+ }
23
+ const response = await fetch(url, {
24
+ method: "POST",
25
+ headers,
26
+ body: JSON.stringify({
27
+ event: "content.commit",
28
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
29
+ })
30
+ });
31
+ if (!response.ok) {
32
+ console.warn(`[setzkasten] Deploy hook antwortete mit ${response.status}: ${url}`);
33
+ return new Response(
34
+ JSON.stringify({ ok: false, status: response.status }),
35
+ { status: 200, headers: { "Content-Type": "application/json" } }
36
+ );
37
+ }
38
+ return new Response(JSON.stringify({ ok: true }), {
39
+ status: 200,
40
+ headers: { "Content-Type": "application/json" }
41
+ });
42
+ } catch (error) {
43
+ console.error("[setzkasten] Deploy hook fehlgeschlagen:", error);
44
+ return new Response(
45
+ JSON.stringify({ ok: false, error: String(error) }),
46
+ { status: 200, headers: { "Content-Type": "application/json" } }
47
+ );
48
+ }
49
+ };
50
+ export {
51
+ POST
52
+ };
@@ -0,0 +1,29 @@
1
+ import { APIRoute } from 'astro';
2
+ import { ContentEditorConfig } from '@setzkasten-cms/core';
3
+
4
+ declare const GET: APIRoute;
5
+ declare const PUT: APIRoute;
6
+ declare function readEditorsFile(owner: string, repo: string, branch: string, contentPath: string, token: string): Promise<ContentEditorConfig[] | null>;
7
+ /**
8
+ * Discriminated result for the editors-file fetch. Lets callers decide the
9
+ * fail-mode policy: the auth-guard wants to ALLOW when the file is genuinely
10
+ * absent (no restrictions configured) but DENY when the fetch errors out.
11
+ * The basic readEditorsFile() above returns null for both cases, which is
12
+ * unsafe for authorization checks.
13
+ *
14
+ * Caller is responsible for caching — this function never reads from or
15
+ * writes to the shared cache, because caching an "error" state would
16
+ * silently extend privilege-escalation windows.
17
+ */
18
+ type EditorsStatus = {
19
+ kind: 'absent';
20
+ } | {
21
+ kind: 'present';
22
+ editors: ContentEditorConfig[];
23
+ } | {
24
+ kind: 'error';
25
+ message: string;
26
+ };
27
+ declare function readEditorsFileStatus(owner: string, repo: string, branch: string, contentPath: string, token: string): Promise<EditorsStatus>;
28
+
29
+ export { type EditorsStatus, GET, PUT, readEditorsFile, readEditorsFileStatus };
@@ -0,0 +1,18 @@
1
+ import {
2
+ GET,
3
+ PUT,
4
+ readEditorsFile,
5
+ readEditorsFileStatus
6
+ } from "../chunk-INIWFKQ3.js";
7
+ import "../chunk-6UIKVKED.js";
8
+ import "../chunk-5PIMDP4N.js";
9
+ import "../chunk-45ARVNT3.js";
10
+ import "../chunk-NKDATSPA.js";
11
+ import "../chunk-TJNJKPUL.js";
12
+ import "../chunk-KH22FJO5.js";
13
+ export {
14
+ GET,
15
+ PUT,
16
+ readEditorsFile,
17
+ readEditorsFileStatus
18
+ };
@@ -0,0 +1,12 @@
1
+ import { APIRoute } from 'astro';
2
+
3
+ /**
4
+ * Server-side proxy for GitHub API calls.
5
+ * The GitHub App token stays server-side (never exposed to browser).
6
+ *
7
+ * Client calls: POST /api/setzkasten/github/repos/{owner}/{repo}/...
8
+ * Proxy forwards to: https://api.github.com/repos/{owner}/{repo}/...
9
+ */
10
+ declare const ALL: APIRoute;
11
+
12
+ export { ALL };
@@ -0,0 +1,82 @@
1
+ import {
2
+ resolveGitHubTokenForRequest
3
+ } from "../chunk-NKDATSPA.js";
4
+
5
+ // src/api-routes/github-proxy.ts
6
+ import { writeFile } from "fs/promises";
7
+ import { join } from "path";
8
+ var ALL = async ({ params, request, cookies }) => {
9
+ const session = cookies.get("setzkasten_session")?.value;
10
+ if (!session) {
11
+ return new Response("Unauthorized", { status: 401 });
12
+ }
13
+ const githubPath = params.path;
14
+ if (!githubPath) {
15
+ return new Response("Missing path", { status: 400 });
16
+ }
17
+ const tokenResult = await resolveGitHubTokenForRequest(request);
18
+ if (!tokenResult.ok) {
19
+ return new Response(tokenResult.error.message, { status: 500 });
20
+ }
21
+ const githubToken = tokenResult.value;
22
+ const githubUrl = `https://api.github.com/${githubPath}`;
23
+ try {
24
+ const headers = {
25
+ Authorization: `Bearer ${githubToken}`,
26
+ Accept: "application/vnd.github+json",
27
+ "X-GitHub-Api-Version": "2022-11-28"
28
+ };
29
+ const contentType = request.headers.get("content-type");
30
+ if (contentType) {
31
+ headers["Content-Type"] = contentType;
32
+ }
33
+ const body = request.method !== "GET" && request.method !== "HEAD" ? await request.text() : void 0;
34
+ const response = await fetch(githubUrl, {
35
+ method: request.method,
36
+ headers,
37
+ body
38
+ });
39
+ const responseHeaders = new Headers();
40
+ responseHeaders.set("Content-Type", response.headers.get("content-type") ?? "application/json");
41
+ const rateLimitHeaders = [
42
+ "x-ratelimit-limit",
43
+ "x-ratelimit-remaining",
44
+ "x-ratelimit-reset"
45
+ ];
46
+ for (const header of rateLimitHeaders) {
47
+ const value = response.headers.get(header);
48
+ if (value) responseHeaders.set(header, value);
49
+ }
50
+ const etag = response.headers.get("etag");
51
+ if (etag) responseHeaders.set("etag", etag);
52
+ const responseText = await response.text();
53
+ if (request.method === "PUT" && response.ok && body) {
54
+ try {
55
+ const repoRoot = globalThis.__SETZKASTEN_CONFIG__?.repoRoot;
56
+ if (repoRoot) {
57
+ const contentsMatch = githubPath.match(/^repos\/[^/]+\/[^/]+\/contents\/(.+)$/);
58
+ if (contentsMatch) {
59
+ const filePath = contentsMatch[1];
60
+ const parsed = JSON.parse(body);
61
+ if (parsed.content) {
62
+ const decoded = Buffer.from(parsed.content.replace(/\s/g, ""), "base64").toString("utf-8");
63
+ await writeFile(join(repoRoot, filePath), decoded, "utf-8").catch(() => {
64
+ });
65
+ }
66
+ }
67
+ }
68
+ } catch {
69
+ }
70
+ }
71
+ return new Response(responseText, {
72
+ status: response.status,
73
+ headers: responseHeaders
74
+ });
75
+ } catch (error) {
76
+ console.error("[setzkasten] GitHub proxy error:", error);
77
+ return new Response("GitHub API request failed", { status: 502 });
78
+ }
79
+ };
80
+ export {
81
+ ALL
82
+ };
@@ -0,0 +1,20 @@
1
+ import { APIRoute } from 'astro';
2
+
3
+ interface GlobalConfig {
4
+ firebaseConfig?: {
5
+ apiKey: string;
6
+ authDomain: string;
7
+ projectId: string;
8
+ };
9
+ theme?: {
10
+ primaryColor?: string;
11
+ brandName?: string;
12
+ logo?: string;
13
+ };
14
+ }
15
+ declare const GET: APIRoute;
16
+ declare const PUT: APIRoute;
17
+ declare function readGlobalConfig(): Promise<GlobalConfig | null>;
18
+ declare function writeGlobalConfig(config: GlobalConfig): Promise<void>;
19
+
20
+ export { GET, type GlobalConfig, PUT, readGlobalConfig, writeGlobalConfig };
@@ -0,0 +1,19 @@
1
+ import {
2
+ GET,
3
+ PUT,
4
+ readGlobalConfig,
5
+ writeGlobalConfig
6
+ } from "../chunk-AM4DZXXM.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
+ export {
15
+ GET,
16
+ PUT,
17
+ readGlobalConfig,
18
+ writeGlobalConfig
19
+ };
@@ -0,0 +1,22 @@
1
+ import { APIRoute } from 'astro';
2
+
3
+ /**
4
+ * POST /api/setzkasten/history/rollback
5
+ *
6
+ * Body: { path, sha, expectedHeadSha? }
7
+ *
8
+ * Restores `path` to the contents from `sha` by writing a new commit
9
+ * (no `git revert` — JSON content is set wholesale). The original SHA
10
+ * stays in history so users can roll forward again.
11
+ *
12
+ * Conflict semantics: the client passes the SHA they currently render in
13
+ * the file picker. If the file's HEAD has moved between page-load and the
14
+ * rollback click, we return 409 with `code: 'head-moved'` so the UI can
15
+ * tell the user to refresh.
16
+ *
17
+ * Admin-only — editors can edit, but rollback is destructive enough to
18
+ * warrant the audit-log control.
19
+ */
20
+ declare const POST: APIRoute;
21
+
22
+ export { POST };
@@ -0,0 +1,111 @@
1
+ import {
2
+ parseSession,
3
+ requireAdmin
4
+ } from "../chunk-INIWFKQ3.js";
5
+ import {
6
+ resolveStorageConfigForRequest
7
+ } from "../chunk-6UIKVKED.js";
8
+ import "../chunk-5PIMDP4N.js";
9
+ import {
10
+ invalidateCache
11
+ } from "../chunk-45ARVNT3.js";
12
+ import {
13
+ resolveGitHubTokenForRequest
14
+ } from "../chunk-NKDATSPA.js";
15
+ import "../chunk-TJNJKPUL.js";
16
+ import {
17
+ withTrailers
18
+ } from "../chunk-KH22FJO5.js";
19
+
20
+ // src/api-routes/history-rollback.ts
21
+ var POST = async ({ request, cookies }) => {
22
+ const denied = requireAdmin(cookies.get("setzkasten_session")?.value);
23
+ if (denied) return denied;
24
+ const session = parseSession(cookies.get("setzkasten_session")?.value);
25
+ if (!session) return new Response("Unauthorized", { status: 401 });
26
+ let body;
27
+ try {
28
+ body = await request.json();
29
+ } catch {
30
+ return Response.json({ error: "Invalid JSON body" }, { status: 400 });
31
+ }
32
+ const { path, sha, expectedHeadSha } = body;
33
+ if (!path || !sha) {
34
+ return Response.json({ error: "path and sha are required" }, { status: 400 });
35
+ }
36
+ const tokenResult = await resolveGitHubTokenForRequest(request);
37
+ if (!tokenResult.ok) return new Response(tokenResult.error.message, { status: 500 });
38
+ const storage = await resolveStorageConfigForRequest(request);
39
+ if (!storage) {
40
+ return Response.json({ error: "Could not resolve owner/repo" }, { status: 400 });
41
+ }
42
+ const { owner, repo, branch } = storage;
43
+ const headers = {
44
+ Authorization: `Bearer ${tokenResult.value}`,
45
+ Accept: "application/vnd.github+json",
46
+ "X-GitHub-Api-Version": "2022-11-28",
47
+ "Content-Type": "application/json"
48
+ };
49
+ const versionRes = await fetch(
50
+ `https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${sha}`,
51
+ { headers }
52
+ );
53
+ if (versionRes.status === 404) {
54
+ return Response.json(
55
+ { error: "File did not exist at the requested sha", code: "version-not-found" },
56
+ { status: 404 }
57
+ );
58
+ }
59
+ if (!versionRes.ok) {
60
+ return Response.json(
61
+ { error: `Failed to read version: ${versionRes.status}` },
62
+ { status: 502 }
63
+ );
64
+ }
65
+ const versionData = await versionRes.json();
66
+ const targetContent = versionData.encoding === "base64" ? Buffer.from(versionData.content, "base64").toString("utf-8") : versionData.content;
67
+ const headRes = await fetch(
68
+ `https://api.github.com/repos/${owner}/${repo}/contents/${path}?ref=${branch}`,
69
+ { headers }
70
+ );
71
+ let currentSha = null;
72
+ if (headRes.ok) {
73
+ const data = await headRes.json();
74
+ currentSha = data.sha;
75
+ }
76
+ if (expectedHeadSha && currentSha && expectedHeadSha !== currentSha) {
77
+ return Response.json(
78
+ {
79
+ error: "Datei wurde inzwischen ge\xE4ndert. Bitte den Verlauf neu laden.",
80
+ code: "head-moved"
81
+ },
82
+ { status: 409 }
83
+ );
84
+ }
85
+ const shortSha = sha.slice(0, 7);
86
+ const fileName = path.split("/").pop() ?? path;
87
+ const message = withTrailers(
88
+ `revert(${fileName}): rollback to ${shortSha}`,
89
+ session.user.email
90
+ );
91
+ const putBody = {
92
+ message,
93
+ content: Buffer.from(targetContent).toString("base64"),
94
+ branch
95
+ };
96
+ if (currentSha) putBody.sha = currentSha;
97
+ const putRes = await fetch(
98
+ `https://api.github.com/repos/${owner}/${repo}/contents/${path}`,
99
+ { method: "PUT", headers, body: JSON.stringify(putBody) }
100
+ );
101
+ if (!putRes.ok) {
102
+ const text = await putRes.text();
103
+ return Response.json({ error: `Rollback write failed: ${text}` }, { status: 502 });
104
+ }
105
+ invalidateCache(`history:${owner}/${repo}:${branch}:${path}:head`);
106
+ const putData = await putRes.json();
107
+ return Response.json({ ok: true, commitSha: putData.commit.sha });
108
+ };
109
+ export {
110
+ POST
111
+ };
@@ -0,0 +1,11 @@
1
+ import { APIRoute } from 'astro';
2
+
3
+ /**
4
+ * GET /api/setzkasten/history/version?path=<file>&sha=<commit-sha>
5
+ *
6
+ * Returns the file content at a specific commit (for diff rendering).
7
+ * Cached per (path, sha) for 5 minutes — historical content is immutable.
8
+ */
9
+ declare const GET: APIRoute;
10
+
11
+ export { GET };
@@ -0,0 +1,57 @@
1
+ import {
2
+ requireAdmin
3
+ } from "../chunk-INIWFKQ3.js";
4
+ import {
5
+ resolveStorageConfigForRequest
6
+ } from "../chunk-6UIKVKED.js";
7
+ import "../chunk-5PIMDP4N.js";
8
+ import {
9
+ cachedFetch
10
+ } from "../chunk-45ARVNT3.js";
11
+ import {
12
+ resolveGitHubTokenForRequest
13
+ } from "../chunk-NKDATSPA.js";
14
+ import "../chunk-TJNJKPUL.js";
15
+ import "../chunk-KH22FJO5.js";
16
+
17
+ // src/api-routes/history-version.ts
18
+ var GET = async ({ request, url, cookies }) => {
19
+ const denied = requireAdmin(cookies.get("setzkasten_session")?.value);
20
+ if (denied) return denied;
21
+ const path = url.searchParams.get("path");
22
+ const sha = url.searchParams.get("sha");
23
+ if (!path || !sha) {
24
+ return Response.json({ error: "Missing required `path` or `sha`." }, { status: 400 });
25
+ }
26
+ const tokenResult = await resolveGitHubTokenForRequest(request);
27
+ if (!tokenResult.ok) return new Response(tokenResult.error.message, { status: 500 });
28
+ const storage = await resolveStorageConfigForRequest(request);
29
+ if (!storage) {
30
+ return Response.json({ error: "Could not resolve owner/repo" }, { status: 400 });
31
+ }
32
+ const { owner, repo } = storage;
33
+ const cacheKey = `history-version:${owner}/${repo}:${path}:${sha}`;
34
+ const result = await cachedFetch(cacheKey, 5 * 6e4, async () => {
35
+ const u = new URL(
36
+ `https://api.github.com/repos/${owner}/${repo}/contents/${path}`
37
+ );
38
+ u.searchParams.set("ref", sha);
39
+ const res = await fetch(u, {
40
+ headers: {
41
+ Authorization: `Bearer ${tokenResult.value}`,
42
+ Accept: "application/vnd.github+json",
43
+ "X-GitHub-Api-Version": "2022-11-28"
44
+ }
45
+ });
46
+ if (res.status === 404) return { ok: false, status: 404, error: "File not found at given sha" };
47
+ if (!res.ok) return { ok: false, status: 502, error: `GitHub returned ${res.status}` };
48
+ const data = await res.json();
49
+ const raw = data.encoding === "base64" ? Buffer.from(data.content, "base64").toString("utf-8") : data.content;
50
+ return { ok: true, value: { content: raw, sha: data.sha } };
51
+ });
52
+ if (!result.ok) return Response.json({ error: result.error }, { status: result.status });
53
+ return Response.json(result.value);
54
+ };
55
+ export {
56
+ GET
57
+ };
@@ -0,0 +1,13 @@
1
+ import { APIRoute } from 'astro';
2
+
3
+ /**
4
+ * GET /api/setzkasten/history?path=<contentPath>&before=<sha>
5
+ *
6
+ * Returns up to 5 most recent commits affecting the given file. Pagination
7
+ * via `before=<sha>` returns 10 more older commits — clients call this on
8
+ * "Mehr laden". Admin-only — editors can read content but not the audit
9
+ * trail (and certainly not roll back).
10
+ */
11
+ declare const GET: APIRoute;
12
+
13
+ export { GET };
@@ -0,0 +1,85 @@
1
+ import {
2
+ requireAdmin
3
+ } from "../chunk-INIWFKQ3.js";
4
+ import {
5
+ resolveStorageConfigForRequest
6
+ } from "../chunk-6UIKVKED.js";
7
+ import "../chunk-5PIMDP4N.js";
8
+ import {
9
+ cachedFetch
10
+ } from "../chunk-45ARVNT3.js";
11
+ import {
12
+ resolveGitHubTokenForRequest
13
+ } from "../chunk-NKDATSPA.js";
14
+ import "../chunk-TJNJKPUL.js";
15
+ import "../chunk-KH22FJO5.js";
16
+
17
+ // src/api-routes/history.ts
18
+ import { parseCoAuthorTrailers } from "@setzkasten-cms/core";
19
+ var GET = async ({ request, url, cookies }) => {
20
+ const denied = requireAdmin(cookies.get("setzkasten_session")?.value);
21
+ if (denied) return denied;
22
+ const path = url.searchParams.get("path");
23
+ const before = url.searchParams.get("before");
24
+ if (!path) {
25
+ return Response.json({ error: "Missing required `path` parameter." }, { status: 400 });
26
+ }
27
+ const tokenResult = await resolveGitHubTokenForRequest(request);
28
+ if (!tokenResult.ok) return new Response(tokenResult.error.message, { status: 500 });
29
+ const storage = await resolveStorageConfigForRequest(request);
30
+ if (!storage) {
31
+ return Response.json({ error: "Could not resolve owner/repo" }, { status: 400 });
32
+ }
33
+ const { owner, repo, branch } = storage;
34
+ const perPage = before ? 10 : 5;
35
+ const cacheKey = `history:${owner}/${repo}:${branch}:${path}:${before ?? "head"}`;
36
+ const commits = await cachedFetch(
37
+ cacheKey,
38
+ 6e4,
39
+ () => fetchCommits(owner, repo, branch, path, perPage, before, tokenResult.value)
40
+ );
41
+ if (!commits.ok) {
42
+ return Response.json({ error: commits.error }, { status: commits.status });
43
+ }
44
+ return Response.json({ commits: commits.value });
45
+ };
46
+ async function fetchCommits(owner, repo, branch, path, perPage, before, token) {
47
+ const sha = before ?? branch;
48
+ const u = new URL(`https://api.github.com/repos/${owner}/${repo}/commits`);
49
+ u.searchParams.set("path", path);
50
+ u.searchParams.set("sha", sha);
51
+ u.searchParams.set("per_page", String(before ? perPage + 1 : perPage));
52
+ const res = await fetch(u, {
53
+ headers: {
54
+ Authorization: `Bearer ${token}`,
55
+ Accept: "application/vnd.github+json",
56
+ "X-GitHub-Api-Version": "2022-11-28"
57
+ }
58
+ });
59
+ if (res.status === 404) return { ok: true, value: [] };
60
+ if (!res.ok) {
61
+ return { ok: false, status: 502, error: `GitHub returned ${res.status}` };
62
+ }
63
+ const data = await res.json();
64
+ const start = before ? 1 : 0;
65
+ const slice = data.slice(start, start + perPage);
66
+ const commits = slice.map((c) => {
67
+ const [firstLine, ...rest] = c.commit.message.split("\n");
68
+ const body = rest.join("\n");
69
+ return {
70
+ sha: c.sha,
71
+ shortSha: c.sha.slice(0, 7),
72
+ authoredAt: c.commit.author.date,
73
+ authorName: c.commit.author.name,
74
+ authorEmail: c.commit.author.email,
75
+ authorAvatarUrl: c.author?.avatar_url,
76
+ coAuthors: parseCoAuthorTrailers(body),
77
+ message: firstLine ?? "",
78
+ body
79
+ };
80
+ });
81
+ return { ok: true, value: commits };
82
+ }
83
+ export {
84
+ GET
85
+ };