Full app rebuild: FastAPI backend + React Native mobile with auth, championships, admin
Backend (FastAPI + SQLAlchemy + SQLite): - JWT auth with access/refresh tokens, bcrypt password hashing - User model with member/organizer/admin roles, auto-approve members - Championship, Registration, ParticipantList, Notification models - Alembic async migrations, seed data with test users - Registration endpoint returns tokens for members, pending for organizers - /registrations/my returns championship title/date/location via eager loading - Admin endpoints: list users, approve/reject organizers Mobile (React Native + Expo + TypeScript): - Zustand auth store, Axios client with token refresh interceptor - Role-based registration (Member vs Organizer) with contextual form labels - Tab navigation with Ionicons, safe area headers, admin tab for admin role - Championships list with status badges, detail screen with registration progress - My Registrations with championship title, progress bar, and tap-to-navigate - Admin panel with pending/all filter, approve/reject with confirmation - Profile screen with role badge, Ionicons info rows, sign out - Password visibility toggle (Ionicons), keyboard flow hints (returnKeyType) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
134
backend/seed.py
Normal file
134
backend/seed.py
Normal file
@@ -0,0 +1,134 @@
|
||||
"""Seed script — creates test users and one championship.
|
||||
Run from backend/: .venv/Scripts/python seed.py
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
from app.database import AsyncSessionLocal
|
||||
from app.models.championship import Championship
|
||||
from app.models.user import User
|
||||
from app.services.auth_service import hash_password
|
||||
from sqlalchemy import select
|
||||
|
||||
|
||||
async def seed():
|
||||
async with AsyncSessionLocal() as db:
|
||||
# ── Users ──────────────────────────────────────────────────────────────
|
||||
users_data = [
|
||||
{
|
||||
"email": "admin@pole.dev",
|
||||
"full_name": "Diana Admin",
|
||||
"password": "Admin1234",
|
||||
"role": "admin",
|
||||
"status": "approved",
|
||||
},
|
||||
{
|
||||
"email": "organizer@pole.dev",
|
||||
"full_name": "Ekaterina Organizer",
|
||||
"password": "Org1234",
|
||||
"role": "organizer",
|
||||
"status": "approved",
|
||||
},
|
||||
{
|
||||
"email": "member@pole.dev",
|
||||
"full_name": "Anna Petrova",
|
||||
"password": "Member1234",
|
||||
"role": "member",
|
||||
"status": "approved",
|
||||
},
|
||||
{
|
||||
"email": "pending@pole.dev",
|
||||
"full_name": "New Applicant",
|
||||
"password": "Pending1234",
|
||||
"role": "member",
|
||||
"status": "pending",
|
||||
},
|
||||
]
|
||||
|
||||
created_users = {}
|
||||
for ud in users_data:
|
||||
result = await db.execute(select(User).where(User.email == ud["email"]))
|
||||
user = result.scalar_one_or_none()
|
||||
if user is None:
|
||||
user = User(
|
||||
email=ud["email"],
|
||||
hashed_password=hash_password(ud["password"]),
|
||||
full_name=ud["full_name"],
|
||||
role=ud["role"],
|
||||
status=ud["status"],
|
||||
)
|
||||
db.add(user)
|
||||
print(f" Created user: {ud['email']}")
|
||||
else:
|
||||
# Update role/status if needed
|
||||
user.role = ud["role"]
|
||||
user.status = ud["status"]
|
||||
user.hashed_password = hash_password(ud["password"])
|
||||
print(f" Updated user: {ud['email']}")
|
||||
created_users[ud["email"]] = user
|
||||
|
||||
await db.flush()
|
||||
|
||||
# ── Championships ──────────────────────────────────────────────────────
|
||||
championships_data = [
|
||||
{
|
||||
"title": "Spring Open 2026",
|
||||
"description": "Annual spring pole dance championship. All levels welcome.",
|
||||
"location": "Cultural Center, Minsk",
|
||||
"event_date": datetime(2026, 4, 15, 10, 0, tzinfo=UTC),
|
||||
"registration_open_at": datetime(2026, 3, 1, 0, 0, tzinfo=UTC),
|
||||
"registration_close_at": datetime(2026, 4, 1, 0, 0, tzinfo=UTC),
|
||||
"form_url": "https://forms.example.com/spring2026",
|
||||
"entry_fee": 50.0,
|
||||
"video_max_duration": 180,
|
||||
"judges": json.dumps([
|
||||
{"name": "Oksana Ivanova", "bio": "Champion 2023", "instagram": "@oksana_pole"},
|
||||
{"name": "Marta Sokolova", "bio": "Certified judge", "instagram": "@marta_pole"},
|
||||
]),
|
||||
"categories": json.dumps(["Novice", "Amateur", "Professional"]),
|
||||
"status": "open",
|
||||
"source": "manual",
|
||||
"image_url": "https://images.unsplash.com/photo-1524594152303-9fd13543fe6e?w=800",
|
||||
},
|
||||
{
|
||||
"title": "Summer Championship 2026",
|
||||
"description": "The biggest pole dance event of the summer.",
|
||||
"location": "Sports Palace, Minsk",
|
||||
"event_date": datetime(2026, 7, 20, 9, 0, tzinfo=UTC),
|
||||
"registration_open_at": datetime(2026, 6, 1, 0, 0, tzinfo=UTC),
|
||||
"registration_close_at": datetime(2026, 7, 5, 0, 0, tzinfo=UTC),
|
||||
"entry_fee": 75.0,
|
||||
"video_max_duration": 240,
|
||||
"judges": json.dumps([
|
||||
{"name": "Elena Kozlova", "bio": "World finalist", "instagram": "@elena_wpc"},
|
||||
]),
|
||||
"categories": json.dumps(["Junior", "Senior", "Masters"]),
|
||||
"status": "draft",
|
||||
"source": "manual",
|
||||
},
|
||||
]
|
||||
|
||||
for cd in championships_data:
|
||||
result = await db.execute(
|
||||
select(Championship).where(Championship.title == cd["title"])
|
||||
)
|
||||
champ = result.scalar_one_or_none()
|
||||
if champ is None:
|
||||
champ = Championship(**cd)
|
||||
db.add(champ)
|
||||
print(f" Created championship: {cd['title']}")
|
||||
else:
|
||||
print(f" Championship already exists: {cd['title']}")
|
||||
|
||||
await db.commit()
|
||||
print("\nSeed complete!")
|
||||
print("\n=== TEST CREDENTIALS ===")
|
||||
print("Admin: admin@pole.dev / Admin1234")
|
||||
print("Organizer: organizer@pole.dev / Org1234")
|
||||
print("Member: member@pole.dev / Member1234")
|
||||
print("Pending: pending@pole.dev / Pending1234")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(seed())
|
||||
Reference in New Issue
Block a user