// src/routes/resources/history.ts import { FastifyInstance } from "fastify"; import { and, asc, eq, inArray } from "drizzle-orm"; import { authProfiles, historyitems } from "../../db/schema"; const columnMap: Record = { customers: historyitems.customer, members: historyitems.customer, vendors: historyitems.vendor, projects: historyitems.project, plants: historyitems.plant, contacts: historyitems.contact, tasks: historyitems.task, vehicles: historyitems.vehicle, events: historyitems.event, files: historyitems.file, products: historyitems.product, inventoryitems: historyitems.inventoryitem, inventoryitemgroups: historyitems.inventoryitemgroup, checks: historyitems.check, costcentres: historyitems.costcentre, ownaccounts: historyitems.ownaccount, documentboxes: historyitems.documentbox, hourrates: historyitems.hourrate, services: historyitems.service, customerspaces: historyitems.customerspace, customerinventoryitems: historyitems.customerinventoryitem, memberrelations: historyitems.memberrelation, }; const insertFieldMap: Record = { customers: "customer", members: "customer", vendors: "vendor", projects: "project", plants: "plant", contacts: "contact", tasks: "task", vehicles: "vehicle", events: "event", files: "file", products: "product", inventoryitems: "inventoryitem", inventoryitemgroups: "inventoryitemgroup", checks: "check", costcentres: "costcentre", ownaccounts: "ownaccount", documentboxes: "documentbox", hourrates: "hourrate", services: "service", customerspaces: "customerspace", customerinventoryitems: "customerinventoryitem", memberrelations: "memberrelation", } const parseId = (value: string) => { if (/^\d+$/.test(value)) return Number(value) return value } export default async function resourceHistoryRoutes(server: FastifyInstance) { server.get("/history", { schema: { tags: ["History"], summary: "Get all history entries for the active tenant", }, }, async (req: any) => { const data = await server.db .select() .from(historyitems) .where(eq(historyitems.tenant, req.user?.tenant_id)) .orderBy(asc(historyitems.createdAt)); const userIds = Array.from( new Set(data.map((item) => item.createdBy).filter(Boolean)) ) as string[]; const profiles = userIds.length > 0 ? await server.db .select() .from(authProfiles) .where(and( eq(authProfiles.tenant_id, req.user?.tenant_id), inArray(authProfiles.user_id, userIds) )) : []; const profileByUserId = new Map( profiles.map((profile) => [profile.user_id, profile]) ); return data.map((historyitem) => ({ ...historyitem, created_at: historyitem.createdAt, created_by: historyitem.createdBy, created_by_profile: historyitem.createdBy ? profileByUserId.get(historyitem.createdBy) || null : null, })); }); server.get<{ Params: { resource: string; id: string } }>("/resource/:resource/:id/history", { schema: { tags: ["History"], summary: "Get history entries for a resource", params: { type: "object", required: ["resource", "id"], properties: { resource: { type: "string" }, id: { type: "string" }, }, }, }, }, async (req, reply) => { const { resource, id } = req.params; const column = columnMap[resource]; if (!column) { return reply.code(400).send({ error: `History not supported for resource '${resource}'` }); } const data = await server.db .select() .from(historyitems) .where(eq(column, parseId(id))) .orderBy(asc(historyitems.createdAt)); const userIds = Array.from( new Set(data.map((item) => item.createdBy).filter(Boolean)) ) as string[] const profiles = userIds.length > 0 ? await server.db .select() .from(authProfiles) .where(and( eq(authProfiles.tenant_id, req.user?.tenant_id), inArray(authProfiles.user_id, userIds) )) : [] const profileByUserId = new Map( profiles.map((profile) => [profile.user_id, profile]) ) const dataCombined = data.map((historyitem) => ({ ...historyitem, created_at: historyitem.createdAt, created_by: historyitem.createdBy, created_by_profile: historyitem.createdBy ? profileByUserId.get(historyitem.createdBy) || null : null, })) return dataCombined; }); // Neuen HistoryItem anlegen server.post<{ Params: { resource: string; id: string }; Body: { text: string; old_val?: string | null; new_val?: string | null; config?: Record; }; }>("/resource/:resource/:id/history", { schema: { tags: ["History"], summary: "Create new history entry", params: { type: "object", properties: { resource: { type: "string" }, id: { type: "string" } }, required: ["resource", "id"] }, body: { type: "object", properties: { text: { type: "string" }, old_val: { type: "string", nullable: true }, new_val: { type: "string", nullable: true }, config: { type: "object", nullable: true } }, required: ["text"] }, response: { 201: { type: "object", properties: { id: { type: "number" }, text: { type: "string" }, created_at: { type: "string" }, created_by: { type: "string" } } } } } }, async (req, reply) => { const { resource, id } = req.params; const { text, old_val, new_val, config } = req.body; const userId = (req.user as any)?.user_id; const fkField = insertFieldMap[resource]; if (!fkField) { return reply.code(400).send({ error: `Unknown resource: ${resource}` }); } const inserted = await server.db .insert(historyitems) .values({ text, [fkField]: parseId(id), oldVal: old_val || null, newVal: new_val || null, config: config || null, tenant: (req.user as any)?.tenant_id, createdBy: userId }) .returning() const data = inserted[0] if (!data) { return reply.code(500).send({ error: "Failed to create history entry" }); } return reply.code(201).send({ ...data, created_at: data.createdAt, created_by: data.createdBy }); }); }