@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.
- package/dist/api-routes/_auth-guard.d.ts +47 -0
- package/dist/api-routes/_auth-guard.js +18 -0
- package/dist/api-routes/_commit-trailers.d.ts +8 -0
- package/dist/api-routes/_commit-trailers.js +8 -0
- package/dist/api-routes/_feature-gate.d.ts +23 -0
- package/dist/api-routes/_feature-gate.js +7 -0
- package/dist/api-routes/_github-cache.d.ts +4 -0
- package/dist/api-routes/_github-cache.js +8 -0
- package/dist/api-routes/_github-token.d.ts +27 -0
- package/dist/api-routes/_github-token.js +8 -0
- package/dist/api-routes/_license-tier.d.ts +22 -0
- package/dist/api-routes/_license-tier.js +6 -0
- package/dist/api-routes/_pages-meta-store.d.ts +32 -0
- package/dist/api-routes/_pages-meta-store.js +9 -0
- package/dist/api-routes/_role-resolver.d.ts +15 -0
- package/dist/api-routes/_role-resolver.js +13 -0
- package/dist/api-routes/_session-cookie.d.ts +18 -0
- package/dist/api-routes/_session-cookie.js +6 -0
- package/dist/api-routes/_storage-config.d.ts +60 -0
- package/dist/api-routes/_storage-config.js +10 -0
- package/dist/api-routes/_vercel-origin.d.ts +16 -0
- package/dist/api-routes/_vercel-origin.js +6 -0
- package/dist/api-routes/_webhook-dispatcher.d.ts +13 -0
- package/dist/api-routes/_webhook-dispatcher.js +97 -0
- package/dist/api-routes/_webhook-signing.d.ts +11 -0
- package/dist/api-routes/_webhook-signing.js +6 -0
- package/dist/api-routes/_webhook-status-store.d.ts +19 -0
- package/dist/api-routes/_webhook-status-store.js +10 -0
- package/dist/api-routes/_website-resolver.d.ts +49 -0
- package/dist/api-routes/_website-resolver.js +14 -0
- package/dist/api-routes/_websites-store.d.ts +30 -0
- package/dist/api-routes/_websites-store.js +11 -0
- package/dist/api-routes/asset-proxy.d.ts +12 -0
- package/dist/api-routes/asset-proxy.js +67 -0
- package/dist/api-routes/auth-callback.d.ts +9 -0
- package/dist/api-routes/auth-callback.js +68 -0
- package/dist/api-routes/auth-login.d.ts +11 -0
- package/dist/api-routes/auth-login.js +27 -0
- package/dist/api-routes/auth-logout.d.ts +10 -0
- package/dist/api-routes/auth-logout.js +13 -0
- package/dist/api-routes/auth-session.d.ts +9 -0
- package/dist/api-routes/auth-session.js +31 -0
- package/dist/api-routes/auth-setzkasten-login.d.ts +18 -0
- package/dist/api-routes/auth-setzkasten-login.js +74 -0
- package/dist/api-routes/catalog-add.d.ts +14 -0
- package/dist/api-routes/catalog-add.js +153 -0
- package/dist/api-routes/catalog-export.d.ts +13 -0
- package/dist/api-routes/catalog-export.js +71 -0
- package/dist/api-routes/catalog-helpers.d.ts +41 -0
- package/dist/api-routes/catalog-helpers.js +11 -0
- package/dist/api-routes/catalog-list.d.ts +11 -0
- package/dist/api-routes/catalog-list.js +12 -0
- package/dist/api-routes/config.d.ts +12 -0
- package/dist/api-routes/config.js +43 -0
- package/dist/api-routes/deploy-hook.d.ts +14 -0
- package/dist/api-routes/deploy-hook.js +52 -0
- package/dist/api-routes/editors.d.ts +29 -0
- package/dist/api-routes/editors.js +18 -0
- package/dist/api-routes/github-proxy.d.ts +12 -0
- package/dist/api-routes/github-proxy.js +82 -0
- package/dist/api-routes/global-config.d.ts +20 -0
- package/dist/api-routes/global-config.js +19 -0
- package/dist/api-routes/history-rollback.d.ts +22 -0
- package/dist/api-routes/history-rollback.js +111 -0
- package/dist/api-routes/history-version.d.ts +11 -0
- package/dist/api-routes/history-version.js +57 -0
- package/dist/api-routes/history.d.ts +13 -0
- package/dist/api-routes/history.js +85 -0
- package/dist/api-routes/icons-local.d.ts +28 -0
- package/dist/api-routes/icons-local.js +115 -0
- package/dist/api-routes/init-add-section.d.ts +23 -0
- package/dist/api-routes/init-add-section.js +396 -0
- package/dist/api-routes/init-apply.d.ts +11 -0
- package/dist/api-routes/init-apply.js +266 -0
- package/dist/api-routes/init-migrate.d.ts +16 -0
- package/dist/api-routes/init-migrate.js +205 -0
- package/dist/api-routes/init-scan-page.d.ts +39 -0
- package/dist/api-routes/init-scan-page.js +260 -0
- package/dist/api-routes/init-scan.d.ts +11 -0
- package/dist/api-routes/init-scan.js +128 -0
- package/dist/api-routes/migrate-to-multi.d.ts +26 -0
- package/dist/api-routes/migrate-to-multi.js +188 -0
- package/dist/api-routes/pages.d.ts +39 -0
- package/dist/api-routes/pages.js +88 -0
- package/dist/api-routes/section-add.d.ts +18 -0
- package/dist/api-routes/section-add.js +173 -0
- package/dist/api-routes/section-commit-pending.d.ts +18 -0
- package/dist/api-routes/section-commit-pending.js +207 -0
- package/dist/api-routes/section-delete.d.ts +15 -0
- package/dist/api-routes/section-delete.js +149 -0
- package/dist/api-routes/section-duplicate.d.ts +15 -0
- package/dist/api-routes/section-duplicate.js +143 -0
- package/dist/api-routes/section-management.d.ts +41 -0
- package/dist/api-routes/section-management.js +14 -0
- package/dist/api-routes/section-prepare-copy.d.ts +25 -0
- package/dist/api-routes/section-prepare-copy.js +69 -0
- package/dist/api-routes/section-prepare.d.ts +18 -0
- package/dist/api-routes/section-prepare.js +104 -0
- package/dist/api-routes/setup-github-app-bounce.d.ts +13 -0
- package/dist/api-routes/setup-github-app-bounce.js +45 -0
- package/dist/api-routes/setup-github-app-branches.d.ts +14 -0
- package/dist/api-routes/setup-github-app-branches.js +58 -0
- package/dist/api-routes/setup-github-app-callback.d.ts +15 -0
- package/dist/api-routes/setup-github-app-callback.js +45 -0
- package/dist/api-routes/setup-github-app-installed.d.ts +15 -0
- package/dist/api-routes/setup-github-app-installed.js +33 -0
- package/dist/api-routes/setup-github-app-repos.d.ts +17 -0
- package/dist/api-routes/setup-github-app-repos.js +41 -0
- package/dist/api-routes/setup-github-app.d.ts +15 -0
- package/dist/api-routes/setup-github-app.js +41 -0
- package/dist/api-routes/updater-check.d.ts +10 -0
- package/dist/api-routes/updater-check.js +37 -0
- package/dist/api-routes/updater-register.d.ts +14 -0
- package/dist/api-routes/updater-register.js +71 -0
- package/dist/api-routes/updater-transfer.d.ts +11 -0
- package/dist/api-routes/updater-transfer.js +37 -0
- package/dist/api-routes/updater-unbind.d.ts +17 -0
- package/dist/api-routes/updater-unbind.js +35 -0
- package/dist/api-routes/webhooks-status.d.ts +12 -0
- package/dist/api-routes/webhooks-status.js +22 -0
- package/dist/api-routes/webhooks-test.d.ts +13 -0
- package/dist/api-routes/webhooks-test.js +124 -0
- package/dist/api-routes/webhooks.d.ts +6 -0
- package/dist/api-routes/webhooks.js +148 -0
- package/dist/api-routes/websites-add.d.ts +15 -0
- package/dist/api-routes/websites-add.js +92 -0
- package/dist/api-routes/websites-list.d.ts +12 -0
- package/dist/api-routes/websites-list.js +35 -0
- package/dist/api-routes/websites-remove.d.ts +15 -0
- package/dist/api-routes/websites-remove.js +69 -0
- package/dist/chunk-35S35OIV.js +80 -0
- package/dist/chunk-45ARVNT3.js +25 -0
- package/dist/chunk-5PIMDP4N.js +25 -0
- package/dist/chunk-5ZFTG4BW.js +10 -0
- package/dist/chunk-6UIKVKED.js +51 -0
- package/dist/chunk-737TIZRU.js +9 -0
- package/dist/chunk-AM4DZXXM.js +120 -0
- package/dist/chunk-FXNOTESI.js +87 -0
- package/dist/chunk-GHNK2GFE.js +48 -0
- package/dist/chunk-GRG3LNKH.js +37 -0
- package/dist/chunk-INIWFKQ3.js +236 -0
- package/dist/chunk-JHY6XTLL.js +24 -0
- package/dist/chunk-K22A4ZBS.js +1574 -0
- package/dist/chunk-KH22FJO5.js +17 -0
- package/dist/chunk-NKDATSPA.js +43 -0
- package/dist/chunk-RHJONMLK.js +1267 -0
- package/dist/chunk-TJNJKPUL.js +11 -0
- package/dist/chunk-V6IMPVF3.js +120 -0
- package/dist/chunk-W3QHY5GW.js +19 -0
- package/dist/chunk-ZQDGGWJP.js +43 -0
- package/package.json +249 -53
- package/src/api-routes/__tests__/route-registry.test.ts +7 -1
- package/tsconfig.json +0 -9
|
@@ -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 { 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
|
+
};
|