dev_arc_aws/backend/src/routes/bookmarks.ts

82 lines
3.4 KiB
TypeScript
Raw Normal View History

import type { FastifyInstance } from 'fastify'
import { z } from 'zod'
import { db, logEvent } from '../db/index.js'
const bookmarkSchema = z.object({
categoryId: z.number().int().nullable().optional(),
title: z.string().min(1).max(128),
url: z.string().url(),
icon: z.string().optional(),
favorite: z.boolean().optional(),
})
const categorySchema = z.object({
name: z.string().min(1).max(64),
icon: z.string().optional(),
sortOrder: z.number().int().optional(),
})
export async function bookmarkRoutes(app: FastifyInstance) {
app.addHook('onRequest', app.authenticate)
app.get('/api/bookmarks/categories', async () => {
const categories = db.prepare('SELECT * FROM bookmark_categories ORDER BY sort_order').all()
return { categories }
})
app.post('/api/bookmarks/categories', async (req, reply) => {
const parsed = categorySchema.safeParse(req.body)
if (!parsed.success) return reply.code(400).send({ error: 'Invalid input' })
const { name, icon, sortOrder } = parsed.data
const result = db
.prepare('INSERT INTO bookmark_categories (name, icon, sort_order) VALUES (?, ?, ?)')
.run(name, icon ?? null, sortOrder ?? 0)
return reply.code(201).send({ id: result.lastInsertRowid })
})
app.get('/api/bookmarks', async () => {
const bookmarks = db.prepare('SELECT * FROM bookmarks ORDER BY created_at DESC').all()
return { bookmarks }
})
app.post('/api/bookmarks', async (req, reply) => {
const parsed = bookmarkSchema.safeParse(req.body)
if (!parsed.success) return reply.code(400).send({ error: 'Invalid input' })
const { categoryId, title, url, icon, favorite } = parsed.data
const result = db
.prepare(
'INSERT INTO bookmarks (category_id, title, url, icon, favorite) VALUES (?, ?, ?, ?, ?)'
)
.run(categoryId ?? null, title, url, icon ?? null, favorite ? 1 : 0)
logEvent('bookmark_created', `Bookmark added: ${title}`)
return reply.code(201).send({ id: result.lastInsertRowid })
})
app.put('/api/bookmarks/:id', async (req, reply) => {
const id = Number((req.params as { id: string }).id)
const parsed = bookmarkSchema.partial().safeParse(req.body)
if (!parsed.success) return reply.code(400).send({ error: 'Invalid input' })
const existing = db.prepare('SELECT * FROM bookmarks WHERE id = ?').get(id) as
| { category_id: number | null; title: string; url: string; icon: string | null; favorite: number }
| undefined
if (!existing) return reply.code(404).send({ error: 'Not found' })
const categoryId = parsed.data.categoryId ?? existing.category_id
const title = parsed.data.title ?? existing.title
const url = parsed.data.url ?? existing.url
const icon = parsed.data.icon ?? existing.icon
const favorite = parsed.data.favorite ?? !!existing.favorite
db.prepare(
'UPDATE bookmarks SET category_id = ?, title = ?, url = ?, icon = ?, favorite = ? WHERE id = ?'
).run(categoryId, title, url, icon, favorite ? 1 : 0, id)
return { ok: true }
})
app.delete('/api/bookmarks/:id', async (req, reply) => {
const id = Number((req.params as { id: string }).id)
const existing = db.prepare('SELECT title FROM bookmarks WHERE id = ?').get(id) as { title: string } | undefined
db.prepare('DELETE FROM bookmarks WHERE id = ?').run(id)
if (existing) logEvent('bookmark_deleted', `Bookmark removed: ${existing.title}`)
return reply.code(204).send()
})
}