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