import type { AuthInvite, AuthInviteEvent } from "@prisma/client" import { ROLE_OPTIONS, type RoleOption, normalizeRole } from "@/lib/authz" import { DEFAULT_TENANT_ID } from "@/lib/constants" import { env } from "@/lib/env" export type InviteStatus = "pending" | "accepted" | "revoked" | "expired" export type InviteWithEvents = AuthInvite & { events: AuthInviteEvent[] } export type InviteEventPayload = { id: string type: string createdAt: string actorId: string | null payload: unknown } export type NormalizedInvite = { id: string email: string name: string | null role: RoleOption tenantId: string status: InviteStatus token: string inviteUrl: string expiresAt: string createdAt: string createdById: string | null acceptedAt: string | null acceptedById: string | null revokedAt: string | null revokedById: string | null revokedReason: string | null events: InviteEventPayload[] } const DEFAULT_APP_URL = env.NEXT_PUBLIC_APP_URL ?? env.BETTER_AUTH_URL ?? "http://localhost:3000" export function computeInviteStatus(invite: AuthInvite, now: Date = new Date()): InviteStatus { if (invite.status === "revoked") return "revoked" if (invite.status === "accepted") return "accepted" if (invite.status === "expired") return "expired" if (invite.expiresAt.getTime() <= now.getTime()) { return "expired" } return "pending" } export function buildInviteUrl(token: string) { const base = DEFAULT_APP_URL.endsWith("/") ? DEFAULT_APP_URL.slice(0, -1) : DEFAULT_APP_URL return `${base}/invite/${token}` } export function normalizeRoleOption(role?: string | null): RoleOption { const normalized = normalizeRole(role) if (normalized && (ROLE_OPTIONS as readonly string[]).includes(normalized)) { return normalized as RoleOption } return "agent" } export function normalizeInvite(invite: InviteWithEvents, now: Date = new Date()): NormalizedInvite { const status = computeInviteStatus(invite, now) const inviteUrl = buildInviteUrl(invite.token) return { id: invite.id, email: invite.email, name: invite.name ?? null, role: normalizeRoleOption(invite.role), tenantId: invite.tenantId ?? DEFAULT_TENANT_ID, status, token: invite.token, inviteUrl, expiresAt: invite.expiresAt.toISOString(), createdAt: invite.createdAt.toISOString(), createdById: invite.createdById ?? null, acceptedAt: invite.acceptedAt ? invite.acceptedAt.toISOString() : null, acceptedById: invite.acceptedById ?? null, revokedAt: invite.revokedAt ? invite.revokedAt.toISOString() : null, revokedById: invite.revokedById ?? null, revokedReason: invite.revokedReason ?? null, events: invite.events .slice() .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()) .map((event) => ({ id: event.id, type: event.type, createdAt: event.createdAt.toISOString(), actorId: event.actorId ?? null, payload: event.payload, })), } }