2025-08-17 23:22:06 +02:00

152 lines
5.2 KiB
TypeScript

// /app/api/matches/[id]/export-to-sftp/route.ts
import { NextResponse } from 'next/server'
import { randomBytes } from 'crypto'
export const runtime = 'nodejs' // KEIN edge
export const dynamic = 'force-dynamic'
type MapVoteStep = {
action: 'ban' | 'pick' | 'decider'
map?: string | null
teamId?: string | null
}
type MapVoteState = {
bestOf?: number
steps: MapVoteStep[]
mapVisuals?: Record<string, { label?: string; bg?: string }>
teams?: {
teamA?: { id?: string | null, name?: string | null, players?: Array<{ steamId: string, name?: string | null }> }
teamB?: { id?: string | null, name?: string | null, players?: Array<{ steamId: string, name?: string | null }> }
}
locked?: boolean
}
type PlayerLike = { user?: { steamId: string, name?: string | null }, steamId?: string, name?: string | null }
type MatchLike = {
id: string | number
bestOf?: number
teamA?: { name?: string | null, players?: PlayerLike[] | any[] }
teamB?: { name?: string | null, players?: PlayerLike[] | any[] }
}
function sanitizeFilePart(s?: string | null) {
return (s ?? 'team').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '')
}
function playersMapFromList(list: PlayerLike[] | any[] | undefined) {
const out: Record<string, string> = {}
for (const p of list ?? []) {
const sid = (p?.user?.steamId ?? (p as any)?.steamId) as string | undefined
if (!sid) continue
const name = (p?.user?.name ?? p?.name ?? 'Player') as string
out[sid] = name
}
return out
}
function toDeMapName(key: string) {
if (key.startsWith('de_')) return key
return `de_${key}`
}
function buildMatchJson(match: MatchLike, state: MapVoteState) {
const bestOf = match.bestOf ?? state.bestOf ?? 3
// Nur Picks + Decider in Reihenfolge (ohne Lücken)
const chosen = (state.steps ?? []).filter(s => (s.action === 'pick' || s.action === 'decider') && s.map)
// maplist
const maplist = chosen.slice(0, bestOf).map(s => toDeMapName(s.map!))
// einfache Sides-Logik (Beispiel): first two maps start CT for Team1/Team2, last knife
const map_sides = maplist.map((_, i) => {
if (i === maplist.length - 1) return 'knife'
return i % 2 === 0 ? 'team1_ct' : 'team2_ct'
})
const team1Name = match.teamA?.name ?? 'Team_1'
const team2Name = match.teamB?.name ?? 'Team_2'
const team1Players = playersMapFromList(match.teamA?.players)
const team2Players = playersMapFromList(match.teamB?.players)
const payload = {
matchid: Number(match.id) || 0,
team1: { name: team1Name, players: team1Players },
team2: { name: team2Name, players: team2Players },
num_maps: bestOf,
maplist,
map_sides,
spectators: { players: {} as Record<string, string> }, // optional
clinch_series: true,
players_per_team: 5,
cvars: {
hostname: `Iron:e Open 4 | ${team1Name} vs ${team2Name}`,
mp_friendlyfire: '1',
},
}
return payload
}
export async function POST(req: Request, { params }: { params: { matchId: string } }) {
try {
const SFTPClient = (await import('ssh2-sftp-client')).default // dyn. import
const { match, state } = (await req.json()) as { match: MatchLike, state: MapVoteState }
if (!match || !state?.locked) {
return NextResponse.json({ error: 'Ungültige Daten oder Voting nicht abgeschlossen.' }, { status: 400 })
}
const bestOf = match.bestOf ?? state.bestOf ?? 3
const chosen = (state.steps ?? []).filter(s => (s.action === 'pick' || s.action === 'decider') && s.map)
if (chosen.length < bestOf) {
return NextResponse.json({ error: 'Es sind noch nicht alle Maps gewählt.' }, { status: 400 })
}
// JSON bauen
const json = buildMatchJson(match, state)
const jsonStr = JSON.stringify(json, null, 2)
// Dateiname (wie gewünscht)
const team1 = sanitizeFilePart(match.teamA?.name)
const team2 = sanitizeFilePart(match.teamB?.name)
const rid = randomBytes(3).toString('hex') // 6 hex chars
const filename = `team_${team1}_vs_team_${team2}_${rid}.json`
// SFTP Credentials
const url = process.env.PTERO_SERVER_SFTP_URL || '' // z.B. "sftp://your.host:22" oder "your.host:22" oder nur Host
const user = process.env.PTERO_SERVER_SFTP_USER
const pass = process.env.PTERO_SERVER_SFTP_PASSWORD
if (!url || !user || !pass) {
return NextResponse.json({ error: 'SFTP-Umgebungsvariablen fehlen.' }, { status: 500 })
}
// host/port extrahieren
let host = url
let port = 22
try {
const u = new URL(url.includes('://') ? url : `sftp://${url}`)
host = u.hostname
port = Number(u.port) || 22
} catch {
// Fallback: "host:port" oder nur host
const [h, p] = url.split(':')
host = h ?? url
port = p ? Number(p) : 22
}
// Upload
const sftp = new SFTPClient()
await sftp.connect({ host, port, username: user, password: pass })
const remotePath = `/game/csgo/${filename}`
await sftp.put(Buffer.from(jsonStr, 'utf8'), remotePath)
await sftp.end()
return NextResponse.json({ ok: true, remotePath, filename })
} catch (err: any) {
console.error('[export-to-sftp] error:', err)
return NextResponse.json({ error: err?.message ?? 'Upload fehlgeschlagen' }, { status: 500 })
}
}