Files
FEDEO/backend/src/routes/history.ts
2026-02-21 22:17:58 +01:00

239 lines
7.5 KiB
TypeScript

// 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<string, any> = {
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<string, string> = {
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<string, any>;
};
}>("/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
});
});
}