prepared queries

This commit is contained in:
Niki Wix Skaarup 2025-06-16 21:44:48 +02:00
parent d10a9e2bde
commit 0348b07325
Signed by: nikiskaarup
GPG key ID: FC2F1B116F6E788C
3 changed files with 90 additions and 39 deletions

View file

@ -1,11 +1,11 @@
import homepage from '@routes/index.html';
import { drizzleDB } from "@server/db";
import { entry } from "@server/db/schema";
import { drizzleDB } from '@server/db';
import { entry } from '@server/db/schema';
import favicon from '@static/favicon.png';
import robotsTxt from '@static/robots.txt';
import sitemapTxt from '@static/sitemap.txt';
import { env } from "bun";
import { desc, eq } from "drizzle-orm";
import { env } from 'bun';
import { desc, eq, sql } from 'drizzle-orm';
const development = env.NODE_ENV !== 'production';
@ -26,6 +26,58 @@ function etagResponse(etag: string, cacheControl?: string) {
let entriesETag = '';
let entriesNotModified = false;
const fetchEntries = drizzleDB
.select({
id: entry.id,
name: entry.name,
href: entry.href,
finished: entry.finished,
updated_at: entry.updatedAt,
})
.from(entry)
.orderBy(desc(entry.updatedAt))
.prepare();
const entryExists = drizzleDB
.select({ id: entry.id })
.from(entry)
.where(eq(entry.name, sql.placeholder('name')))
.prepare();
const insertEntry = drizzleDB
.insert(entry)
.values({
name: sql.placeholder('name'),
href: sql.placeholder('href'),
})
.prepare();
const updateEntry = drizzleDB
.update(entry)
.set({
name: sql.placeholder('name') as any,
href: sql.placeholder('href') as any,
})
.where(eq(entry.id, sql.placeholder('id')))
.prepare();
const fetchEntryById = drizzleDB
.select()
.from(entry)
.where(eq(entry.id, sql.placeholder('id')))
.prepare();
const deleteEntry = drizzleDB
.delete(entry)
.where(eq(entry.id, sql.placeholder('id')))
.prepare();
const updateEntryFinished = drizzleDB
.update(entry)
.set({ finished: sql.placeholder('finished') as any })
.where(eq(entry.id, sql.placeholder('id')))
.prepare();
Bun.serve({
routes: {
'/': homepage,
@ -46,16 +98,13 @@ Bun.serve({
}
entriesNotModified = true;
const entries = await drizzleDB
.select({ id: entry.id, name: entry.name, href: entry.href, finished: entry.finished, updated_at: entry.updatedAt })
.from(entry)
.orderBy(desc(entry.updatedAt));
const entries = await fetchEntries.execute();
const body = JSON.stringify(entries);
entriesETag = getEtag(body);
return new Response(body, {
headers: { 'Content-Type': 'application/json', 'ETag': entriesETag }
headers: { 'Content-Type': 'application/json', ETag: entriesETag },
});
},
async PUT(req) {
@ -64,23 +113,27 @@ Bun.serve({
const body = await getEntryFromReq(req);
if (!body) return new Response('Invalid data', { status: 400 });
const result = await drizzleDB.select({ id: entry.id }).from(entry).where(eq(entry.name, body.name));
const result = await entryExists.execute({ name: body.name });
let created = true;
if (result.length === 0) {
await drizzleDB.insert(entry).values(body).execute();
await insertEntry.execute(body);
} else if (result.length === 1) {
const row = result[0] as NonNullable<typeof result[number]>;
const row = result[0] as NonNullable<(typeof result)[number]>;
created = false;
await drizzleDB.update(entry).set(body).where(eq(entry.id, row.id)).execute();
await updateEntry.execute({
id: row.id,
name: body.name,
href: body.href,
});
} else {
return new Response('Invalid data, multiple matches?', { status: 400 });
}
entriesNotModified = false;
return new Response(JSON.stringify({ created }), {
headers: { 'Content-Type': 'application/json' }
headers: { 'Content-Type': 'application/json' },
});
},
},
@ -91,10 +144,10 @@ Bun.serve({
const id = Number.parseInt(req.params.id, 10);
if (Number.isNaN(id)) return new Response('Invalid id', { status: 400 });
const result = await drizzleDB.select().from(entry).where(eq(entry.id, id));
const result = await fetchEntryById.execute({ id });
return new Response(JSON.stringify(result), {
headers: { 'Content-Type': 'application/json' }
headers: { 'Content-Type': 'application/json' },
});
},
async PUT(req) {
@ -106,13 +159,15 @@ Bun.serve({
const body = await getEntryFromReq(req);
if (!body) return new Response('Invalid data', { status: 400 });
await drizzleDB.update(entry).set(body).where(eq(entry.id, id)).execute();
let created = false;
await updateEntry.execute({
id,
name: body.name,
href: body.href,
});
entriesNotModified = false;
return new Response(JSON.stringify({ created: false }), {
headers: { 'Content-Type': 'application/json' }
headers: { 'Content-Type': 'application/json' },
});
},
async DELETE(req) {
@ -121,7 +176,7 @@ Bun.serve({
const id = Number.parseInt(req.params.id, 10);
if (Number.isNaN(id)) return new Response('Invalid id', { status: 400 });
await drizzleDB.delete(entry).where(eq(entry.id, id)).execute();
await deleteEntry.execute({ id });
entriesNotModified = false;
return new Response('OK');
@ -135,11 +190,7 @@ Bun.serve({
if (Number.isNaN(id)) return new Response('Invalid id', { status: 400 });
const finished = req.params.finished === 'true';
await drizzleDB
.update(entry)
.set({ finished: finished })
.where(eq(entry.id, id))
.execute();
await updateEntryFinished.execute({ id, finished });
entriesNotModified = false;
return new Response('OK');
@ -159,7 +210,7 @@ console.log('Server started on port:', env.PORT ? Number.parseInt(env.PORT, 10)
async function getEntryFromReq(req: Request) {
const json = await req.json();
const body = json as { name: string, href: string }
const body = json as { name: string; href: string };
if (!body.name || !body.href || typeof body.name !== 'string' || typeof body.href !== 'string') {
return null;
}
@ -180,7 +231,7 @@ function isAuthenticated(req: Request) {
}
const unauthorizedHeaders = new Headers({
'WWW-Authenticate': `Bearer realm='sign', error="invalid_request"`
'WWW-Authenticate': `Bearer realm='sign', error="invalid_request"`,
});
function unauthorizedResp() {