Skip to content

Commit 2f6d697

Browse files
committed
node.js setting change
1 parent fa28f1e commit 2f6d697

File tree

3 files changed

+377
-8
lines changed

3 files changed

+377
-8
lines changed

netlify.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
publish = "public"
44

55
[build.environment]
6+
NODE_VERSION = "22"
67
HUGO_VERSION = "0.153.1"
78
HUGO_EXTENDED = "true"
89
HUGO_ENV = "production"

netlify/functions/create-entry.js

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
const GITHUB_API_BASE = "https://api.github.com";
2+
3+
function json(statusCode, body) {
4+
return {
5+
statusCode,
6+
headers: { "Content-Type": "application/json" },
7+
body: JSON.stringify(body),
8+
};
9+
}
10+
11+
function slugify(input) {
12+
return String(input || "")
13+
.toLowerCase()
14+
.normalize("NFKD")
15+
.replace(/[\u0300-\u036f]/g, "")
16+
.replace(/[^a-z0-9]+/g, "-")
17+
.replace(/^-+|-+$/g, "")
18+
.slice(0, 80);
19+
}
20+
21+
function formatDateYYYYMMDD(value) {
22+
const date = new Date(value);
23+
if (Number.isNaN(date.getTime())) {
24+
return null;
25+
}
26+
return date.toISOString().slice(0, 10);
27+
}
28+
29+
function toFrontmatterValue(value) {
30+
const str = String(value);
31+
if (/^[a-zA-Z0-9_./:@+\- ]+$/.test(str) && !str.includes(":")) {
32+
return str;
33+
}
34+
return JSON.stringify(str);
35+
}
36+
37+
function buildBlogMarkdown(payload) {
38+
const lines = [
39+
"---",
40+
`title: ${toFrontmatterValue(payload.title)}`,
41+
`date: ${toFrontmatterValue(payload.date)}`,
42+
`author: ${toFrontmatterValue(payload.author)}`,
43+
];
44+
45+
if (payload.image) {
46+
lines.push(`image: ${toFrontmatterValue(payload.image)}`);
47+
}
48+
49+
lines.push("---", payload.body || "");
50+
return `${lines.join("\n")}\n`;
51+
}
52+
53+
function buildEventMarkdown(payload) {
54+
const lines = [
55+
"---",
56+
`title: ${toFrontmatterValue(payload.title)}`,
57+
`date: ${toFrontmatterValue(payload.date)}`,
58+
`author: ${toFrontmatterValue(payload.author)}`,
59+
`publishdate: ${toFrontmatterValue(payload.publishdate || "2023-03-18")}`,
60+
];
61+
62+
if (payload.location) lines.push(`location: ${toFrontmatterValue(payload.location)}`);
63+
if (payload.time) lines.push(`time: ${toFrontmatterValue(payload.time)}`);
64+
if (payload.organiser) lines.push(`organiser: ${toFrontmatterValue(payload.organiser)}`);
65+
if (payload.image) lines.push(`image: ${toFrontmatterValue(payload.image)}`);
66+
if (typeof payload.show_signup === "boolean") {
67+
lines.push(`show_signup: ${payload.show_signup ? "true" : "false"}`);
68+
}
69+
if (payload.signup_link) lines.push(`signup_link: ${toFrontmatterValue(payload.signup_link)}`);
70+
71+
lines.push("---", payload.body || "");
72+
return `${lines.join("\n")}\n`;
73+
}
74+
75+
async function createFileInRepo({ owner, repo, branch, token, path, content, message }) {
76+
const url = `${GITHUB_API_BASE}/repos/${owner}/${repo}/contents/${encodeURIComponent(path).replace(/%2F/g, "/")}`;
77+
const response = await fetch(url, {
78+
method: "PUT",
79+
headers: {
80+
Authorization: `Bearer ${token}`,
81+
"Content-Type": "application/json",
82+
Accept: "application/vnd.github+json",
83+
"User-Agent": "begrip-custom-admin",
84+
},
85+
body: JSON.stringify({
86+
message,
87+
content: Buffer.from(content, "utf8").toString("base64"),
88+
branch,
89+
}),
90+
});
91+
92+
if (!response.ok) {
93+
const details = await response.text();
94+
throw new Error(`GitHub API ${response.status}: ${details}`);
95+
}
96+
97+
return response.json();
98+
}
99+
100+
exports.handler = async (event) => {
101+
if (event.httpMethod !== "POST") {
102+
return json(405, { error: "Method not allowed" });
103+
}
104+
105+
const adminKey = process.env.CUSTOM_ADMIN_KEY;
106+
if (!adminKey) {
107+
return json(500, { error: "Server misconfigured: CUSTOM_ADMIN_KEY missing" });
108+
}
109+
110+
const requestKey = event.headers["x-admin-key"] || event.headers["X-Admin-Key"];
111+
if (!requestKey || requestKey !== adminKey) {
112+
return json(401, { error: "Unauthorized" });
113+
}
114+
115+
const githubToken = process.env.GITHUB_CONTENTS_TOKEN;
116+
const githubRepo = process.env.GITHUB_REPO;
117+
const githubBranch = process.env.GITHUB_BRANCH || "main";
118+
119+
if (!githubToken || !githubRepo) {
120+
return json(500, { error: "Server misconfigured: missing GitHub env vars" });
121+
}
122+
123+
const [owner, repo] = githubRepo.split("/");
124+
if (!owner || !repo) {
125+
return json(500, { error: "GITHUB_REPO must be in format owner/repo" });
126+
}
127+
128+
let payload;
129+
try {
130+
payload = JSON.parse(event.body || "{}");
131+
} catch {
132+
return json(400, { error: "Invalid JSON" });
133+
}
134+
135+
const type = payload.type;
136+
if (type !== "blog" && type !== "event") {
137+
return json(400, { error: "type must be 'blog' or 'event'" });
138+
}
139+
140+
if (!payload.title || !payload.date || !payload.author || !payload.body) {
141+
return json(400, { error: "title, date, author and body are required" });
142+
}
143+
144+
const datePrefix = formatDateYYYYMMDD(payload.date);
145+
if (!datePrefix) {
146+
return json(400, { error: "Invalid date" });
147+
}
148+
149+
const slug = slugify(payload.slug || payload.title);
150+
if (!slug) {
151+
return json(400, { error: "Could not generate slug" });
152+
}
153+
154+
const folder = type === "blog" ? "content/blogs" : "content/events";
155+
const path = `${folder}/${datePrefix}-${slug}.md`;
156+
157+
const markdown = type === "blog" ? buildBlogMarkdown(payload) : buildEventMarkdown(payload);
158+
const message = `Create ${type}: ${payload.title}`;
159+
160+
try {
161+
await createFileInRepo({
162+
owner,
163+
repo,
164+
branch: githubBranch,
165+
token: githubToken,
166+
path,
167+
content: markdown,
168+
message,
169+
});
170+
171+
return json(200, { ok: true, path });
172+
} catch (err) {
173+
return json(500, { error: "Failed to create content file", details: err.message });
174+
}
175+
};

0 commit comments

Comments
 (0)