98 lines
2.9 KiB
TypeScript
98 lines
2.9 KiB
TypeScript
import type { AuthInvite, AuthInviteEvent } from "@/lib/prisma"
|
|
|
|
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,
|
|
})),
|
|
}
|
|
}
|