clementpep Claude Sonnet 4.5 commited on
Commit
3fa5496
·
1 Parent(s): 610bf2d

fix: resolve login issues and auto-seed database

Browse files

Frontend fixes:
- Fix double /api in API URL (was /api/api/auth/login)
- Smart baseURL detection to avoid duplication

Backend fixes:
- Add auto-seed module to populate database on first startup
- Seed 13 participants and 15 sample challenges automatically
- Check if database is empty before seeding to avoid duplicates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

backend/app/main.py CHANGED
@@ -11,7 +11,8 @@ from fastapi.staticfiles import StaticFiles
11
  from contextlib import asynccontextmanager
12
  from pathlib import Path
13
  from app.config import get_settings
14
- from app.database import init_db, get_db
 
15
  from app.routes import (
16
  auth_router,
17
  participants_router,
@@ -56,6 +57,13 @@ async def lifespan(app: FastAPI):
56
  init_db()
57
  logger.info("Database initialized")
58
 
 
 
 
 
 
 
 
59
  # Start pack scheduler
60
  start_scheduler()
61
  logger.info("Pack scheduler started")
 
11
  from contextlib import asynccontextmanager
12
  from pathlib import Path
13
  from app.config import get_settings
14
+ from app.database import init_db, get_db, SessionLocal
15
+ from app.seed import auto_seed_if_empty
16
  from app.routes import (
17
  auth_router,
18
  participants_router,
 
57
  init_db()
58
  logger.info("Database initialized")
59
 
60
+ # Auto-seed database if empty
61
+ db = SessionLocal()
62
+ try:
63
+ auto_seed_if_empty(db)
64
+ finally:
65
+ db.close()
66
+
67
  # Start pack scheduler
68
  start_scheduler()
69
  logger.info("Pack scheduler started")
backend/app/seed.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Auto-seed module for EVG Ultimate Team.
3
+
4
+ Automatically seeds the database with initial data if empty.
5
+ """
6
+
7
+ from sqlalchemy.orm import Session
8
+ from app.models import Participant, Challenge, ChallengeType, ChallengeStatus
9
+ from app.utils.logger import logger
10
+
11
+
12
+ def seed_participants(db: Session) -> None:
13
+ """Seed the database with all 13 participants."""
14
+ participants_data = [
15
+ {"name": "Paul C.", "is_groom": True, "avatar_url": None},
16
+ {"name": "Clément P.", "is_groom": False, "avatar_url": None}, # Admin
17
+ {"name": "Paul J.", "is_groom": False, "avatar_url": None},
18
+ {"name": "Hugo F.", "is_groom": False, "avatar_url": None},
19
+ {"name": "Théo C.", "is_groom": False, "avatar_url": None},
20
+ {"name": "Antonin M.", "is_groom": False, "avatar_url": None},
21
+ {"name": "Philippe C.", "is_groom": False, "avatar_url": None},
22
+ {"name": "Lancelot M.", "is_groom": False, "avatar_url": None},
23
+ {"name": "Vianney D.", "is_groom": False, "avatar_url": None},
24
+ {"name": "Thomas S.", "is_groom": False, "avatar_url": None},
25
+ {"name": "Martin L.", "is_groom": False, "avatar_url": None},
26
+ {"name": "Guillaume V.", "is_groom": False, "avatar_url": None},
27
+ {"name": "Adrien M.", "is_groom": False, "avatar_url": None},
28
+ ]
29
+
30
+ for data in participants_data:
31
+ participant = Participant(
32
+ name=data["name"],
33
+ is_groom=data["is_groom"],
34
+ avatar_url=data["avatar_url"],
35
+ total_points=0,
36
+ current_packs={"bronze": 0, "silver": 0, "gold": 0, "ultimate": 0}
37
+ )
38
+ db.add(participant)
39
+
40
+ db.commit()
41
+ logger.info(f"✓ Created {len(participants_data)} participants")
42
+
43
+
44
+ def seed_challenges(db: Session) -> None:
45
+ """Seed the database with sample challenges."""
46
+ challenges_data = [
47
+ # Individual Challenges (20-50 pts)
48
+ {
49
+ "title": "Convince a stranger you're a Red Bull sales rep",
50
+ "description": "Approach a stranger and successfully convince them you work for Red Bull. Must last at least 2 minutes.",
51
+ "type": ChallengeType.INDIVIDUAL,
52
+ "points": 30,
53
+ "status": ChallengeStatus.PENDING
54
+ },
55
+ {
56
+ "title": "Win a 1v1 FIFA match against Paul",
57
+ "description": "Challenge Paul to a FIFA match and win. Best of 3 games.",
58
+ "type": ChallengeType.INDIVIDUAL,
59
+ "points": 50,
60
+ "status": ChallengeStatus.PENDING
61
+ },
62
+ {
63
+ "title": "Complete a rugby transformation",
64
+ "description": "Score a try in the touch rugby game with proper technique.",
65
+ "type": ChallengeType.INDIVIDUAL,
66
+ "points": 40,
67
+ "status": ChallengeStatus.PENDING
68
+ },
69
+ {
70
+ "title": "Finish first in go-kart racing",
71
+ "description": "Win the go-kart race against all other participants.",
72
+ "type": ChallengeType.INDIVIDUAL,
73
+ "points": 50,
74
+ "status": ChallengeStatus.PENDING
75
+ },
76
+ {
77
+ "title": "Give a 2-minute speech about why Paul is the best groom",
78
+ "description": "Deliver an impromptu 2-minute speech praising Paul. Must be heartfelt and entertaining.",
79
+ "type": ChallengeType.INDIVIDUAL,
80
+ "points": 35,
81
+ "status": ChallengeStatus.PENDING
82
+ },
83
+ {
84
+ "title": "Order a shot mimicking Paul's accent",
85
+ "description": "Successfully order a shot at the bar using Paul's accent. Bartender must not notice.",
86
+ "type": ChallengeType.INDIVIDUAL,
87
+ "points": 25,
88
+ "status": ChallengeStatus.PENDING
89
+ },
90
+ {
91
+ "title": "Pitch an absurd item to a stranger",
92
+ "description": "Use Paul's commercial skills to pitch a ridiculous product to a stranger (e.g., invisible socks). Must last 2 minutes.",
93
+ "type": ChallengeType.INDIVIDUAL,
94
+ "points": 30,
95
+ "status": ChallengeStatus.PENDING
96
+ },
97
+ {
98
+ "title": "Negotiate a discount at the restaurant",
99
+ "description": "Successfully negotiate at least a 10% discount on the bill using your charm.",
100
+ "type": ChallengeType.INDIVIDUAL,
101
+ "points": 45,
102
+ "status": ChallengeStatus.PENDING
103
+ },
104
+ # Team Challenges (100 pts shared)
105
+ {
106
+ "title": "Win the padel tournament",
107
+ "description": "Your team must win the padel tournament on Saturday afternoon.",
108
+ "type": ChallengeType.TEAM,
109
+ "points": 100,
110
+ "status": ChallengeStatus.PENDING
111
+ },
112
+ {
113
+ "title": "Win the football match",
114
+ "description": "Your team must win the football match on Saturday afternoon.",
115
+ "type": ChallengeType.TEAM,
116
+ "points": 100,
117
+ "status": ChallengeStatus.PENDING
118
+ },
119
+ {
120
+ "title": "Finish champagne bottle under 5 minutes",
121
+ "description": "Your team of 4 must finish a full champagne bottle in under 5 minutes.",
122
+ "type": ChallengeType.TEAM,
123
+ "points": 100,
124
+ "status": ChallengeStatus.PENDING
125
+ },
126
+ {
127
+ "title": "Complete a 5-person karaoke",
128
+ "description": "Get 5 people to perform a full karaoke song together. Must be a classic.",
129
+ "type": ChallengeType.TEAM,
130
+ "points": 100,
131
+ "status": ChallengeStatus.PENDING
132
+ },
133
+ # Secret Challenges (50-100 pts)
134
+ {
135
+ "title": "Make Paul laugh during dinner",
136
+ "description": "SECRET: Next person to make Paul genuinely laugh during dinner wins. Admin will reveal this at dinner.",
137
+ "type": ChallengeType.SECRET,
138
+ "points": 50,
139
+ "status": ChallengeStatus.PENDING
140
+ },
141
+ {
142
+ "title": "Spot the reference",
143
+ "description": "SECRET: First person to notice and mention the hidden Toulouse Stade reference wins.",
144
+ "type": ChallengeType.SECRET,
145
+ "points": 75,
146
+ "status": ChallengeStatus.PENDING
147
+ },
148
+ {
149
+ "title": "Midnight champion",
150
+ "description": "SECRET: Last person awake on Friday night wins bonus points.",
151
+ "type": ChallengeType.SECRET,
152
+ "points": 100,
153
+ "status": ChallengeStatus.PENDING
154
+ },
155
+ ]
156
+
157
+ for data in challenges_data:
158
+ challenge = Challenge(
159
+ title=data["title"],
160
+ description=data["description"],
161
+ type=data["type"],
162
+ points=data["points"],
163
+ status=data["status"]
164
+ )
165
+ db.add(challenge)
166
+
167
+ db.commit()
168
+ logger.info(f"✓ Created {len(challenges_data)} challenges")
169
+
170
+
171
+ def auto_seed_if_empty(db: Session) -> None:
172
+ """
173
+ Automatically seed the database if it's empty.
174
+
175
+ Called during application startup.
176
+ """
177
+ # Check if database is empty (no participants)
178
+ participant_count = db.query(Participant).count()
179
+
180
+ if participant_count == 0:
181
+ logger.info("Database is empty - auto-seeding with initial data...")
182
+ seed_participants(db)
183
+ seed_challenges(db)
184
+ logger.info("✓ Database auto-seed completed successfully!")
185
+ else:
186
+ logger.info(f"Database already contains {participant_count} participants - skipping seed")
frontend/src/services/api.ts CHANGED
@@ -13,7 +13,7 @@ const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';
13
  * Axios instance with pre-configured base URL and interceptors.
14
  */
15
  const apiClient = axios.create({
16
- baseURL: `${API_URL}/api`,
17
  headers: {
18
  'Content-Type': 'application/json',
19
  },
 
13
  * Axios instance with pre-configured base URL and interceptors.
14
  */
15
  const apiClient = axios.create({
16
+ baseURL: API_URL.endsWith('/api') ? API_URL : `${API_URL}/api`,
17
  headers: {
18
  'Content-Type': 'application/json',
19
  },