134 lines
4.3 KiB
TypeScript
134 lines
4.3 KiB
TypeScript
// /app/api/matches/[id]/mapvote/reset/route.ts
|
||
import { NextRequest, NextResponse } from 'next/server'
|
||
import { getServerSession } from 'next-auth'
|
||
import { authOptions } from '@/app/lib/auth'
|
||
import { prisma } from '@/app/lib/prisma'
|
||
import { sendServerSSEMessage } from '@/app/lib/sse-server-client'
|
||
import { MAP_OPTIONS } from '@/app/lib/mapOptions'
|
||
import { createHash } from 'crypto'
|
||
|
||
export const runtime = 'nodejs'
|
||
export const dynamic = 'force-dynamic'
|
||
|
||
function shapeState(vote: any) {
|
||
const ACTION_MAP = { BAN: 'ban', PICK: 'pick', DECIDER: 'decider' } as const
|
||
const steps = [...vote.steps]
|
||
.sort((a, b) => a.order - b.order)
|
||
.map((s: any) => ({
|
||
order : s.order,
|
||
action : ACTION_MAP[s.action as keyof typeof ACTION_MAP],
|
||
teamId : s.teamId,
|
||
map : s.map,
|
||
chosenAt: s.chosenAt ? s.chosenAt.toISOString() : null,
|
||
chosenBy: s.chosenBy ?? null,
|
||
}))
|
||
|
||
return {
|
||
bestOf : vote.bestOf,
|
||
mapPool : vote.mapPool as string[],
|
||
currentIndex: vote.currentIdx,
|
||
locked : vote.locked as boolean,
|
||
opensAt : vote.opensAt ? new Date(vote.opensAt).toISOString() : null,
|
||
steps,
|
||
adminEdit: vote.adminEditingBy
|
||
? {
|
||
enabled: true,
|
||
by: vote.adminEditingBy as string,
|
||
since: vote.adminEditingSince ? new Date(vote.adminEditingSince).toISOString() : null,
|
||
}
|
||
: { enabled: false, by: null, since: null },
|
||
}
|
||
}
|
||
|
||
function buildMapVisuals(matchId: string, mapPool: string[]) {
|
||
const visuals: Record<string, { label: string; bg: string; images?: string[] }> = {}
|
||
for (const key of mapPool) {
|
||
const opt = MAP_OPTIONS.find(o => o.key === key)
|
||
const label = opt?.label ?? key
|
||
const imgs = opt?.images ?? []
|
||
let bg = `/assets/img/maps/${key}/1.jpg`
|
||
|
||
if (imgs.length > 0) {
|
||
const h = createHash('sha256').update(`${matchId}:${key}`).digest('hex')
|
||
const n = parseInt(h.slice(0, 8), 16)
|
||
const idx = n % imgs.length
|
||
bg = imgs[idx]
|
||
}
|
||
|
||
visuals[key] = { label, bg }
|
||
}
|
||
return visuals
|
||
}
|
||
|
||
export async function POST(req: NextRequest, { params }: { params: { matchId: string } }) {
|
||
const session = await getServerSession(authOptions(req))
|
||
const me = session?.user as { steamId: string; isAdmin?: boolean } | undefined
|
||
if (!me?.steamId) return NextResponse.json({ message: 'Nicht eingeloggt' }, { status: 401 })
|
||
|
||
const matchId = params.matchId
|
||
if (!matchId) return NextResponse.json({ message: 'Missing id' }, { status: 400 })
|
||
|
||
try {
|
||
const match = await prisma.match.findUnique({
|
||
where: { id: matchId },
|
||
include: { mapVote: { include: { steps: true } } },
|
||
})
|
||
if (!match?.mapVote) {
|
||
return NextResponse.json({ message: 'Map-Vote nicht gefunden' }, { status: 404 })
|
||
}
|
||
|
||
// optional: nur Admins erlauben – falls gewünscht:
|
||
// if (!me.isAdmin) return NextResponse.json({ message: 'Nur Admins dürfen resetten' }, { status: 403 })
|
||
|
||
const vote = match.mapVote
|
||
|
||
// Reset in einer Transaktion
|
||
await prisma.$transaction(async (tx) => {
|
||
// alle Steps zurücksetzen
|
||
await Promise.all(
|
||
vote.steps.map(s =>
|
||
tx.mapVoteStep.update({
|
||
where: { id: s.id },
|
||
data : { map: null, chosenAt: null, chosenBy: null },
|
||
})
|
||
)
|
||
)
|
||
|
||
// Vote Zustand zurücksetzen
|
||
await tx.mapVote.update({
|
||
where: { id: vote.id },
|
||
data : {
|
||
currentIdx: 0,
|
||
locked: false,
|
||
adminEditingBy: null,
|
||
adminEditingSince: null,
|
||
},
|
||
})
|
||
})
|
||
|
||
const updated = await prisma.mapVote.findUnique({
|
||
where: { id: vote.id },
|
||
include: { steps: true },
|
||
})
|
||
if (!updated) {
|
||
return NextResponse.json({ message: 'Reset fehlgeschlagen' }, { status: 500 })
|
||
}
|
||
|
||
// Events: zuerst map-vote-updated (UI neu laden)
|
||
await sendServerSSEMessage({ type: 'map-vote-updated', matchId })
|
||
|
||
// dann map-vote-reset (globale Overlays schließen, lokale Flags löschen)
|
||
await sendServerSSEMessage({ type: 'map-vote-reset', matchId })
|
||
|
||
const mapVisuals = buildMapVisuals(match.id, updated.mapPool)
|
||
|
||
return NextResponse.json(
|
||
{ ...shapeState(updated), mapVisuals },
|
||
{ headers: { 'Cache-Control': 'no-store' } },
|
||
)
|
||
} catch (e) {
|
||
console.error('[map-vote-reset][POST] error', e)
|
||
return NextResponse.json({ message: 'Reset fehlgeschlagen' }, { status: 500 })
|
||
}
|
||
}
|