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>
135 lines
5.5 KiB
Python
135 lines
5.5 KiB
Python
"""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())
|