feat(households): hane rolleri, servis, DAL hane kapsamı ve denetim kaydı
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,10 @@ 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";
|
||||
@@ -66,3 +70,67 @@ export async function getActionUser(role?: SystemRole): Promise<SessionUser | nu
|
||||
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<HouseholdContext | null> => {
|
||||
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<ActiveHousehold> {
|
||||
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<ActiveHousehold | null> {
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user