Files
FEDEO/backend/src/routes/profiles.ts
florianfederspiel 849e24092e
All checks were successful
Build and Push Docker Images / build-backend (push) Successful in 26s
Build and Push Docker Images / build-frontend (push) Successful in 1m7s
Added Teams
Minor Rework of Plantafel
2026-04-14 21:17:05 +02:00

163 lines
5.5 KiB
TypeScript

import { FastifyInstance } from "fastify";
import { eq, and } from "drizzle-orm";
import {
authProfiles,
} from "../../db/schema";
import {
loadProfileWithBranches,
resolveTenantBranchIds,
syncProfileBranches,
} from "../utils/profileBranches";
import {
enrichProfilesWithTeams,
resolveTenantTeamIds,
syncProfileTeams,
} from "../utils/profileTeams";
export default async function authProfilesRoutes(server: FastifyInstance) {
// -------------------------------------------------------------
// GET SINGLE PROFILE
// -------------------------------------------------------------
server.get("/profiles/:id", async (req, reply) => {
try {
const { id } = req.params as { id: string };
const tenantId = (req.user as any)?.tenant_id;
if (!tenantId) {
return reply.code(400).send({ error: "No tenant selected" });
}
const profileWithBranches = await loadProfileWithBranches(server, id, tenantId)
const [profile] = profileWithBranches
? await enrichProfilesWithTeams(server, [profileWithBranches])
: [null]
if (!profile) {
return reply.code(404).send({ error: "User not found or not in tenant" });
}
return profile;
} catch (error) {
console.error("GET /profiles/:id ERROR:", error);
return reply.code(500).send({ error: "Internal Server Error" });
}
});
function sanitizeProfileUpdate(body: any) {
const cleaned: any = { ...body }
// ❌ Systemfelder entfernen
const forbidden = [
"id", "user_id", "tenant_id", "created_at", "updated_at",
"updatedAt", "updatedBy", "old_profile_id", "full_name",
"branch"
]
forbidden.forEach(f => delete cleaned[f])
// ❌ Falls NULL Strings vorkommen → in null umwandeln
for (const key of Object.keys(cleaned)) {
if (cleaned[key] === "") cleaned[key] = null
}
// ✅ Date-Felder sauber konvertieren, falls vorhanden
const dateFields = ["birthday", "entry_date"]
for (const field of dateFields) {
if (cleaned[field]) {
const d = new Date(cleaned[field])
if (!isNaN(d.getTime())) cleaned[field] = d
else delete cleaned[field] // invalid → entfernen
}
}
return cleaned
}
// -------------------------------------------------------------
// UPDATE PROFILE
// -------------------------------------------------------------
server.put("/profiles/:id", async (req, reply) => {
try {
const tenantId = req.user?.tenant_id
const userId = req.user?.user_id
if (!tenantId) {
return reply.code(400).send({ error: "No tenant selected" })
}
const { id } = req.params as { id: string }
let body = req.body as any
// Clean + Normalize
body = sanitizeProfileUpdate(body)
const { primaryBranchId, branchIds } = await resolveTenantBranchIds(
server,
tenantId,
[
...(Array.isArray(body.branch_ids) ? body.branch_ids : []),
...(Array.isArray(body.branches) ? body.branches : []),
],
body.branch_id ?? body.branch?.id ?? null
)
const teamIds = await resolveTenantTeamIds(
server,
tenantId,
[
...(Array.isArray(body.team_ids) ? body.team_ids : []),
...(Array.isArray(body.teams) ? body.teams : []),
],
)
delete body.branch_ids
delete body.branches
delete body.team_ids
delete body.teams
const updateData = {
...body,
branch_id: primaryBranchId,
updatedAt: new Date(),
updatedBy: userId
}
const updated = await server.db
.update(authProfiles)
.set(updateData)
.where(
and(
eq(authProfiles.id, id),
eq(authProfiles.tenant_id, tenantId)
)
)
.returning()
if (!updated.length) {
return reply.code(404).send({ error: "User not found or not in tenant" })
}
await syncProfileBranches(server, id, branchIds, userId)
await syncProfileTeams(server, id, teamIds, userId)
const profileWithBranches = await loadProfileWithBranches(server, id, tenantId)
const [profile] = profileWithBranches
? await enrichProfilesWithTeams(server, [profileWithBranches])
: [null]
return profile || updated[0]
} catch (err) {
console.error("PUT /profiles/:id ERROR:", err)
if (err instanceof Error && ["INVALID_BRANCH_SELECTION", "INVALID_PRIMARY_BRANCH"].includes(err.message)) {
return reply.code(400).send({ error: "Ungültige Niederlassungsauswahl" })
}
if (err instanceof Error && err.message === "INVALID_TEAM_SELECTION") {
return reply.code(400).send({ error: "Ungültige Teamauswahl" })
}
return reply.code(500).send({ error: "Internal Server Error" })
}
})
}