import { redirect } from "next/navigation"; import type { ReactNode } from "react"; import { HireWizardProvider } from "@/components/hire/HireWizardContext"; import { AppShell } from "@/components/shell/AppShell"; import { currentUserId } from "@/lib/auth/session"; import { withUser } from "@/lib/db"; import { loadOpenNotes } from "@/lib/notes"; import { loadOpenPositions } from "@/lib/positions"; export default async function AppLayout({ children }: { children: ReactNode }) { const userId = await currentUserId(); if (!userId) redirect("/login"); // Alles in *einer* Transaktion, weil nur dort der Sitzungskontext gilt — // und damit nebenbei auf einem einheitlichen Lesestand. const data = await withUser(userId, async (tx) => { // Defense in depth: proxy.ts already redirects any non-active-HR session // away before this layout ever renders. Re-checking here means a gap in // the proxy matcher (or a future route added outside it) still fails // closed instead of silently granting access — see docs/security.md. const profile = await tx .selectFrom("profiles") .select(["full_name", "email", "role", "is_active"]) .where("id", "=", userId) .executeTakeFirst(); if (profile?.role !== "hr" || profile?.is_active !== true) return null; const [openPositions, locations, drafts, openNotes] = await Promise.all([ loadOpenPositions(tx), tx.selectFrom("locations").select(["id", "name", "country"]).orderBy("name").execute(), tx .selectFrom("hire_drafts") .select(["id", "step", "payload", "updated_at"]) .where("created_by", "=", userId) .orderBy("updated_at", "desc") .execute(), loadOpenNotes(tx), ]); return { profile, openPositions, locations, drafts, openNotes }; }); if (!data) redirect("/login"); const userLabel = data.profile.full_name || data.profile.email || ""; return ( {children} ); }