import { and, eq } from "drizzle-orm" import { db } from "@/lib/db" import { account_members } from "@/lib/db/schema" export type AccountRole = "owner" | "member" | "viewer" export type AccountContext = { /** The logged-in user. */ userId: string /** Whose portfolio the user operates on — themselves if they're an owner. */ ownerId: string role: AccountRole isOwner: boolean /** Owners and members can write; viewers are read-only. */ canWrite: boolean } /** * Resolve the account a user operates under (TEAM ACCESS). * * If the user is an ACTIVE member of another owner's account, they act on that * owner's data; otherwise they own their own account. A user can be an active * member of at most one account. This is the single source of truth for team * scoping — data queries must scope by `ownerId`, not the raw session user id. * * Fails safe: on any error it returns the user as their own owner (they only * ever see their own data), never someone else's. */ export async function getAccountContext(userId: string): Promise { try { const membership = await db.query.account_members.findFirst({ where: and(eq(account_members.member_id, userId), eq(account_members.status, "active")), }) if (membership) { const role: AccountRole = membership.role === "viewer" ? "viewer" : "member" return { userId, ownerId: membership.owner_id, role, isOwner: false, canWrite: role !== "viewer", } } } catch { // fall through to self-owned context } return { userId, ownerId: userId, role: "owner", isOwner: true, canWrite: true } } /** The user_id that data queries/ownership checks should be scoped by. */ export async function getEffectiveOwnerId(userId: string): Promise { return (await getAccountContext(userId)).ownerId }