import "server-only"; import { headers } from "next/headers"; import { notFound, redirect } from "next/navigation"; import { cache } from "react"; import type { SessionUser } from "@/features/auth/types"; import { hasHouseholdRole, pickActiveHousehold } from "@/features/households/roles"; import { listMemberships, resolveMembership, type Membership } from "@/features/households/service"; import type { HouseholdRole, HouseholdSummary } from "@/features/households/types"; import { readActiveHouseholdId } from "@/lib/active-household"; import { getAuth } from "@/lib/auth"; import { visibleEmail } from "@/shared/account-email"; import type { SystemRole } from "@/shared/schemas/common"; /** * Data Access Layer — her korumalı sayfa, layout, Server Action ve Route Handler oturumu * BURADAN okur. proxy.ts yalnızca iyimser (cookie var mı?) kontrol yapar; güvenlik bu katmandadır. */ type RawUser = { id: string; name: string; email: string; username?: string | null; role?: string | null; banned?: boolean | null; image?: string | null; }; export function toSessionUser(user: RawUser): SessionUser { return { id: user.id, name: user.name, username: user.username ?? "", email: visibleEmail(user.email), role: user.role === "admin" ? "admin" : "user", image: user.image ?? undefined, }; } /** İstek başına bir kez çalışır (React cache). Oturum yoksa veya kullanıcı askıdaysa null. */ export const getCurrentUser = cache(async (): Promise => { // headers() önce çağrılır: Cache Components prerender'ı burada durur, auth hiç oluşturulmaz. const requestHeaders = await headers(); const session = await getAuth().api.getSession({ headers: requestHeaders }); if (!session || session.user.banned) return null; return toSessionUser(session.user); }); /** Sayfa/layout: oturum yoksa /login'e yönlendirir. */ export async function requireSession(): Promise { const user = await getCurrentUser(); if (!user) redirect("/login"); return user; } /** Sayfa/layout: rol yoksa 404 (sayfanın varlığı bile gösterilmez). */ export async function requireRole(role: SystemRole): Promise { const user = await requireSession(); if (user.role !== role) notFound(); return user; } /** * Server Action / Route Handler: yönlendirme yapmaz, yetkisizse null döner * (çağıran `fail(...)` veya 401/403 döndürür). */ export async function getActionUser(role?: SystemRole): Promise { const user = await getCurrentUser(); if (!user) return null; if (role && user.role !== role) return null; return user; } // --- Hane kapsamı (veri izolasyonu) --- export type HouseholdContext = { user: SessionUser; /** Aktif hane; kullanıcı hiçbir haneye üye değilse null. */ household: HouseholdSummary | null; memberships: HouseholdSummary[]; }; /** * Oturum + kullanıcının haneleri + aktif hane (istek başına bir kez). Cookie'deki hane * kullanıcının üyesi olduğu bir hane değilse yok sayılır ve ilk haneye düşülür. * Sistem admini için ayrıcalık YOKTUR: yalnızca üyesi olduğu haneler görünür. */ export const getHouseholdContext = cache(async (): Promise => { const user = await getCurrentUser(); if (!user) return null; const [memberships, preferred] = await Promise.all([ listMemberships(user.id), readActiveHouseholdId(), ]); const household = pickActiveHousehold(memberships, preferred); return { user, household, memberships }; }); export type ActiveHousehold = HouseholdContext & { household: HouseholdSummary }; /** * Sayfa: aktif hanede en az `minRole` yetkisi. Oturum yoksa /login, hanesi yoksa /hane-yok, * yetkisi yetmiyorsa 404. */ export async function requireActiveHousehold( minRole: HouseholdRole = "viewer", ): Promise { const ctx = await getHouseholdContext(); if (!ctx) redirect("/login"); if (!ctx.household) redirect("/hane-yok"); if (!hasHouseholdRole(ctx.household.role, minRole)) notFound(); return ctx as ActiveHousehold; } /** Server Action: aktif hanede en az `minRole` yetkisi; yoksa null (yönlendirme yapmaz). */ export async function getHouseholdActionContext( minRole: HouseholdRole = "viewer", ): Promise { const ctx = await getHouseholdContext(); if (!ctx?.household || !hasHouseholdRole(ctx.household.role, minRole)) return null; return ctx as ActiveHousehold; } /** * Belirli bir hane için üyelik kontrolü (ör. URL'den veya istek gövdesinden gelen householdId). * Üye değilse veya yetkisi yetmiyorsa null — çağıran 404/403 döner. */ export async function requireHouseholdMember( householdId: string, minRole: HouseholdRole = "viewer", ): Promise<{ user: SessionUser; membership: Membership } | null> { const user = await getCurrentUser(); if (!user) return null; const membership = await resolveMembership(user.id, householdId, minRole); return membership ? { user, membership } : null; }