import { Router, Request, Response } from 'express'; import { query } from '../db/client'; import { logger } from '../services/logger'; const router = Router(); router.get('/', async (req: Request, res: Response): Promise => { const userId = req.staffbaseUser?.sub; if (!userId) { res.status(401).json({ error: 'Unauthorized' }); return; } try { // 1. Hero: next upcoming match const heroResult = await query( `SELECT m.id, m.utc_date, m.status, m.home_team_name, m.home_team_short, m.home_team_crest, m.away_team_name, m.away_team_short, m.away_team_crest, t.tip_home, t.tip_away, EXTRACT(EPOCH FROM (m.utc_date - NOW())) / 60 AS minutes_until FROM matches m LEFT JOIN tips t ON t.match_id = m.id AND t.user_id = $1 WHERE m.status IN ('SCHEDULED', 'TIMED') ORDER BY m.utc_date ASC LIMIT 1`, [userId] ); const h = heroResult[0]; const hero = h ? { match: { id: h.id, homeTeam: { name: h.home_team_name, shortName: h.home_team_short, crest: h.home_team_crest }, awayTeam: { name: h.away_team_name, shortName: h.away_team_short, crest: h.away_team_crest }, utcDate: h.utc_date, status: h.status, minutesUntilKickoff: Math.round(parseFloat(h.minutes_until)), }, userTip: h.tip_home != null ? { home: h.tip_home, away: h.tip_away } : null, tippable: parseFloat(h.minutes_until) > 5, } : null; // 2. Stats from leaderboard const statsResult = await query( `SELECT rank, total_points FROM leaderboard WHERE user_id = $1`, [userId] ); const s = statsResult[0]; // 3. Streak: consecutive tipped matches (most recent backward) const pastMatches = await query<{ has_tip: boolean }>( `SELECT CASE WHEN t.id IS NOT NULL THEN true ELSE false END AS has_tip FROM matches m LEFT JOIN tips t ON t.match_id = m.id AND t.user_id = $1 WHERE m.status IN ('FINISHED', 'IN_PLAY') ORDER BY m.utc_date DESC`, [userId] ); let streak = 0; for (const m of pastMatches) { if (m.has_tip === true || (m.has_tip as unknown) === 't' || (m.has_tip as unknown) === '1') streak++; else break; } // 4. Nudges const nudges: Array<{ type: string; text: string; matchId?: number }> = []; const untipped = await query<{ count: string }>( `SELECT COUNT(*) AS count FROM matches m LEFT JOIN tips t ON t.match_id = m.id AND t.user_id = $1 WHERE m.utc_date::date = CURRENT_DATE AND m.status IN ('SCHEDULED', 'TIMED') AND t.id IS NULL`, [userId] ); const untippedCount = parseInt(untipped[0]?.count || '0'); if (untippedCount > 0) { nudges.push({ type: 'untipped', text: `📅 Heute noch ${untippedCount} ${untippedCount === 1 ? 'Spiel' : 'Spiele'} ohne Tipp`, }); } const leader = await query<{ full_name: string; total_points: string }>( `SELECT full_name, total_points FROM leaderboard ORDER BY rank ASC LIMIT 1` ); if (leader[0]) { nudges.push({ type: 'leader', text: `🏆 ${leader[0].full_name} führt mit ${leader[0].total_points} Punkten` }); } const latest = await query( `SELECT m.home_team_short, m.away_team_short, m.score_home, m.score_away, t.points, m.id AS match_id FROM tips t JOIN matches m ON m.id = t.match_id WHERE t.user_id = $1 AND t.points IS NOT NULL ORDER BY m.utc_date DESC LIMIT 1`, [userId] ); if (latest[0]) { const r = latest[0]; nudges.push({ type: 'result', text: `🎯 Letzte Auswertung: ${r.points} Punkte für ${r.home_team_short} ${r.score_home}:${r.score_away} ${r.away_team_short}`, matchId: r.match_id, }); } res.json({ hero, stats: { rank: s ? parseInt(s.rank) : null, totalPoints: s ? parseInt(s.total_points) : 0, streak, }, nudges, }); } catch (error) { logger.error('Dashboard failed', { error }); res.status(500).json({ error: 'Internal server error' }); } }); export default router;