93 lines
3 KiB
TypeScript
93 lines
3 KiB
TypeScript
import { NextResponse } from "next/server"
|
|
|
|
import { ConvexHttpClient } from "convex/browser"
|
|
|
|
// @ts-expect-error Convex runtime API lacks generated types at build time in Next routes
|
|
import { api } from "@/convex/_generated/api"
|
|
import { assertAdminSession } from "@/lib/auth-server"
|
|
import { env } from "@/lib/env"
|
|
import { prisma } from "@/lib/prisma"
|
|
import { computeInviteStatus, normalizeInvite, type NormalizedInvite } from "@/server/invite-utils"
|
|
|
|
type RevokePayload = {
|
|
reason?: string
|
|
}
|
|
|
|
async function syncInvite(invite: NormalizedInvite) {
|
|
const convexUrl = env.NEXT_PUBLIC_CONVEX_URL
|
|
if (!convexUrl) return
|
|
const client = new ConvexHttpClient(convexUrl)
|
|
await client.mutation(api.invites.sync, {
|
|
tenantId: invite.tenantId,
|
|
inviteId: invite.id,
|
|
email: invite.email,
|
|
name: invite.name ?? undefined,
|
|
role: invite.role.toUpperCase(),
|
|
status: invite.status,
|
|
token: invite.token,
|
|
expiresAt: Date.parse(invite.expiresAt),
|
|
createdAt: Date.parse(invite.createdAt),
|
|
createdById: invite.createdById ?? undefined,
|
|
acceptedAt: invite.acceptedAt ? Date.parse(invite.acceptedAt) : undefined,
|
|
acceptedById: invite.acceptedById ?? undefined,
|
|
revokedAt: invite.revokedAt ? Date.parse(invite.revokedAt) : undefined,
|
|
revokedById: invite.revokedById ?? undefined,
|
|
revokedReason: invite.revokedReason ?? undefined,
|
|
})
|
|
}
|
|
|
|
export async function PATCH(request: Request, { params }: { params: { id: string } }) {
|
|
const session = await assertAdminSession()
|
|
if (!session) {
|
|
return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
|
}
|
|
|
|
const body = (await request.json().catch(() => null)) as Partial<RevokePayload> | null
|
|
const reason = typeof body?.reason === "string" && body.reason.trim() ? body.reason.trim() : null
|
|
|
|
const invite = await prisma.authInvite.findUnique({
|
|
where: { id: params.id },
|
|
include: { events: { orderBy: { createdAt: "asc" } } },
|
|
})
|
|
|
|
if (!invite) {
|
|
return NextResponse.json({ error: "Convite não encontrado" }, { status: 404 })
|
|
}
|
|
|
|
const now = new Date()
|
|
const status = computeInviteStatus(invite, now)
|
|
|
|
if (status === "accepted") {
|
|
return NextResponse.json({ error: "Convite já aceito" }, { status: 400 })
|
|
}
|
|
|
|
if (status === "revoked") {
|
|
const normalized = normalizeInvite(invite, now)
|
|
await syncInvite(normalized)
|
|
return NextResponse.json({ invite: normalized })
|
|
}
|
|
|
|
const updated = await prisma.authInvite.update({
|
|
where: { id: invite.id },
|
|
data: {
|
|
status: "revoked",
|
|
revokedAt: now,
|
|
revokedById: session.user.id ?? null,
|
|
revokedReason: reason,
|
|
},
|
|
})
|
|
|
|
const event = await prisma.authInviteEvent.create({
|
|
data: {
|
|
inviteId: invite.id,
|
|
type: "revoked",
|
|
payload: reason ? { reason } : null,
|
|
actorId: session.user.id ?? null,
|
|
},
|
|
})
|
|
|
|
const normalized = normalizeInvite({ ...updated, events: [...invite.events, event] }, now)
|
|
await syncInvite(normalized)
|
|
|
|
return NextResponse.json({ invite: normalized })
|
|
}
|