feat: Stadium Elite Design, Rangliste, Profil-Team, User-Upsert & n8n Cronjob

- MatchCard + TipModal: Uhrzeit statt VS zwischen Flaggen, Gruppe zentriert
- LeaderboardPage: Podium (2./1./3.), DU-Badge, Trend-Pfeile, Team-Zeile, CTA-Card
- AdminPage: Stadium Elite Redesign mit Result-Bar und Inline-Spinner
- ProfilePage: Team-Feld inline editierbar (PATCH /api/profile/team)
- User-Upsert beim ersten App-Aufruf (Matches-Route) statt erst beim Tipp
- DB Migration 002: team-Spalte in users, Leaderboard View aktualisiert
- Leaderboard-Refresh automatisch nach Tipps-Auswertung
- n8n Workflow angelegt: stündlicher Sync + Auswertung (ID: t3SDspIGDXwkfOt3)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ronny Mueller
2026-04-03 23:37:38 +02:00
parent e967f36f6c
commit e27a62a37b
20 changed files with 1515 additions and 297 deletions
+45
View File
@@ -0,0 +1,45 @@
import { query } from '../db/client';
import { StaffbaseTokenData } from '../types';
import { logger } from './logger';
/**
* Legt einen User an oder aktualisiert seinen Namen/Locale beim Login.
* Das Team wird über die department_team_mapping Tabelle ermittelt,
* falls noch kein manuelles Team gesetzt wurde.
*/
export async function upsertUser(user: StaffbaseTokenData): Promise<void> {
try {
await query(
`INSERT INTO users (id, full_name, locale, branch_id, role)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (id) DO UPDATE SET
full_name = EXCLUDED.full_name,
locale = EXCLUDED.locale,
branch_id = EXCLUDED.branch_id,
updated_at = NOW()`,
[
user.sub,
user.name ?? 'Unbekannt',
user.locale ?? 'de_DE',
user.branchId ?? null,
user.role ?? 'viewer',
]
);
// Team über Matching-Tabelle setzen, falls noch kein manuelles Team gesetzt
if (user.branchId) {
await query(
`UPDATE users u
SET team = m.team_name
FROM department_team_mapping m
WHERE u.id = $1
AND u.team IS NULL
AND m.department_key = $2`,
[user.sub, user.branchId]
);
}
} catch (err) {
// Fehler beim Upsert soll die eigentliche Anfrage nicht blockieren
logger.error('Failed to upsert user', { error: err, userId: user.sub });
}
}