import type { FastifyInstance } from "fastify" import { eq } from "drizzle-orm" import { authProfiles, authRefreshTokens, authUsers, m2mApiKeys } from "../../db/schema" import { requireSystemAdmin } from "../utils/requireSystemAdmin" export default async function adminUserArchiveRoutes(server: FastifyInstance) { server.post("/admin/users/:user_id/archive", { schema: { params: { type: "object", required: ["user_id"], properties: { user_id: { type: "string", format: "uuid" } }, }, }, }, async (req, reply) => { const currentUser = await requireSystemAdmin(server, req, reply) if (!currentUser) return const { user_id } = req.params as { user_id: string } if (user_id === currentUser.id) { return reply.code(409).send({ error: "Das eigene Benutzerkonto kann nicht archiviert werden." }) } const user = await server.db.transaction(async (tx) => { // Serialize repeated archive requests and keep the original archive date. const [target] = await tx.select({ id: authUsers.id, archived_at: authUsers.archived_at }) .from(authUsers).where(eq(authUsers.id, user_id)).limit(1).for("update") if (!target) return null const archivedAt = target.archived_at || new Date() const [archivedUser] = await tx.update(authUsers) .set({ archived_at: archivedAt, updatedAt: archivedAt }) .where(eq(authUsers.id, user_id)) .returning({ id: authUsers.id, archived_at: authUsers.archived_at }) // Keep links intact so existing documents and time records retain their authors. await tx.update(authProfiles) .set({ active: false, archived_at: archivedAt, token_id: null, calendar_subscription_token: null }) .where(eq(authProfiles.user_id, user_id)) await tx.update(authRefreshTokens).set({ revokedAt: archivedAt }) .where(eq(authRefreshTokens.userId, user_id)) await tx.update(m2mApiKeys).set({ active: false, updatedAt: archivedAt }) .where(eq(m2mApiKeys.userId, user_id)) return archivedUser }) if (!user) return reply.code(404).send({ error: "Benutzer nicht gefunden." }) return { user } }) }