Handle image in jsonb

This commit is contained in:
Zoe Roux 2025-03-16 20:31:34 +01:00
parent 6e642db7db
commit 71b57c50e7
No known key found for this signature in database
3 changed files with 40 additions and 41 deletions

View File

@ -1,13 +1,12 @@
import { mkdir, writeFile } from "node:fs/promises"; import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import { encode } from "blurhash"; import { encode } from "blurhash";
import { eq, sql } from "drizzle-orm"; import { type SQLWrapper, eq, sql } from "drizzle-orm";
import type { PgColumn } from "drizzle-orm/pg-core"; import type { PgColumn, PgTable } from "drizzle-orm/pg-core";
import { version } from "package.json"; import { version } from "package.json";
import type { PoolClient } from "pg"; import type { PoolClient } from "pg";
import sharp from "sharp"; import sharp from "sharp";
import { type Transaction, db } from "~/db"; import { type Transaction, db } from "~/db";
import * as schema from "~/db/schema";
import { mqueue } from "~/db/schema/queue"; import { mqueue } from "~/db/schema/queue";
import type { Image } from "~/models/utils"; import type { Image } from "~/models/utils";
@ -21,30 +20,38 @@ type ImageTask = {
column: string; column: string;
}; };
type ImageTaskC = {
url: string;
column: PgColumn;
};
// this will only push a task to the image downloader service and not download it instantly. // this will only push a task to the image downloader service and not download it instantly.
// this is both done to prevent to many requests to be sent at once and to make sure POST // this is both done to prevent to many requests to be sent at once and to make sure POST
// requests are not blocked by image downloading or blurhash calculation // requests are not blocked by image downloading or blurhash calculation
export const enqueueImage = async ( export const enqueueOptImage = async (
tx: Transaction, tx: Transaction,
img: ImageTaskC, img:
): Promise<Image> => { | { url: string | null; column: PgColumn }
| { url: string | null; table: PgTable; column: SQLWrapper },
): Promise<Image | null> => {
if (!img.url) return null;
const hasher = new Bun.CryptoHasher("sha256"); const hasher = new Bun.CryptoHasher("sha256");
hasher.update(img.url); hasher.update(img.url);
const id = hasher.digest().toString("hex"); const id = hasher.digest().toString("hex");
const message: ImageTask =
"table" in img
? {
id,
url: img.url,
table: img.table._.name,
column: img.column.getSQL().sql,
}
: {
id,
url: img.url,
table: img.column.table._.name,
column: img.column,
};
await tx.insert(mqueue).values({ await tx.insert(mqueue).values({
kind: "image", kind: "image",
message: { message,
id,
url: img.url,
table: img.column.table._.name,
column: img.column.name,
} satisfies ImageTask,
}); });
await tx.execute(sql`notify image`); await tx.execute(sql`notify image`);
@ -55,14 +62,6 @@ export const enqueueImage = async (
}; };
}; };
export const enqueueOptImage = async (
tx: Transaction,
img: { url: string | null; column: PgColumn },
): Promise<Image | null> => {
if (!img.url) return null;
return await enqueueImage(tx, { url: img.url, column: img.column });
};
export const processImages = async () => { export const processImages = async () => {
async function processOne() { async function processOne() {
return await db.transaction(async (tx) => { return await db.transaction(async (tx) => {
@ -78,19 +77,14 @@ export const processImages = async () => {
const img = item.message as ImageTask; const img = item.message as ImageTask;
const blurhash = await downloadImage(img.id, img.url); const blurhash = await downloadImage(img.id, img.url);
const ret: Image = { id: img.id, source: img.url, blurhash };
const table = schema[img.table as keyof typeof schema] as any; const table = sql.raw(img.table);
const column = sql.raw(img.column);
await tx await tx.execute(sql`
.update(table) update ${table} set ${column} = ${ret} where ${column}->'id' = '${item.id}'
.set({ `);
[img.column]: {
id: img.id,
source: img.url,
blurhash,
} satisfies Image,
})
.where(eq(sql`${table[img.column]}->'id'`, img.id));
await tx.delete(mqueue).where(eq(mqueue.id, item.id)); await tx.delete(mqueue).where(eq(mqueue.id, item.id));
return true; return true;

View File

@ -30,19 +30,23 @@ export const insertShow = async (
...original, ...original,
poster: await enqueueOptImage(tx, { poster: await enqueueOptImage(tx, {
url: original.poster, url: original.poster,
column: shows.original.poster, table: shows,
column: sql`${shows.original}['poster']`,
}), }),
thumbnail: await enqueueOptImage(tx, { thumbnail: await enqueueOptImage(tx, {
url: original.thumbnail, url: original.thumbnail,
column: shows.original.thumbnail, table: shows,
column: sql`${shows.original}['thumbnail']`,
}), }),
banner: await enqueueOptImage(tx, { banner: await enqueueOptImage(tx, {
url: original.banner, url: original.banner,
column: shows.original.banner, table: shows,
column: sql`${shows.original}['banner']`,
}), }),
logo: await enqueueOptImage(tx, { logo: await enqueueOptImage(tx, {
url: original.logo, url: original.logo,
column: shows.original.logo, table: shows,
column: sql`${shows.original}['logo']`,
}), }),
}; };
const ret = await insertBaseShow(tx, { ...show, original: orig }); const ret = await insertBaseShow(tx, { ...show, original: orig });

View File

@ -40,7 +40,8 @@ export const insertStaff = async (
...x.character, ...x.character,
image: await enqueueOptImage(tx, { image: await enqueueOptImage(tx, {
url: x.character.image, url: x.character.image,
column: roles.character.image, table: roles,
column: `${roles.character}['image']`,
}), }),
}, },
})), })),