2026-02-08 13:39:33 +01:00

313 lines
9.8 KiB
TypeScript

import type { FastifyInstance } from "fastify";
import { eq, and } from "drizzle-orm";
import { db } from "../db/index.js";
import { wms, packages, projects } from "../db/schema.js";
export async function wmRoutes(app: FastifyInstance) {
// GET /projects/:projectId/wms — list WMs for a project
app.get<{ Params: { projectId: string } }>(
"/projects/:projectId/wms",
{
preHandler: [app.authenticate, app.requireAccount],
schema: {
description: "List all WMs for a project",
tags: ["WMs"],
security: [{ bearerAuth: [] }],
params: {
type: "object",
properties: {
accountId: { type: "string", format: "uuid" },
projectId: { type: "string", format: "uuid" },
},
},
response: {
200: {
description: "List of WMs",
type: "array",
items: {
type: "object",
properties: {
id: { type: "string" },
projectId: { type: "string" },
name: { type: "string" },
packageId: { type: "string", nullable: true },
vcpu: { type: "number" },
ram: { type: "integer" },
disk: { type: "integer" },
createdAt: { type: "string", format: "date-time" },
updatedAt: { type: "string", format: "date-time" },
},
},
},
404: {
description: "Project not found",
type: "object",
properties: { error: { type: "string" } },
},
},
},
},
async (request, reply) => {
// Verify project belongs to the account
const [project] = await db
.select()
.from(projects)
.where(
and(eq(projects.id, request.params.projectId), eq(projects.accountId, request.accountId)),
);
if (!project) {
return reply.status(404).send({ error: "Project not found" });
}
return db.select().from(wms).where(eq(wms.projectId, request.params.projectId));
},
);
// POST /projects/:projectId/wms — create a WM
app.post<{
Params: { projectId: string };
Body: { name: string; packageId?: string; vcpu?: number; ram?: number; disk?: number };
}>(
"/projects/:projectId/wms",
{
preHandler: [app.authenticate, app.requireAccount],
schema: {
description:
"Create a WM from a package or with custom settings. Provide packageId to use a package, or vcpu/ram/disk for custom settings.",
tags: ["WMs"],
security: [{ bearerAuth: [] }],
params: {
type: "object",
properties: {
accountId: { type: "string", format: "uuid" },
projectId: { type: "string", format: "uuid" },
},
},
body: {
type: "object",
required: ["name"],
properties: {
name: { type: "string", minLength: 1 },
packageId: { type: "string", format: "uuid" },
vcpu: { type: "number", minimum: 0.25 },
ram: { type: "integer", minimum: 1 },
disk: { type: "integer", minimum: 1 },
},
},
response: {
201: {
description: "WM created successfully",
type: "object",
properties: {
id: { type: "string" },
projectId: { type: "string" },
name: { type: "string" },
packageId: { type: "string", nullable: true },
vcpu: { type: "number" },
ram: { type: "integer" },
disk: { type: "integer" },
createdAt: { type: "string", format: "date-time" },
updatedAt: { type: "string", format: "date-time" },
},
},
400: {
description: "Invalid input",
type: "object",
properties: { error: { type: "string" } },
},
404: {
description: "Project or package not found",
type: "object",
properties: { error: { type: "string" } },
},
},
},
},
async (request, reply) => {
const { name, packageId, vcpu, ram, disk } = request.body;
// Verify project belongs to the account
const [project] = await db
.select()
.from(projects)
.where(
and(eq(projects.id, request.params.projectId), eq(projects.accountId, request.accountId)),
);
if (!project) {
return reply.status(404).send({ error: "Project not found" });
}
let settings: { vcpu: number; ram: number; disk: number; packageId: string | null };
if (packageId) {
const [pkg] = await db.select().from(packages).where(eq(packages.id, packageId));
if (!pkg) {
return reply.status(404).send({ error: "Package not found" });
}
settings = { vcpu: pkg.vcpu, ram: pkg.ram, disk: pkg.disk, packageId: pkg.id };
} else if (vcpu !== undefined && ram !== undefined && disk !== undefined) {
settings = { vcpu, ram, disk, packageId: null };
} else {
return reply
.status(400)
.send({ error: "Provide either packageId or all of vcpu, ram, and disk" });
}
const [wm] = await db
.insert(wms)
.values({
projectId: request.params.projectId,
name,
...settings,
})
.returning();
return reply.status(201).send(wm);
},
);
// PATCH /projects/:projectId/wms/:id — update a WM
app.patch<{
Params: { projectId: string; id: string };
Body: { name?: string; vcpu?: number; ram?: number; disk?: number };
}>(
"/projects/:projectId/wms/:id",
{
preHandler: [app.authenticate, app.requireAccount],
schema: {
description: "Update a WM's name or settings",
tags: ["WMs"],
security: [{ bearerAuth: [] }],
params: {
type: "object",
properties: {
accountId: { type: "string", format: "uuid" },
projectId: { type: "string", format: "uuid" },
id: { type: "string", format: "uuid" },
},
},
body: {
type: "object",
properties: {
name: { type: "string", minLength: 1 },
vcpu: { type: "number", minimum: 0.25 },
ram: { type: "integer", minimum: 1 },
disk: { type: "integer", minimum: 1 },
},
},
response: {
200: {
description: "WM updated successfully",
type: "object",
properties: {
id: { type: "string" },
projectId: { type: "string" },
name: { type: "string" },
packageId: { type: "string", nullable: true },
vcpu: { type: "number" },
ram: { type: "integer" },
disk: { type: "integer" },
createdAt: { type: "string", format: "date-time" },
updatedAt: { type: "string", format: "date-time" },
},
},
404: {
description: "WM not found",
type: "object",
properties: { error: { type: "string" } },
},
},
},
},
async (request, reply) => {
// Verify project belongs to the account
const [project] = await db
.select()
.from(projects)
.where(
and(eq(projects.id, request.params.projectId), eq(projects.accountId, request.accountId)),
);
if (!project) {
return reply.status(404).send({ error: "Project not found" });
}
const { name, vcpu, ram, disk } = request.body;
const updates: Record<string, unknown> = { updatedAt: new Date() };
if (name !== undefined) updates.name = name;
if (vcpu !== undefined) updates.vcpu = vcpu;
if (ram !== undefined) updates.ram = ram;
if (disk !== undefined) updates.disk = disk;
const [wm] = await db
.update(wms)
.set(updates)
.where(and(eq(wms.id, request.params.id), eq(wms.projectId, request.params.projectId)))
.returning();
if (!wm) {
return reply.status(404).send({ error: "WM not found" });
}
return wm;
},
);
// DELETE /projects/:projectId/wms/:id — delete a WM
app.delete<{ Params: { projectId: string; id: string } }>(
"/projects/:projectId/wms/:id",
{
preHandler: [app.authenticate, app.requireAccount],
schema: {
description: "Delete a WM",
tags: ["WMs"],
security: [{ bearerAuth: [] }],
params: {
type: "object",
properties: {
accountId: { type: "string", format: "uuid" },
projectId: { type: "string", format: "uuid" },
id: { type: "string", format: "uuid" },
},
},
response: {
204: {
description: "WM deleted successfully",
},
404: {
description: "WM not found",
type: "object",
properties: { error: { type: "string" } },
},
},
},
},
async (request, reply) => {
// Verify project belongs to the account
const [project] = await db
.select()
.from(projects)
.where(
and(eq(projects.id, request.params.projectId), eq(projects.accountId, request.accountId)),
);
if (!project) {
return reply.status(404).send({ error: "Project not found" });
}
const [deleted] = await db
.delete(wms)
.where(and(eq(wms.id, request.params.id), eq(wms.projectId, request.params.projectId)))
.returning();
if (!deleted) {
return reply.status(404).send({ error: "WM not found" });
}
return reply.status(204).send();
},
);
}