1
0

feat: loan tracking routes

This commit is contained in:
2026-09-03 21:37:06 +02:00
parent f8f240f00a
commit 53b4bc39f5
3 changed files with 144 additions and 0 deletions

View File

@@ -15,6 +15,7 @@ import { registerLookupRoutes } from './routes/lookupRoutes.js'
import { registerCollectionRoutes } from './routes/collectionRoutes.js'
import { registerLibraryRoutes } from './routes/libraryRoutes.js'
import { registerStreamRoutes } from './routes/streamRoutes.js'
import { registerLoanRoutes } from './routes/loanRoutes.js'
declare module 'fastify' {
interface FastifyInstance {
@@ -54,6 +55,7 @@ export async function buildApp(opts: AppOptions): Promise<FastifyInstance> {
await registerCollectionRoutes(app)
await registerLibraryRoutes(app)
await registerStreamRoutes(app)
await registerLoanRoutes(app)
// artwork cache (always available)
await app.register(fastifyStatic, {

View File

@@ -0,0 +1,64 @@
import { FastifyInstance } from 'fastify'
import { requireAuth } from './authRoutes.js'
interface LoanRow {
id: number
user_id: number
item_id: number
borrower: string
lent_at: string
returned_at: string | null
}
function toLoan(l: LoanRow) {
return { id: l.id, itemId: l.item_id, borrower: l.borrower, lentAt: l.lent_at, returnedAt: l.returned_at }
}
export async function registerLoanRoutes(app: FastifyInstance): Promise<void> {
app.post('/api/collection/:id/loan', { preHandler: [requireAuth] }, async (request, reply) => {
const db = request.server.db
const userId = request.user!.id
const id = Number((request.params as { id: string }).id)
const item = db
.prepare('SELECT id FROM collection_items WHERE id = ? AND user_id = ?')
.get(id, userId)
if (!item) return reply.code(404).send({ error: 'not_found' })
const { borrower } = (request.body ?? {}) as { borrower?: string }
if (typeof borrower !== 'string' || borrower.trim() === '') {
return reply.code(400).send({ error: 'invalid_input', detail: 'borrower is required' })
}
const active = db
.prepare('SELECT id FROM loans WHERE item_id = ? AND returned_at IS NULL')
.get(id)
if (active) return reply.code(409).send({ error: 'already_on_loan' })
const info = db
.prepare('INSERT INTO loans (user_id, item_id, borrower) VALUES (?, ?, ?)')
.run(userId, id, borrower.trim())
const loan = db.prepare('SELECT * FROM loans WHERE id = ?').get(info.lastInsertRowid) as LoanRow
return reply.code(200).send(toLoan(loan))
})
app.get('/api/loans', { preHandler: [requireAuth] }, async (request) => {
const db = request.server.db
const userId = request.user!.id
const active = db
.prepare('SELECT * FROM loans WHERE user_id = ? AND returned_at IS NULL ORDER BY lent_at DESC')
.all(userId) as LoanRow[]
const history = db
.prepare(
'SELECT * FROM loans WHERE user_id = ? AND returned_at IS NOT NULL ORDER BY returned_at DESC LIMIT 50'
)
.all(userId) as LoanRow[]
return { active: active.map(toLoan), history: history.map(toLoan) }
})
app.post('/api/loans/:id/return', { preHandler: [requireAuth] }, async (request, reply) => {
const db = request.server.db
const id = Number((request.params as { id: string }).id)
const info = db
.prepare("UPDATE loans SET returned_at = datetime('now') WHERE id = ? AND user_id = ? AND returned_at IS NULL")
.run(id, request.user!.id)
if (info.changes === 0) return reply.code(404).send({ error: 'not_found' })
return { ok: true }
})
}