2025-09-13 13:02:17 +08:00

123 lines
4.6 KiB
TypeScript

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));
});