import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib/prisma"; import { sendToNode } from "@/lib/websocket"; export async function GET(req: NextRequest) { const { searchParams } = new URL(req.url); const nodeId = searchParams.get("nodeId"); const establishmentId = searchParams.get("establishmentId"); let where: any = {}; if (nodeId) where.nodeId = nodeId; if (establishmentId) { const classes = await prisma.class.findMany({ where: { establishmentId }, select: { id: true } }); const students = await prisma.student.findMany({ where: { classId: { in: classes.map((c) => c.id) } }, select: { id: true } }); const nodes = await prisma.node.findMany({ where: { studentId: { in: students.map((s) => s.id) } }, select: { id: true } }); where.nodeId = { in: nodes.map((n) => n.id) }; } const instances = await prisma.instance.findMany({ where, include: { node: { include: { student: { include: { class: { include: { establishment: true, }, }, }, }, }, }, template: true, }, orderBy: { createdAt: "desc" }, }); const enriched = instances.map((inst) => { const domain = inst.node.student?.class.establishment?.domain; const publicUrl = domain ? `https://${inst.id}.${domain}` : null; const localUrl = inst.node.tailscaleIp ? `http://${inst.node.tailscaleIp}:${inst.port}` : null; return { ...inst, publicUrl, localUrl, }; }); return NextResponse.json(enriched); } export async function POST(req: NextRequest) { const body = await req.json(); const { nodeId, templateId, port } = body; const template = await prisma.template.findUnique({ where: { id: templateId } }); if (!template) return NextResponse.json({ error: "Template not found" }, { status: 404 }); const instance = await prisma.instance.create({ data: { nodeId, templateId, port: port || 8080, status: "stopped" }, }); const node = await prisma.node.findUnique({ where: { id: nodeId }, include: { student: { include: { class: { include: { establishment: true } } } } }, }); const domain = node?.student?.class.establishment?.domain; const publicDomain = domain ? `${instance.id}.${domain}` : "localhost"; const publicUrl = domain ? `https://${publicDomain}` : null; const sent = sendToNode(nodeId, { action: "start", instanceId: instance.id, type: template.type, port: instance.port, composeConfig: template.composeConfig .replace(/{PORT}/g, String(instance.port)) .replace(/{INSTANCE_ID}/g, instance.id) .replace(/{PUBLIC_URL}/g, publicUrl || `http://localhost:${instance.port}`) .replace(/{PUBLIC_DOMAIN}/g, "localhost"), }); if (!sent) { await prisma.instance.update({ where: { id: instance.id }, data: { status: "error" } }); } return NextResponse.json(instance, { status: 201 }); } export async function PATCH(req: NextRequest) { const body = await req.json(); const { id, action } = body; const instance = await prisma.instance.findUnique({ where: { id }, include: { template: true, node: { include: { student: { include: { class: { include: { establishment: true } } } } } } } }); if (!instance) return NextResponse.json({ error: "Not found" }, { status: 404 }); const domain = instance.node.student?.class.establishment?.domain; const publicDomain = domain ? `${instance.id}.${domain}` : "localhost"; const publicUrl = domain ? `https://${publicDomain}` : null; if (action === "stop") { sendToNode(instance.nodeId, { action: "delete", instanceId: instance.id }); await prisma.instance.update({ where: { id }, data: { status: "stopped" } }); } else if (action === "start") { const sent = sendToNode(instance.nodeId, { action: "start", instanceId: instance.id, type: instance.template.type, port: instance.port, composeConfig: instance.template.composeConfig .replace(/{PORT}/g, String(instance.port)) .replace(/{INSTANCE_ID}/g, instance.id) .replace(/{PUBLIC_URL}/g, publicUrl || `http://localhost:${instance.port}`) .replace(/{PUBLIC_DOMAIN}/g, "localhost"), }); if (!sent) await prisma.instance.update({ where: { id }, data: { status: "error" } }); } else if (action === "reset") { const sent = sendToNode(instance.nodeId, { action: "reset", instanceId: instance.id, type: instance.template.type, port: instance.port, composeConfig: instance.template.composeConfig .replace(/{PORT}/g, String(instance.port)) .replace(/{INSTANCE_ID}/g, instance.id) .replace(/{PUBLIC_URL}/g, publicUrl || `http://localhost:${instance.port}`) .replace(/{PUBLIC_DOMAIN}/g, "localhost"), }); if (!sent) await prisma.instance.update({ where: { id }, data: { status: "error" } }); } return NextResponse.json({ ok: true }); } export async function DELETE(req: NextRequest) { const { searchParams } = new URL(req.url); const id = searchParams.get("id"); if (!id) return NextResponse.json({ error: "Missing id" }, { status: 400 }); const instance = await prisma.instance.findUnique({ where: { id } }); if (instance) sendToNode(instance.nodeId, { action: "delete", instanceId: instance.id }); await prisma.instance.delete({ where: { id } }); return NextResponse.json({ ok: true }); }