import { Router, type Request, type Response } from 'express'; import { prisma } from '../lib/prisma'; import { basicAuth } from '../middlewares/basicAuth'; import { CreateModelSchema, UpdateModelSchema, ListQuerySchema } from '../lib/validators'; import { decodeBase64ToBuffer } from '../lib/base64'; export const router = Router(); // Create model router.post('/', basicAuth, async (req: Request, res: Response) => { const parsed = CreateModelSchema.safeParse(req.body); if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() }); const { title, desc, model, previewBase64, previewMime } = parsed.data; const created = await prisma.circuitModel.create({ data: { title, desc, model, preview: decodeBase64ToBuffer(previewBase64), previewMime: previewBase64 ? (previewMime ?? 'image/png') : null, authorId: (req as any).user.id, }, }); res.status(201).json({ id: created.id }); }); // Update model router.put('/:id', basicAuth, async (req: Request, res: Response) => { const id = req.params.id; const owned = await prisma.circuitModel.findUnique({ where: { id }, select: { authorId: true } }); if (!owned) return res.status(404).json({ error: 'Not found' }); if (owned.authorId !== (req as any).user.id) return res.status(403).json({ error: 'Forbidden' }); const parsed = UpdateModelSchema.safeParse(req.body); if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() }); const { title, desc, model, previewBase64, previewMime } = parsed.data; const data: any = {}; if (title !== undefined) data.title = title; if (desc !== undefined) data.desc = desc; if (model !== undefined) data.model = model; if (previewBase64 !== undefined) { data.preview = decodeBase64ToBuffer(previewBase64); data.previewMime = previewBase64 ? (previewMime ?? 'image/png') : null; } const updated = await prisma.circuitModel.update({ where: { id }, data }); res.json({ id: updated.id }); }); // Delete model router.delete('/:id', basicAuth, async (req: Request, res: Response) => { const id = req.params.id; const owned = await prisma.circuitModel.findUnique({ where: { id }, select: { authorId: true } }); if (!owned) return res.status(404).json({ error: 'Not found' }); if (owned.authorId !== (req as any).user.id) return res.status(403).json({ error: 'Forbidden' }); await prisma.circuitModel.delete({ where: { id } }); res.status(204).end(); }); // Get by id router.get('/:id', async (req: Request, res: Response) => { const id = req.params.id; const m = await prisma.circuitModel.findUnique({ where: { id }, select: { id: true, title: true, desc: true, model: true, authorId: true, createdAt: true, updatedAt: true, }, }); if (!m) return res.status(404).json({ error: 'Not found' }); res.json(m); }); // List models with pagination and simple search router.get('/', async (req: Request, res: Response) => { const parsed = ListQuerySchema.safeParse(req.query); if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() }); const { page, pageSize, authorId, sort, q } = parsed.data; const where: any = {}; if (authorId) where.authorId = authorId; if (q) where.OR = [ { title: { contains: q, mode: 'insensitive' } }, { desc: { contains: q, mode: 'insensitive' } }, ]; const total = await prisma.circuitModel.count({ where }); const items = await prisma.circuitModel.findMany({ where, orderBy: { createdAt: sort === 'new' ? 'desc' : 'asc' }, skip: (page - 1) * pageSize, take: pageSize, select: { id: true, title: true, authorId: true, createdAt: true, updatedAt: true, }, }); res.json({ page, pageSize, total, items }); }); // Download model JSON router.get('/:id/model.json', async (req: Request, res: Response) => { const id = req.params.id; const m = await prisma.circuitModel.findUnique({ where: { id }, select: { model: true, title: true } }); if (!m) return res.status(404).end(); res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(m.title)}.json"`); res.end(JSON.stringify(m.model)); }); // Get preview image router.get('/:id/preview', async (req: Request, res: Response) => { const id = req.params.id; const m = await prisma.circuitModel.findUnique({ where: { id }, select: { preview: true, previewMime: true } }); if (!m || !m.preview) return res.status(404).end(); res.setHeader('Content-Type', m.previewMime ?? 'image/png'); res.end(Buffer.from(m.preview)); });