memorium/lib/crud.ts

150 lines
3.8 KiB
TypeScript
Raw Permalink Normal View History

2023-08-01 17:50:00 +02:00
import {
createDocument,
getDocument,
getDocuments,
transformDocument,
} from "@lib/documents.ts";
import { Root } from "https://esm.sh/remark-frontmatter@4.0.1";
2023-08-29 13:48:52 +02:00
import { GenericResource } from "@lib/types.ts";
import { parseRating } from "@lib/helpers.ts";
import { isLocalImage } from "@lib/string.ts";
import { SILVERBULLET_SERVER } from "@lib/env.ts";
2025-01-06 16:14:29 +01:00
import { imageTable } from "@lib/db/schema.ts";
import { db } from "@lib/db/sqlite.ts";
import { eq } from "drizzle-orm/sql";
2025-01-25 18:57:06 +01:00
import { createCache } from "@lib/cache.ts";
export async function addThumbnailToResource<T extends GenericResource>(
res: T,
): Promise<T> {
if (!res?.meta?.image) return res;
const imageUrl = isLocalImage(res.meta.image)
? `${SILVERBULLET_SERVER}/${res.meta.image}`
: res.meta.image;
const image = await db.select().from(imageTable)
.where(eq(imageTable.url, imageUrl))
.limit(1)
.then((images) => images[0]);
if (image) {
return {
...res,
meta: {
...res.meta,
average: image.average,
thumbnail: image.blurhash,
},
};
}
return res;
}
2023-08-01 17:50:00 +02:00
2023-08-29 13:48:52 +02:00
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":
2025-01-06 16:14:29 +01:00
return a.meta?.author?.localeCompare(b.meta?.author || "") || 0;
default:
return 0;
2023-08-29 13:48:52 +02:00
}
};
}
export function createCrud<T extends GenericResource>(
{ prefix, parse, render, hasThumbnails = false }: {
2023-08-01 17:50:00 +02:00
prefix: string;
hasThumbnails?: boolean;
render?: (doc: T) => string;
2023-08-01 17:50:00 +02:00
parse: (doc: string, id: string) => T;
},
) {
2025-01-25 18:57:06 +01:00
const cache = createCache<T>(`crud/${prefix}`, { expires: 60 * 1000 });
2023-08-01 17:50:00 +02:00
function pathFromId(id: string) {
2023-08-04 22:35:25 +02:00
return `${prefix}${id.replaceAll(":", "")}.md`;
2023-08-01 17:50:00 +02:00
}
async function read(id: string) {
const path = pathFromId(id);
2025-01-06 16:14:29 +01:00
2023-08-01 17:50:00 +02:00
const content = await getDocument(path);
2025-01-18 00:46:05 +01:00
if (!content) {
return;
}
2023-08-19 23:29:39 +02:00
let parsed = parse(content, id);
if (hasThumbnails) {
parsed = await addThumbnailToResource(parsed);
}
2025-01-06 16:14:29 +01:00
const doc = { ...parsed, content };
2025-01-06 16:14:29 +01:00
return doc;
2023-08-01 17:50:00 +02:00
}
function create(id: string, content: string | ArrayBuffer | T) {
2023-08-01 17:50:00 +02:00
const path = pathFromId(id);
2025-01-25 18:57:06 +01:00
cache.set("all", undefined);
if (
typeof content === "string" || content instanceof ArrayBuffer
) {
return createDocument(path, content);
}
if (render) {
2025-01-18 00:46:05 +01:00
const rendered = render(content);
return createDocument(path, rendered);
}
throw new Error("No renderer defined for " + prefix + " CRUD");
2023-08-01 17:50:00 +02:00
}
async function update(id: string, updater: (r: Root) => Root) {
const path = pathFromId(id);
const content = await getDocument(path);
2025-01-18 00:46:05 +01:00
if (!content) {
return;
}
2023-08-01 17:50:00 +02:00
const newDoc = transformDocument(content, updater);
await createDocument(path, newDoc);
}
2023-08-29 13:48:52 +02:00
async function readAll({ sort = "rating" }: { sort?: SortType } = {}) {
2025-01-25 18:57:06 +01:00
if (cache.has("all")) {
return cache.get("all") as unknown as T[];
}
2023-08-01 17:50:00 +02:00
const allDocuments = await getDocuments();
2025-01-06 16:14:29 +01:00
const parsed = (await Promise.all(
2023-08-01 17:50:00 +02:00
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);
}),
2025-01-18 00:46:05 +01:00
)).sort(sortFunction<T>(sort)).filter((v) => !!v);
2025-01-06 16:14:29 +01:00
return parsed;
2023-08-01 17:50:00 +02:00
}
return {
read,
readAll,
create,
update,
};
}