- Create organizations table with Alembic migration (3-phase: create table, migrate data, drop old column) - Add org_id FK on championships linking to organizations - Refactor all schemas into one-class-per-file packages (auth, championship, organization, participant, registration, user) - Update CRUD layer with selectinload for organization relationships - Update frontend types and components to use nested organization object - Remove phantom Championship fields (subtitle, venue, accent_color) from frontend Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
160 lines
6.6 KiB
Python
160 lines
6.6 KiB
Python
"""Seed script — creates test users, organization, and championships.
|
|
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.organization import Organization
|
|
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",
|
|
"instagram_handle": "@ekaterina_pole",
|
|
},
|
|
{
|
|
"email": "member@pole.dev",
|
|
"full_name": "Anna Petrova",
|
|
"password": "Member1234",
|
|
"role": "member",
|
|
"status": "approved",
|
|
"instagram_handle": "@anna_petrova",
|
|
},
|
|
{
|
|
"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"],
|
|
instagram_handle=ud.get("instagram_handle"),
|
|
)
|
|
db.add(user)
|
|
print(f" Created user: {ud['email']}")
|
|
else:
|
|
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()
|
|
|
|
# ── Organization ──────────────────────────────────────────────────────
|
|
organizer = created_users["organizer@pole.dev"]
|
|
result = await db.execute(select(Organization).where(Organization.user_id == organizer.id))
|
|
org = result.scalar_one_or_none()
|
|
if org is None:
|
|
org = Organization(
|
|
user_id=organizer.id,
|
|
name="Pole Studio Minsk",
|
|
instagram="@polestudio_minsk",
|
|
email="organizer@pole.dev",
|
|
city="Minsk",
|
|
verified=True,
|
|
status="active",
|
|
)
|
|
db.add(org)
|
|
print(f" Created organization: {org.name}")
|
|
else:
|
|
print(f" Organization already exists: {org.name}")
|
|
|
|
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(org_id=org.id, **cd)
|
|
db.add(champ)
|
|
print(f" Created championship: {cd['title']}")
|
|
else:
|
|
champ.org_id = org.id
|
|
print(f" Updated championship: {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())
|