import { Router, type Router as RouterType } from 'express'; import { z } from 'zod'; import { prisma } from '@agent-fox/shared'; import { requireAuth } from '../middleware/auth.js'; import { parseOpenApiDocument } from '../services/openapi-parser.js'; const router: RouterType = Router(); router.use(requireAuth); router.post('/', async (req, res) => { const { spec, specUrl } = req.body; if (!spec && !specUrl) { res.status(400).json({ success: false, error: { code: 'VALIDATION', message: 'Provide spec (JSON object) or specUrl (URL string)' } }); return; } try { const input = specUrl || spec; const parsed = await parseOpenApiDocument(input); const project = await prisma.$transaction(async (tx) => { const proj = await tx.project.create({ data: { userId: req.user!.userId, name: parsed.name, description: parsed.description, baseUrl: parsed.baseUrl, openApiSpec: parsed.spec as any, openApiVersion: parsed.openApiVersion, }, }); const moduleIdMap = new Map(); for (let i = 0; i < parsed.modules.length; i++) { const mod = parsed.modules[i]; const created = await tx.module.create({ data: { projectId: proj.id, name: mod.name, description: mod.description, sortOrder: i, source: mod.source, }, }); moduleIdMap.set(mod.name, created.id); } for (const ep of parsed.endpoints) { const moduleId = moduleIdMap.get(ep.moduleName); if (!moduleId) continue; await tx.endpoint.create({ data: { projectId: proj.id, moduleId, method: ep.method, path: ep.path, summary: ep.summary, description: ep.description, operationId: ep.operationId, parameters: ep.parameters as any, requestBody: ep.requestBody as any, responses: ep.responses as any, tags: ep.tags, deprecated: ep.deprecated, }, }); } return proj; }); res.status(201).json({ success: true, data: { project: { id: project.id, name: project.name }, stats: { modules: parsed.modules.length, endpoints: parsed.endpoints.length }, }, }); } catch (err) { const message = err instanceof Error ? err.message : 'Failed to parse OpenAPI document'; res.status(400).json({ success: false, error: { code: 'PARSE_ERROR', message } }); } }); router.get('/', async (req, res) => { const projects = await prisma.project.findMany({ where: { userId: req.user!.userId }, include: { _count: { select: { endpoints: true, modules: true } } }, orderBy: { updatedAt: 'desc' }, }); res.json({ success: true, data: projects }); }); router.get('/:id', async (req, res) => { const project = await prisma.project.findFirst({ where: { id: req.params.id, userId: req.user!.userId }, include: { modules: { include: { _count: { select: { endpoints: true } } }, orderBy: { sortOrder: 'asc' }, }, _count: { select: { endpoints: true } }, }, }); if (!project) { res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: 'Project not found' } }); return; } res.json({ success: true, data: project }); }); const updateSchema = z.object({ name: z.string().min(1).max(200).optional(), description: z.string().max(1000).optional(), baseUrl: z.string().url().optional(), }); router.put('/:id', async (req, res) => { const parsed = updateSchema.safeParse(req.body); if (!parsed.success) { res.status(400).json({ success: false, error: { code: 'VALIDATION', message: parsed.error.issues[0].message } }); return; } const project = await prisma.project.updateMany({ where: { id: req.params.id, userId: req.user!.userId }, data: parsed.data, }); if (project.count === 0) { res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: 'Project not found' } }); return; } const updated = await prisma.project.findUnique({ where: { id: req.params.id } }); res.json({ success: true, data: updated }); }); router.delete('/:id', async (req, res) => { const result = await prisma.project.deleteMany({ where: { id: req.params.id, userId: req.user!.userId }, }); if (result.count === 0) { res.status(404).json({ success: false, error: { code: 'NOT_FOUND', message: 'Project not found' } }); return; } res.json({ success: true, data: { deleted: true } }); }); export default router;