Move exports/healthcheck to api path
This commit is contained in:
parent
3e8bcbcf43
commit
c8c5b64dc8
5 changed files with 190 additions and 181 deletions
scripts
src/pages
|
@ -1,5 +1,5 @@
|
||||||
import { type ChildProcess, exec, execSync } from "node:child_process";
|
import { type ChildProcess, exec, execSync } from "node:child_process";
|
||||||
import { readdir, mkdir, mkdtemp, writeFile, readFile } from "node:fs/promises";
|
import { readdir, mkdir, mkdtemp, writeFile, readFile, copyFile } from "node:fs/promises";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { join as pathJoin, normalize } from "node:path";
|
import { join as pathJoin, normalize } from "node:path";
|
||||||
import { setTimeout } from "node:timers/promises";
|
import { setTimeout } from "node:timers/promises";
|
||||||
|
@ -26,7 +26,34 @@ function getRTFStyles(rtfSource: string) {
|
||||||
|
|
||||||
const fetchRetry = fetchRetryWrapper(global.fetch);
|
const fetchRetry = fetchRetryWrapper(global.fetch);
|
||||||
|
|
||||||
|
interface AstroApiResponse {
|
||||||
|
story: string;
|
||||||
|
description: Record<string, string>;
|
||||||
|
thumbnail: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLibreOfficeRunning = async () =>
|
||||||
|
new Promise<boolean>((res, rej) => {
|
||||||
|
exec("ps -ax", (err, stdout) => {
|
||||||
|
if (err) {
|
||||||
|
rej(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res(
|
||||||
|
stdout
|
||||||
|
.toLowerCase()
|
||||||
|
.split("\n")
|
||||||
|
.some((line) => line.includes("libreoffice") && line.includes("--writer")),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
async function exportStory(slug: string, options: { outputDir: string }) {
|
async function exportStory(slug: string, options: { outputDir: string }) {
|
||||||
|
/* Check that LibreOffice is not running */
|
||||||
|
if (await isLibreOfficeRunning()) {
|
||||||
|
console.error("LibreOffice cannot be open while this command is running!");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
/* Check that outputDir is valid */
|
/* Check that outputDir is valid */
|
||||||
const outputDir = normalize(options.outputDir);
|
const outputDir = normalize(options.outputDir);
|
||||||
let files: string[];
|
let files: string[];
|
||||||
|
@ -41,17 +68,31 @@ async function exportStory(slug: string, options: { outputDir: string }) {
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
/* Check if Astro development server needs to be spawned */
|
/* Check if Astro development server needs to be spawned */
|
||||||
const healthcheckURL = `http://localhost:4321/healthcheck`;
|
const healthcheckURL = `http://localhost:4321/api/healthcheck`;
|
||||||
let devServerProcess: ChildProcess | null = null;
|
let devServerProcess: ChildProcess | null = null;
|
||||||
try {
|
try {
|
||||||
await fetchRetry(healthcheckURL, { retries: 3, retryDelay: 1000 });
|
const response = await fetchRetry(healthcheckURL, { retries: 3, retryDelay: 1000 });
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
const healthcheck = await response.json();
|
||||||
|
if (!healthcheck.isAlive) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
/* Spawn Astro dev server */
|
/* Spawn Astro dev server */
|
||||||
console.log("Starting Astro development server...");
|
console.log("Starting Astro development server...");
|
||||||
devServerProcess = exec("./node_modules/.bin/astro dev");
|
devServerProcess = exec("./node_modules/.bin/astro dev");
|
||||||
await setTimeout(2000);
|
await setTimeout(2000);
|
||||||
try {
|
try {
|
||||||
await fetchRetry(healthcheckURL, { retries: 5, retryDelay: 2000 });
|
const response = await fetchRetry(healthcheckURL, { retries: 5, retryDelay: 2000 });
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
const healthcheck = await response.json();
|
||||||
|
if (!healthcheck.isAlive) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
console.error("Astro dev server didn't respond in time!");
|
console.error("Astro dev server didn't respond in time!");
|
||||||
devServerProcess && devServerProcess.kill("SIGINT");
|
devServerProcess && devServerProcess.kill("SIGINT");
|
||||||
|
@ -64,35 +105,35 @@ async function exportStory(slug: string, options: { outputDir: string }) {
|
||||||
try {
|
try {
|
||||||
console.log("Getting data from Astro...");
|
console.log("Getting data from Astro...");
|
||||||
|
|
||||||
const exportStoryURL = `http://localhost:4321/stories/export/story/${slug}`;
|
const response = await fetch(`http://localhost:4321/api/export-story/${slug}`);
|
||||||
const exportThumbnailURL = `http://localhost:4321/stories/export/thumbnail/${slug}`;
|
if (!response.ok) {
|
||||||
const exportDescriptionURLs = (website: string) =>
|
throw new Error(`Failed to reach API (status code ${response.status})`);
|
||||||
`http://localhost:4321/stories/export/description/${website}/${slug}`;
|
}
|
||||||
|
const data: AstroApiResponse = await response.json();
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
["eka", "furaffinity", "inkbunny", "sofurry", "weasyl"].map(async (website) => {
|
Object.entries(data.description).map(async ([website, description]) => {
|
||||||
const description = await fetch(exportDescriptionURLs(website));
|
|
||||||
if (!description.ok) {
|
|
||||||
throw new Error(`Failed to get description for "${website}"`);
|
|
||||||
}
|
|
||||||
const descriptionExt = description.headers.get("Content-Type")?.startsWith("text/markdown") ? "md" : "txt";
|
|
||||||
return await writeFile(
|
return await writeFile(
|
||||||
pathJoin(outputDir, `description_${website}.${descriptionExt}`),
|
pathJoin(outputDir, `description_${website}.${website === "weasyl" ? "md" : "txt"}`),
|
||||||
await description.text(),
|
description,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const thumbnail = await fetch(exportThumbnailURL);
|
if (data.thumbnail) {
|
||||||
if (!thumbnail.ok) {
|
if (data.thumbnail.startsWith("/@fs/")) {
|
||||||
throw new Error("Failed to get thumbnail");
|
const thumbnailPath = data.thumbnail
|
||||||
|
.replace(/^\/@fs/, "")
|
||||||
|
.replace(/\?(&?[a-z][a-zA-Z0-9_-]+=[a-zA-Z0-9_-]*)*$/, "");
|
||||||
|
await copyFile(thumbnailPath, pathJoin(outputDir, `thumbnail${thumbnailPath.match(/\.[^.]+$/)![0]}`));
|
||||||
|
} else {
|
||||||
|
const thumbnail = await fetch(data.thumbnail);
|
||||||
|
if (!thumbnail.ok) {
|
||||||
|
throw new Error("Failed to get thumbnail");
|
||||||
|
}
|
||||||
|
const thumbnailExt = thumbnail.headers.get("Content-Type")?.startsWith("image/png") ? "png" : "jpg";
|
||||||
|
await writeFile(pathJoin(outputDir, `thumbnail.${thumbnailExt}`), Buffer.from(await thumbnail.arrayBuffer()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const thumbnailExt = thumbnail.headers.get("Content-Type")?.startsWith("image/png") ? "png" : "jpg";
|
storyText = data.story;
|
||||||
writeFile(pathJoin(outputDir, `thumbnail.${thumbnailExt}`), Buffer.from(await thumbnail.arrayBuffer()));
|
|
||||||
const story = await fetch(exportStoryURL);
|
|
||||||
if (!story.ok) {
|
|
||||||
throw new Error("Failed to get story");
|
|
||||||
}
|
|
||||||
storyText = await story.text();
|
|
||||||
writeFile(pathJoin(outputDir, `${slug}.txt`), storyText);
|
writeFile(pathJoin(outputDir, `${slug}.txt`), storyText);
|
||||||
} finally {
|
} finally {
|
||||||
if (devServerProcess) {
|
if (devServerProcess) {
|
||||||
|
|
|
@ -2,11 +2,19 @@ import { type APIRoute, type GetStaticPaths } from "astro";
|
||||||
import { getCollection, getEntry, type CollectionEntry, getEntries } from "astro:content";
|
import { getCollection, getEntry, type CollectionEntry, getEntries } from "astro:content";
|
||||||
import { marked, type RendererApi } from "marked";
|
import { marked, type RendererApi } from "marked";
|
||||||
import { decode as tinyDecode } from "tiny-decode";
|
import { decode as tinyDecode } from "tiny-decode";
|
||||||
import { type Website } from "../../../../../content/config";
|
import { type Lang, type Website } from "../../../content/config";
|
||||||
|
|
||||||
const WEBSITE_LIST = ["eka", "furaffinity", "inkbunny", "sofurry", "weasyl"] as const satisfies Website[];
|
type DescriptionFormat = "bbcode" | "markdown";
|
||||||
|
|
||||||
type ExportWebsite = typeof WEBSITE_LIST extends ReadonlyArray<infer K> ? K : never;
|
const WEBSITE_LIST = [
|
||||||
|
["eka", "bbcode"],
|
||||||
|
["furaffinity", "bbcode"],
|
||||||
|
["inkbunny", "bbcode"],
|
||||||
|
["sofurry", "bbcode"],
|
||||||
|
["weasyl", "markdown"],
|
||||||
|
] as const satisfies [Website, DescriptionFormat][];
|
||||||
|
|
||||||
|
type ExportWebsite = typeof WEBSITE_LIST extends ReadonlyArray<[infer K, DescriptionFormat]> ? K : never;
|
||||||
|
|
||||||
const bbcodeRenderer: RendererApi = {
|
const bbcodeRenderer: RendererApi = {
|
||||||
strong: (text) => `[b]${text}[/b]`,
|
strong: (text) => `[b]${text}[/b]`,
|
||||||
|
@ -186,12 +194,18 @@ function getLinkForUser(user: CollectionEntry<"users">, website: ExportWebsite):
|
||||||
throw new Error(`No "${website}" link for user "${user.id}" (consider setting preferredLink)`);
|
throw new Error(`No "${website}" link for user "${user.id}" (consider setting preferredLink)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getNameForUser(user: CollectionEntry<"users">, anonymousUser: CollectionEntry<"users">, lang: Lang): string {
|
||||||
|
if (user.data.isAnonymous) {
|
||||||
|
return anonymousUser.data.nameLang[lang] || anonymousUser.data.name;
|
||||||
|
}
|
||||||
|
return user.data.nameLang[lang] || user.data.name;
|
||||||
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
story: CollectionEntry<"stories">;
|
story: CollectionEntry<"stories">;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
website: ExportWebsite;
|
|
||||||
slug: CollectionEntry<"stories">["slug"];
|
slug: CollectionEntry<"stories">["slug"];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -199,19 +213,14 @@ export const getStaticPaths: GetStaticPaths = async () => {
|
||||||
if (import.meta.env.PROD) {
|
if (import.meta.env.PROD) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return (await getCollection("stories"))
|
return (await getCollection("stories")).map((story) => ({
|
||||||
.map((story) =>
|
params: { slug: story.slug } satisfies Params,
|
||||||
WEBSITE_LIST.map((website) => ({
|
props: { story } satisfies Props,
|
||||||
params: { website, slug: story.slug } satisfies Params,
|
}));
|
||||||
props: { story } satisfies Props,
|
|
||||||
})),
|
|
||||||
)
|
|
||||||
.flat();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GET: APIRoute<Props, Params> = async ({ props: { story }, params: { website }, site }) => {
|
export const GET: APIRoute<Props, Params> = async ({ props: { story }, site }) => {
|
||||||
const u = (user: CollectionEntry<"users">) => getLinkForUser(user, website);
|
const { lang } = story.data;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
story.data.copyrightedCharacters &&
|
story.data.copyrightedCharacters &&
|
||||||
"" in story.data.copyrightedCharacters &&
|
"" in story.data.copyrightedCharacters &&
|
||||||
|
@ -236,45 +245,103 @@ export const GET: APIRoute<Props, Params> = async ({ props: { story }, params: {
|
||||||
>,
|
>,
|
||||||
);
|
);
|
||||||
|
|
||||||
let storyDescription = (
|
const description: Record<ExportWebsite, string> = Object.fromEntries(
|
||||||
[
|
await Promise.all(
|
||||||
story.data.description,
|
WEBSITE_LIST.map(async ([website, exportFormat]) => {
|
||||||
`*Word count: ${story.data.wordCount}. ${story.data.contentWarning.trim()}*`,
|
const u = (user: CollectionEntry<"users">) => getLinkForUser(user, website);
|
||||||
"Writing: " + (await getEntries([story.data.authors].flat())).map((author) => u(author)).join(" , "),
|
const storyDescription = (
|
||||||
story.data.requester && "Request for: " + u(await getEntry(story.data.requester)),
|
[
|
||||||
story.data.commissioner && "Commissioned by: " + u(await getEntry(story.data.commissioner)),
|
story.data.description,
|
||||||
...(await Promise.all(
|
`*Word count: ${story.data.wordCount}. ${story.data.contentWarning.trim()}*`,
|
||||||
(Object.keys(charactersPerUser) as CollectionEntry<"users">["id"][]).map(async (id) => {
|
"Writing: " + (await getEntries([story.data.authors].flat())).map((author) => u(author)).join(" , "),
|
||||||
const user = u(await getEntry("users", id));
|
story.data.requester && "Request for: " + u(await getEntry(story.data.requester)),
|
||||||
const characterList = charactersPerUser[id];
|
story.data.commissioner && "Commissioned by: " + u(await getEntry(story.data.commissioner)),
|
||||||
if (characterList[0] == "") {
|
...(await Promise.all(
|
||||||
return `All characters are © ${user}`;
|
(Object.keys(charactersPerUser) as CollectionEntry<"users">["id"][]).map(async (id) => {
|
||||||
} else if (characterList.length > 2) {
|
const user = u(await getEntry("users", id));
|
||||||
return `${characterList.slice(0, characterList.length - 1).join(", ")}, and ${characterList[characterList.length - 1]} are © ${user}`;
|
const characterList = charactersPerUser[id];
|
||||||
} else if (characterList.length > 1) {
|
if (characterList[0] == "") {
|
||||||
return `${characterList[0]} and ${characterList[1]} are © ${user}`;
|
return `All characters are © ${user}`;
|
||||||
}
|
} else if (characterList.length > 2) {
|
||||||
return `${characterList[0]} is © ${user}`;
|
return `${characterList.slice(0, characterList.length - 1).join(", ")}, and ${characterList[characterList.length - 1]} are © ${user}`;
|
||||||
}),
|
} else if (characterList.length > 1) {
|
||||||
)),
|
return `${characterList[0]} and ${characterList[1]} are © ${user}`;
|
||||||
].filter((data) => data) as string[]
|
}
|
||||||
)
|
return `${characterList[0]} is © ${user}`;
|
||||||
.join("\n\n")
|
}),
|
||||||
.replaceAll(
|
)),
|
||||||
/\[([^\]]+)\]\((\.[^\)]+)\)/g,
|
].filter((data) => data) as string[]
|
||||||
(_, group1, group2) => `[${group1}](${new URL(group2, new URL(`/stories/${story.slug}`, site)).toString()})`,
|
)
|
||||||
);
|
.join("\n\n")
|
||||||
const headers = { "Content-Type": "text/markdown; charset=utf-8" };
|
.replaceAll(
|
||||||
const bbcodeExports: ReadonlyArray<ExportWebsite> = ["eka", "furaffinity", "inkbunny", "sofurry"] as const;
|
/\[([^\]]+)\]\((\.[^\)]+)\)/g,
|
||||||
const markdownExport: ReadonlyArray<ExportWebsite> = ["weasyl"] as const;
|
(_, group1, group2) =>
|
||||||
// BBCode exports
|
`[${group1}](${new URL(group2, new URL(`/stories/${story.slug}`, site)).toString()})`,
|
||||||
if (bbcodeExports.includes(website)) {
|
);
|
||||||
storyDescription = tinyDecode(await marked.use({ renderer: bbcodeRenderer }).parse(storyDescription));
|
if (exportFormat === "bbcode") {
|
||||||
headers["Content-Type"] = "text/plain; charset=utf-8";
|
return [
|
||||||
// Markdown exports (no-op)
|
website,
|
||||||
} else if (!markdownExport.includes(website)) {
|
tinyDecode(await marked.use({ renderer: bbcodeRenderer }).parse(storyDescription))
|
||||||
console.log(`Unrecognized ExportWebsite "${website}"`);
|
.replaceAll(/\n\n\n+/g, "\n\n")
|
||||||
return new Response(null, { status: 404 });
|
.trim(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (exportFormat === "markdown") {
|
||||||
|
return [website, storyDescription.replaceAll(/\n\n\n+/g, "\n\n").trim()];
|
||||||
|
}
|
||||||
|
throw new Error(`Unknown exportFormat "${exportFormat}"`);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const anonymousUser = await getEntry("users", "anonymous");
|
||||||
|
const authorsNames = (await getEntries([story.data.authors].flat())).map((author) =>
|
||||||
|
getNameForUser(author, anonymousUser, lang),
|
||||||
|
);
|
||||||
|
const commissioner = story.data.commissioner && (await getEntry(story.data.commissioner));
|
||||||
|
const requester = story.data.requester && (await getEntry(story.data.requester));
|
||||||
|
|
||||||
|
let storyHeader = `${story.data.title}\n`;
|
||||||
|
if (lang === "eng") {
|
||||||
|
let authorsString = `by ${authorsNames[0]}`;
|
||||||
|
if (authorsNames.length > 2) {
|
||||||
|
authorsString += `, ${authorsNames.slice(1, authorsNames.length - 1).join(", ")}, and ${authorsNames[authorsNames.length - 1]}`;
|
||||||
|
} else if (authorsNames.length == 2) {
|
||||||
|
authorsString += ` and ${authorsNames[1]}`;
|
||||||
|
}
|
||||||
|
storyHeader +=
|
||||||
|
`${authorsString}\n` +
|
||||||
|
(commissioner ? `Commissioned by ${getNameForUser(commissioner, anonymousUser, lang)}\n` : "") +
|
||||||
|
(requester ? `Requested by ${getNameForUser(requester, anonymousUser, lang)}\n` : "");
|
||||||
|
} else if (lang === "tok") {
|
||||||
|
let authorsString = "lipu ni li tan ";
|
||||||
|
if (authorsNames.length > 1) {
|
||||||
|
authorsString += `jan ni: ${authorsNames.join(" en ")}`;
|
||||||
|
} else {
|
||||||
|
authorsString += authorsNames[0];
|
||||||
|
}
|
||||||
|
if (commissioner) {
|
||||||
|
throw new Error(`No "commissioner" handler for language "tok"`);
|
||||||
|
}
|
||||||
|
if (requester) {
|
||||||
|
throw new Error(`No "requester" handler for language "tok"`);
|
||||||
|
}
|
||||||
|
storyHeader += `${authorsString}\n`;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unknown language "${lang}"`);
|
||||||
}
|
}
|
||||||
return new Response(`${storyDescription.replaceAll(/\n\n\n+/g, "\n\n").trim()}\n`, { headers });
|
|
||||||
|
const storyText = `${storyHeader}\n===\n\n${story.body.replaceAll("\\*", "*").replaceAll("\\=", "=")}`
|
||||||
|
.replaceAll(/\n\n\n+/g, "\n\n")
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
const headers = { "Content-Type": "application/json; charset=utf-8" };
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
story: storyText,
|
||||||
|
description,
|
||||||
|
thumbnail: story.data.thumbnail ? story.data.thumbnail.src : null,
|
||||||
|
}),
|
||||||
|
{ headers },
|
||||||
|
);
|
||||||
};
|
};
|
|
@ -1,72 +0,0 @@
|
||||||
import { type APIRoute, type GetStaticPaths } from "astro";
|
|
||||||
import { getCollection, getEntry, type CollectionEntry, getEntries } from "astro:content";
|
|
||||||
import { type Lang } from "../../../../content/config";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
story: CollectionEntry<"stories">;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Params = {
|
|
||||||
slug: CollectionEntry<"stories">["slug"];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getStaticPaths: GetStaticPaths = async () => {
|
|
||||||
if (import.meta.env.PROD) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return (await getCollection("stories")).map((story) => ({
|
|
||||||
params: { slug: story.slug } satisfies Params,
|
|
||||||
props: { story } satisfies Props,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
function getNameForUser(user: CollectionEntry<"users">, anonymousUser: CollectionEntry<"users">, lang: Lang): string {
|
|
||||||
if (user.data.isAnonymous) {
|
|
||||||
return anonymousUser.data.nameLang[lang] || anonymousUser.data.name;
|
|
||||||
}
|
|
||||||
return user.data.nameLang[lang] || user.data.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GET: APIRoute<Props, Params> = async ({ props: { story } }) => {
|
|
||||||
const { lang } = story.data;
|
|
||||||
const anonymousUser = await getEntry("users", "anonymous");
|
|
||||||
const authorsNames = (await getEntries([story.data.authors].flat())).map((author) =>
|
|
||||||
getNameForUser(author, anonymousUser, lang),
|
|
||||||
);
|
|
||||||
const commissioner = story.data.commissioner && (await getEntry(story.data.commissioner));
|
|
||||||
const requester = story.data.requester && (await getEntry(story.data.requester));
|
|
||||||
|
|
||||||
let storyHeader = `${story.data.title}\n`;
|
|
||||||
if (lang === "eng") {
|
|
||||||
let authorsString = `by ${authorsNames[0]}`;
|
|
||||||
if (authorsNames.length > 2) {
|
|
||||||
authorsString += `, ${authorsNames.slice(1, authorsNames.length - 1).join(", ")}, and ${authorsNames[authorsNames.length - 1]}`;
|
|
||||||
} else if (authorsNames.length == 2) {
|
|
||||||
authorsString += ` and ${authorsNames[1]}`;
|
|
||||||
}
|
|
||||||
storyHeader +=
|
|
||||||
`${authorsString}\n` +
|
|
||||||
(commissioner ? `Commissioned by ${getNameForUser(commissioner, anonymousUser, lang)}\n` : "") +
|
|
||||||
(requester ? `Requested by ${getNameForUser(requester, anonymousUser, lang)}\n` : "");
|
|
||||||
} else if (lang === "tok") {
|
|
||||||
let authorsString = "lipu ni li tan ";
|
|
||||||
if (authorsNames.length > 1) {
|
|
||||||
authorsString += `jan ni: ${authorsNames.join(" en ")}`;
|
|
||||||
} else {
|
|
||||||
authorsString += authorsNames[0];
|
|
||||||
}
|
|
||||||
if (commissioner) {
|
|
||||||
throw new Error(`No "commissioner" handler for language "tok"`);
|
|
||||||
}
|
|
||||||
if (requester) {
|
|
||||||
throw new Error(`No "requester" handler for language "tok"`);
|
|
||||||
}
|
|
||||||
storyHeader += `${authorsString}\n`;
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unknown language "${lang}"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const storyText = `${storyHeader}\n===\n\n${story.body.replaceAll("\\*", "*").replaceAll("\\=", "=")}`;
|
|
||||||
const headers = { "Content-Type": "text/plain; charset=utf-8" };
|
|
||||||
return new Response(`${storyText.replaceAll(/\n\n\n+/g, "\n\n").trim()}\n`, { headers });
|
|
||||||
};
|
|
|
@ -1,27 +0,0 @@
|
||||||
import { type APIRoute, type GetStaticPaths } from "astro";
|
|
||||||
import { getCollection, type CollectionEntry } from "astro:content";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
story: CollectionEntry<"stories">;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Params = {
|
|
||||||
slug: CollectionEntry<"stories">["slug"];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getStaticPaths: GetStaticPaths = async () => {
|
|
||||||
if (import.meta.env.PROD) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return (await getCollection("stories")).map((story) => ({
|
|
||||||
params: { slug: story.slug } satisfies Params,
|
|
||||||
props: { story } satisfies Props,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GET: APIRoute<Props, Params> = async ({ props: { story }, redirect }) => {
|
|
||||||
if (!story.data.thumbnail) {
|
|
||||||
return new Response(null, { status: 404 });
|
|
||||||
}
|
|
||||||
return redirect(story.data.thumbnail.src);
|
|
||||||
};
|
|
Loading…
Add table
Add a link
Reference in a new issue