Complete project scaffold with working auth, REST API, Prisma/SQLite schema, Docker config, and React frontend for both Rack Planner and Service Mapper modules. Both server and client pass TypeScript strict mode with zero errors. Initial migration applied. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
171 lines
4.5 KiB
TypeScript
171 lines
4.5 KiB
TypeScript
import { prisma } from '../lib/prisma';
|
|
import { AppError } from '../types/index';
|
|
import type { NodeType } from '../lib/constants';
|
|
|
|
const mapInclude = {
|
|
nodes: {
|
|
include: { module: true },
|
|
},
|
|
edges: true,
|
|
};
|
|
|
|
export async function listMaps() {
|
|
return prisma.serviceMap.findMany({
|
|
orderBy: { createdAt: 'desc' },
|
|
select: { id: true, name: true, description: true, createdAt: true, updatedAt: true },
|
|
});
|
|
}
|
|
|
|
export async function getMap(id: string) {
|
|
const map = await prisma.serviceMap.findUnique({ where: { id }, include: mapInclude });
|
|
if (!map) throw new AppError('Map not found', 404, 'NOT_FOUND');
|
|
return map;
|
|
}
|
|
|
|
export async function createMap(data: { name: string; description?: string }) {
|
|
return prisma.serviceMap.create({ data, include: mapInclude });
|
|
}
|
|
|
|
export async function updateMap(id: string, data: Partial<{ name: string; description: string }>) {
|
|
await getMap(id);
|
|
return prisma.serviceMap.update({ where: { id }, data, include: mapInclude });
|
|
}
|
|
|
|
export async function deleteMap(id: string) {
|
|
await getMap(id);
|
|
return prisma.serviceMap.delete({ where: { id } });
|
|
}
|
|
|
|
// ---- Nodes ----
|
|
|
|
export async function addNode(
|
|
mapId: string,
|
|
data: {
|
|
label: string;
|
|
nodeType: NodeType;
|
|
positionX: number;
|
|
positionY: number;
|
|
metadata?: string;
|
|
color?: string;
|
|
icon?: string;
|
|
moduleId?: string;
|
|
}
|
|
) {
|
|
await getMap(mapId);
|
|
return prisma.serviceNode.create({
|
|
data: { mapId, ...data },
|
|
include: { module: true },
|
|
});
|
|
}
|
|
|
|
export async function populateFromRack(mapId: string) {
|
|
await getMap(mapId);
|
|
|
|
const modules = await prisma.module.findMany({
|
|
orderBy: [{ rack: { displayOrder: 'asc' } }, { uPosition: 'asc' }],
|
|
include: { rack: true },
|
|
});
|
|
|
|
const existing = await prisma.serviceNode.findMany({
|
|
where: { mapId, moduleId: { not: null } },
|
|
select: { moduleId: true },
|
|
});
|
|
const existingModuleIds = new Set(existing.map((n) => n.moduleId as string));
|
|
|
|
const newModules = modules.filter((m) => !existingModuleIds.has(m.id));
|
|
if (newModules.length === 0) return getMap(mapId);
|
|
|
|
const byRack = new Map<string, typeof modules>();
|
|
for (const mod of newModules) {
|
|
if (!byRack.has(mod.rackId)) byRack.set(mod.rackId, []);
|
|
byRack.get(mod.rackId)!.push(mod);
|
|
}
|
|
|
|
const NODE_W = 200;
|
|
const NODE_H = 80;
|
|
const COL_GAP = 260;
|
|
const ROW_GAP = 110;
|
|
|
|
const nodesToCreate: Array<{
|
|
mapId: string;
|
|
label: string;
|
|
nodeType: NodeType;
|
|
positionX: number;
|
|
positionY: number;
|
|
moduleId: string;
|
|
}> = [];
|
|
|
|
let colIdx = 0;
|
|
for (const rackModules of byRack.values()) {
|
|
rackModules.forEach((mod, rowIdx) => {
|
|
nodesToCreate.push({
|
|
mapId,
|
|
label: mod.name,
|
|
nodeType: 'DEVICE' as NodeType,
|
|
positionX: colIdx * (NODE_W + COL_GAP),
|
|
positionY: rowIdx * (NODE_H + ROW_GAP),
|
|
moduleId: mod.id,
|
|
});
|
|
});
|
|
colIdx++;
|
|
}
|
|
|
|
await prisma.serviceNode.createMany({ data: nodesToCreate });
|
|
return getMap(mapId);
|
|
}
|
|
|
|
export async function updateNode(
|
|
id: string,
|
|
data: Partial<{
|
|
label: string;
|
|
positionX: number;
|
|
positionY: number;
|
|
metadata: string;
|
|
color: string;
|
|
icon: string;
|
|
moduleId: string | null;
|
|
}>
|
|
) {
|
|
const existing = await prisma.serviceNode.findUnique({ where: { id } });
|
|
if (!existing) throw new AppError('Node not found', 404, 'NOT_FOUND');
|
|
return prisma.serviceNode.update({ where: { id }, data, include: { module: true } });
|
|
}
|
|
|
|
export async function deleteNode(id: string) {
|
|
const existing = await prisma.serviceNode.findUnique({ where: { id } });
|
|
if (!existing) throw new AppError('Node not found', 404, 'NOT_FOUND');
|
|
return prisma.serviceNode.delete({ where: { id } });
|
|
}
|
|
|
|
// ---- Edges ----
|
|
|
|
export async function addEdge(
|
|
mapId: string,
|
|
data: {
|
|
sourceId: string;
|
|
targetId: string;
|
|
label?: string;
|
|
edgeType?: string;
|
|
animated?: boolean;
|
|
metadata?: string;
|
|
}
|
|
) {
|
|
await getMap(mapId);
|
|
return prisma.serviceEdge.create({ data: { mapId, ...data } });
|
|
}
|
|
|
|
export async function updateEdge(
|
|
id: string,
|
|
data: Partial<{ label: string; edgeType: string; animated: boolean; metadata: string }>
|
|
) {
|
|
const existing = await prisma.serviceEdge.findUnique({ where: { id } });
|
|
if (!existing) throw new AppError('Edge not found', 404, 'NOT_FOUND');
|
|
return prisma.serviceEdge.update({ where: { id }, data });
|
|
}
|
|
|
|
export async function deleteEdge(id: string) {
|
|
const existing = await prisma.serviceEdge.findUnique({ where: { id } });
|
|
if (!existing) throw new AppError('Edge not found', 404, 'NOT_FOUND');
|
|
return prisma.serviceEdge.delete({ where: { id } });
|
|
}
|