// /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 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 = {} 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 }, // 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 }) } }