POL-127: Add organizations table and championship ownership

- 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>
This commit is contained in:
Dianaka123
2026-03-01 22:09:10 +03:00
parent 96e02bf64a
commit d4f0a05707
44 changed files with 450 additions and 183 deletions

View File

@@ -1,4 +1,5 @@
from app.models.user import User, RefreshToken
from app.models.organization import Organization
from app.models.championship import Championship
from app.models.registration import Registration
from app.models.participant import ParticipantList
@@ -7,6 +8,7 @@ from app.models.notification import NotificationLog
__all__ = [
"User",
"RefreshToken",
"Organization",
"Championship",
"Registration",
"ParticipantList",

View File

@@ -1,7 +1,7 @@
import uuid
from datetime import datetime
from sqlalchemy import DateTime, Float, Integer, String, Text, Uuid, func
from sqlalchemy import DateTime, Float, ForeignKey, Integer, String, Text, Uuid, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.database import Base
@@ -11,6 +11,7 @@ class Championship(Base):
__tablename__ = "championships"
id: Mapped[uuid.UUID] = mapped_column(Uuid(as_uuid=True), primary_key=True, default=uuid.uuid4)
org_id: Mapped[uuid.UUID | None] = mapped_column(Uuid(as_uuid=True), ForeignKey("organizations.id", ondelete="SET NULL"))
title: Mapped[str] = mapped_column(String(255), nullable=False)
description: Mapped[str | None] = mapped_column(Text)
location: Mapped[str | None] = mapped_column(String(500))
@@ -37,5 +38,6 @@ class Championship(Base):
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
)
organization: Mapped["Organization | None"] = relationship(back_populates="championships") # type: ignore[name-defined]
registrations: Mapped[list["Registration"]] = relationship(back_populates="championship", cascade="all, delete-orphan") # type: ignore[name-defined]
participant_list: Mapped["ParticipantList | None"] = relationship(back_populates="championship", uselist=False, cascade="all, delete-orphan") # type: ignore[name-defined]

View File

@@ -15,8 +15,6 @@ class User(Base):
hashed_password: Mapped[str] = mapped_column(String(255), nullable=False)
full_name: Mapped[str] = mapped_column(String(255), nullable=False)
phone: Mapped[str | None] = mapped_column(String(50))
# Organizer-specific fields
organization_name: Mapped[str | None] = mapped_column(String(255))
instagram_handle: Mapped[str | None] = mapped_column(String(100))
role: Mapped[str] = mapped_column(String(20), nullable=False, default="member")
# 'pending' | 'approved' | 'rejected'
@@ -30,6 +28,7 @@ class User(Base):
refresh_tokens: Mapped[list["RefreshToken"]] = relationship(back_populates="user", cascade="all, delete-orphan")
registrations: Mapped[list["Registration"]] = relationship(back_populates="user") # type: ignore[name-defined]
notification_logs: Mapped[list["NotificationLog"]] = relationship(back_populates="user") # type: ignore[name-defined]
organization: Mapped["Organization | None"] = relationship(back_populates="user", uselist=False) # type: ignore[name-defined]
class RefreshToken(Base):