Move exports/healthcheck to api path

This commit is contained in:
Bad Manners 2024-03-27 18:53:05 -03:00
parent 3e8bcbcf43
commit c8c5b64dc8
5 changed files with 190 additions and 181 deletions

View file

@ -2,11 +2,19 @@ import { type APIRoute, type GetStaticPaths } from "astro";
import { getCollection, getEntry, type CollectionEntry, getEntries } from "astro:content";
import { marked, type RendererApi } from "marked";
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 = {
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)`);
}
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 = {
story: CollectionEntry<"stories">;
};
type Params = {
website: ExportWebsite;
slug: CollectionEntry<"stories">["slug"];
};
@ -199,19 +213,14 @@ export const getStaticPaths: GetStaticPaths = async () => {
if (import.meta.env.PROD) {
return [];
}
return (await getCollection("stories"))
.map((story) =>
WEBSITE_LIST.map((website) => ({
params: { website, slug: story.slug } satisfies Params,
props: { story } satisfies Props,
})),
)
.flat();
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 }, params: { website }, site }) => {
const u = (user: CollectionEntry<"users">) => getLinkForUser(user, website);
export const GET: APIRoute<Props, Params> = async ({ props: { story }, site }) => {
const { lang } = story.data;
if (
story.data.copyrightedCharacters &&
"" in story.data.copyrightedCharacters &&
@ -236,45 +245,103 @@ export const GET: APIRoute<Props, Params> = async ({ props: { story }, params: {
>,
);
let storyDescription = (
[
story.data.description,
`*Word count: ${story.data.wordCount}. ${story.data.contentWarning.trim()}*`,
"Writing: " + (await getEntries([story.data.authors].flat())).map((author) => u(author)).join(" , "),
story.data.requester && "Request for: " + u(await getEntry(story.data.requester)),
story.data.commissioner && "Commissioned by: " + u(await getEntry(story.data.commissioner)),
...(await Promise.all(
(Object.keys(charactersPerUser) as CollectionEntry<"users">["id"][]).map(async (id) => {
const user = u(await getEntry("users", id));
const characterList = charactersPerUser[id];
if (characterList[0] == "") {
return `All characters are © ${user}`;
} else if (characterList.length > 2) {
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}`;
}
return `${characterList[0]} is © ${user}`;
}),
)),
].filter((data) => data) as string[]
)
.join("\n\n")
.replaceAll(
/\[([^\]]+)\]\((\.[^\)]+)\)/g,
(_, group1, group2) => `[${group1}](${new URL(group2, new URL(`/stories/${story.slug}`, site)).toString()})`,
);
const headers = { "Content-Type": "text/markdown; charset=utf-8" };
const bbcodeExports: ReadonlyArray<ExportWebsite> = ["eka", "furaffinity", "inkbunny", "sofurry"] as const;
const markdownExport: ReadonlyArray<ExportWebsite> = ["weasyl"] as const;
// BBCode exports
if (bbcodeExports.includes(website)) {
storyDescription = tinyDecode(await marked.use({ renderer: bbcodeRenderer }).parse(storyDescription));
headers["Content-Type"] = "text/plain; charset=utf-8";
// Markdown exports (no-op)
} else if (!markdownExport.includes(website)) {
console.log(`Unrecognized ExportWebsite "${website}"`);
return new Response(null, { status: 404 });
const description: Record<ExportWebsite, string> = Object.fromEntries(
await Promise.all(
WEBSITE_LIST.map(async ([website, exportFormat]) => {
const u = (user: CollectionEntry<"users">) => getLinkForUser(user, website);
const storyDescription = (
[
story.data.description,
`*Word count: ${story.data.wordCount}. ${story.data.contentWarning.trim()}*`,
"Writing: " + (await getEntries([story.data.authors].flat())).map((author) => u(author)).join(" , "),
story.data.requester && "Request for: " + u(await getEntry(story.data.requester)),
story.data.commissioner && "Commissioned by: " + u(await getEntry(story.data.commissioner)),
...(await Promise.all(
(Object.keys(charactersPerUser) as CollectionEntry<"users">["id"][]).map(async (id) => {
const user = u(await getEntry("users", id));
const characterList = charactersPerUser[id];
if (characterList[0] == "") {
return `All characters are © ${user}`;
} else if (characterList.length > 2) {
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}`;
}
return `${characterList[0]} is © ${user}`;
}),
)),
].filter((data) => data) as string[]
)
.join("\n\n")
.replaceAll(
/\[([^\]]+)\]\((\.[^\)]+)\)/g,
(_, group1, group2) =>
`[${group1}](${new URL(group2, new URL(`/stories/${story.slug}`, site)).toString()})`,
);
if (exportFormat === "bbcode") {
return [
website,
tinyDecode(await marked.use({ renderer: bbcodeRenderer }).parse(storyDescription))
.replaceAll(/\n\n\n+/g, "\n\n")
.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 },
);
};

View file

@ -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 });
};

View file

@ -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);
};