"use server" import { and, eq, ne } from "drizzle-orm" import { db } from "@/lib/db" import { account_members } from "@/lib/db/schema" import { getSessionUser } from "@/lib/session" export type AcceptInviteResult = | { ok: true } | { ok: false; error: string } /** * Accept a team invite by token. * * Requires an authenticated session (the page redirects to /login first). * On success sets member_id = current user, status='active', accepted_at=now. * A user can be an active member of at most one account, so we block if the * caller is already active somewhere else. Handles invalid / already-used / * revoked tokens gracefully. */ export async function acceptInvite(token: string): Promise { const user = await getSessionUser() if (!user) return { ok: false, error: "You must be signed in to accept an invite." } const invite = await db.query.account_members.findFirst({ where: eq(account_members.invite_token, token), }) if (!invite) { return { ok: false, error: "This invite link is invalid or has expired." } } if (invite.status === "revoked") { return { ok: false, error: "This invite has been revoked by the account owner." } } // Already accepted — by this user (fine, treat as success) or someone else. if (invite.status === "active") { if (invite.member_id === user.id) return { ok: true } return { ok: false, error: "This invite has already been accepted." } } // Can't be a member of your own account. if (invite.owner_id === user.id) { return { ok: false, error: "You can't accept an invite to your own account." } } // A user may be an active member of at most one account. const existingMembership = await db.query.account_members.findFirst({ where: and( eq(account_members.member_id, user.id), eq(account_members.status, "active"), ne(account_members.id, invite.id) ), }) if (existingMembership) { return { ok: false, error: "You're already an active member of another account. Leave it before joining a new one.", } } try { // Guard the update on status='pending' so two concurrent accepts can't both win. const [updated] = await db .update(account_members) .set({ member_id: user.id, status: "active", accepted_at: new Date().toISOString(), }) .where(and(eq(account_members.id, invite.id), eq(account_members.status, "pending"))) .returning({ id: account_members.id }) if (!updated) { return { ok: false, error: "This invite is no longer available." } } } catch { return { ok: false, error: "Something went wrong accepting the invite. Please try again." } } return { ok: true } }