memorium/lib/crud.ts

116 lines
3.0 KiB
TypeScript

import {
createDocument,
getDocument,
getDocuments,
transformDocument,
} from "@lib/documents.ts";
import { Root } from "https://esm.sh/remark-frontmatter@4.0.1";
import { getThumbhash } from "@lib/cache/image.ts";
import { GenericResource } from "@lib/types.ts";
import { parseRating } from "@lib/helpers.ts";
export async function addThumbnailToResource<T = GenericResource>(
res: T,
): Promise<T> {
const imageUrl = res?.meta?.image;
if (!imageUrl) return res;
const [thumbhash, average] = await getThumbhash({ url: imageUrl });
if (!thumbhash) return res;
return {
...res,
meta: {
...res?.meta,
average: average,
thumbnail: thumbhash,
},
};
}
type SortType = "rating" | "date" | "name" | "author";
function sortFunction<T extends GenericResource>(sortType: SortType) {
return (a: T, b: T) => {
switch (sortType) {
case "rating":
return parseRating(a.meta?.rating || 0) >
parseRating(b.meta?.rating || 0)
? -1
: 1;
case "date":
return (a.meta?.date || 0) > (b.meta?.date || 0) ? -1 : 1;
case "name":
return a.name.localeCompare(b.name);
case "author":
return a.meta?.author?.localeCompare(b.meta?.author || "");
}
};
}
export function createCrud<T extends GenericResource>(
{ prefix, parse, render, hasThumbnails = false }: {
prefix: string;
hasThumbnails?: boolean;
render?: (doc: T) => string;
parse: (doc: string, id: string) => T;
},
) {
function pathFromId(id: string) {
return `${prefix}${id.replaceAll(":", "")}.md`;
}
async function read(id: string) {
const path = pathFromId(id);
const content = await getDocument(path);
const res = parse(content, id);
if (hasThumbnails) {
return addThumbnailToResource(res);
}
return res;
}
function create(id: string, content: string | ArrayBuffer | T) {
const path = pathFromId(id);
if (
typeof content === "string" || content instanceof ArrayBuffer
) {
return createDocument(path, content);
}
if (render) {
return createDocument(path, render(content));
}
throw new Error("No renderer defined for " + prefix + " CRUD");
}
async function update(id: string, updater: (r: Root) => Root) {
const path = pathFromId(id);
const content = await getDocument(path);
const newDoc = transformDocument(content, updater);
await createDocument(path, newDoc);
}
async function readAll({ sort = "rating" }: { sort?: SortType } = {}) {
const allDocuments = await getDocuments();
return (await Promise.all(
allDocuments.filter((d) => {
return d.name.startsWith(prefix) &&
d.contentType === "text/markdown" &&
!d.name.endsWith("index.md");
}).map((doc) => {
const id = doc.name.replace(prefix, "").replace(/\.md$/, "");
return read(id);
}),
)).sort(sortFunction<T>(sort));
}
return {
read,
readAll,
create,
update,
};
}