import { env } from "bun"; import homepage from './index.html'; import robotsTxt from './static/robots.txt'; import sitemapTxt from './static/sitemap.txt'; // @ts-ignore ts2307 import icon from './static/favicon.png' with { type: 'file' }; import { entry } from "./db/schema"; import { desc, eq } from "drizzle-orm"; const favicon = await Bun.file(icon).bytes(); const development = env.NODE_ENV !== 'production'; Bun.serve({ routes: { '/': homepage, '/robots.txt': new Response(robotsTxt, { headers: { 'Content-Type': 'text/plain' }, }), '/sitemap.txt': new Response(sitemapTxt, { headers: { 'Content-Type': 'text/plain' }, }), '/favicon.ico': new Response(favicon, { headers: { 'Content-Type': 'image/png' }, }), '/health': new Response('OK'), '/api/entries': { async GET(req) { if (!isAuthenticated(req)) return unauthorizedResp(); const entries = await drizzleDB.select().from(entry).orderBy(desc(entry.updatedAt)); return new Response(JSON.stringify(entries), { headers: { 'Content-Type': 'application/json' } }); }, async POST(req) { if (!isAuthenticated(req)) return unauthorizedResp(); 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)); let created = true; if (result.length === 0) { await drizzleDB.insert(entry).values(body).execute(); } else if (result.length === 1) { const row = result[0]; created = false; await drizzleDB.update(entry).set(body).where(eq(entry.id, row.id)).execute(); } else { return new Response('Invalid data, multiple matches?', { status: 400 }); } return new Response(JSON.stringify({ created }), { headers: { 'Content-Type': 'application/json' } }); }, }, '/api/entries/:id': { async GET(req) { if (!isAuthenticated(req)) return unauthorizedResp(); 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)); return new Response(JSON.stringify(result), { headers: { 'Content-Type': 'application/json' } }); }, async PUT(req) { if (!isAuthenticated(req)) return unauthorizedResp(); const id = Number.parseInt(req.params.id, 10); if (Number.isNaN(id)) return new Response('Invalid id', { status: 400 }); 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; return new Response(JSON.stringify({ created: false }), { headers: { 'Content-Type': 'application/json' } }); }, async DELETE(req) { if (!isAuthenticated(req)) return unauthorizedResp(); 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(); return new Response('OK'); }, }, '/api/entries/:id/check/:finished': { async PUT(req) { if (!isAuthenticated(req)) return unauthorizedResp(); const id = Number.parseInt(req.params.id, 10); 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(); return new Response('OK'); }, }, }, development, reusePort: true, port: env.PORT || 3000, // async fetch(req, server) { // return new Response("Not found", { status: 404 }); // }, }); async function getEntryFromReq(req: Request) { const json = await req.json(); const body = json as { name: string, href: string } if (!body.name || !body.href || typeof body.name !== 'string' || typeof body.href !== 'string') { return null; } // Trim and lowercase the name to ensure consistency and uniqueness body.name = body.name.trim().toLocaleLowerCase(); return body; } function isAuthenticated(req: Request) { const bearer = req.headers.get('bearer'); if (!bearer) return false; return bearer === env.BEARER_TOKEN; } const unauthorizedHeaders = new Headers({ 'WWW-Authenticate': `Bearer realm='sign', error="invalid_request"` }); function unauthorizedResp() { return new Response('Unauthorized', { status: 401, headers: unauthorizedHeaders }); }