239 lines
7.5 KiB
TypeScript
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
|
|
});
|
|
});
|
|
}
|