This commit is contained in:
2026-04-09 21:54:18 +02:00
commit ad33255b88
8906 changed files with 1437726 additions and 0 deletions

137
app/models/__init__.py Normal file
View File

@@ -0,0 +1,137 @@
import uuid
from datetime import datetime, timezone
from sqlalchemy import (
Boolean, DateTime, ForeignKey, Integer, String, Text
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base
def new_uuid() -> str:
return str(uuid.uuid4())
def now_utc() -> datetime:
return datetime.now(timezone.utc)
# ── User ──────────────────────────────────────────────────────────────────────
class User(Base):
__tablename__ = "users"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
username: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False)
password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc)
projects: Mapped[list["Project"]] = relationship("Project", back_populates="owner")
memberships: Mapped[list["ProjectMember"]] = relationship("ProjectMember", back_populates="user")
songs: Mapped[list["Song"]] = relationship("Song", back_populates="owner")
alternatives: Mapped[list["DanceAlternative"]] = relationship("DanceAlternative", foreign_keys="DanceAlternative.created_by", back_populates="creator")
alt_ratings: Mapped[list["DanceAlternativeRating"]] = relationship("DanceAlternativeRating", foreign_keys="DanceAlternativeRating.user_id", back_populates="user")
# ── Project ───────────────────────────────────────────────────────────────────
class Project(Base):
__tablename__ = "projects"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
owner_id: Mapped[str] = mapped_column(String(36), ForeignKey("users.id"), nullable=False)
name: Mapped[str] = mapped_column(String(128), nullable=False)
description: Mapped[str] = mapped_column(Text, default="")
is_public: Mapped[bool] = mapped_column(Boolean, default=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc, onupdate=now_utc)
owner: Mapped["User"] = relationship("User", back_populates="projects")
members: Mapped[list["ProjectMember"]] = relationship("ProjectMember", back_populates="project", cascade="all, delete-orphan")
project_songs: Mapped[list["ProjectSong"]] = relationship("ProjectSong", back_populates="project", order_by="ProjectSong.position", cascade="all, delete-orphan")
class ProjectMember(Base):
__tablename__ = "project_members"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
project_id: Mapped[str] = mapped_column(String(36), ForeignKey("projects.id"), nullable=False)
user_id: Mapped[str] = mapped_column(String(36), ForeignKey("users.id"), nullable=False)
role: Mapped[str] = mapped_column(String(16), default="viewer") # owner | editor | viewer
status: Mapped[str] = mapped_column(String(16), default="pending") # pending | accepted
invited_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc)
project: Mapped["Project"] = relationship("Project", back_populates="members")
user: Mapped["User"] = relationship("User", back_populates="memberships")
# ── Song ──────────────────────────────────────────────────────────────────────
class Song(Base):
__tablename__ = "songs"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
owner_id: Mapped[str] = mapped_column(String(36), ForeignKey("users.id"), nullable=False)
title: Mapped[str] = mapped_column(String(255), nullable=False)
artist: Mapped[str] = mapped_column(String(255), default="")
local_path: Mapped[str] = mapped_column(String(512), default="") # kun relevant på PC
bpm: Mapped[int] = mapped_column(Integer, default=0)
duration_sec: Mapped[int] = mapped_column(Integer, default=0)
synced_at: Mapped[datetime] = mapped_column(DateTime, default=now_utc)
owner: Mapped["User"] = relationship("User", back_populates="songs")
dances: Mapped[list["SongDance"]] = relationship("SongDance", back_populates="song", order_by="SongDance.dance_order", cascade="all, delete-orphan")
project_songs: Mapped[list["ProjectSong"]] = relationship("ProjectSong", back_populates="song")
class ProjectSong(Base):
__tablename__ = "project_songs"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
project_id: Mapped[str] = mapped_column(String(36), ForeignKey("projects.id"), nullable=False)
song_id: Mapped[str] = mapped_column(String(36), ForeignKey("songs.id"), nullable=False)
position: Mapped[int] = mapped_column(Integer, nullable=False)
status: Mapped[str] = mapped_column(String(16), default="pending") # pending | playing | played | skipped
project: Mapped["Project"] = relationship("Project", back_populates="project_songs")
song: Mapped["Song"] = relationship("Song", back_populates="project_songs")
# ── Dance ─────────────────────────────────────────────────────────────────────
class SongDance(Base):
__tablename__ = "song_dances"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
song_id: Mapped[str] = mapped_column(String(36), ForeignKey("songs.id"), nullable=False)
dance_name: Mapped[str] = mapped_column(String(128), nullable=False)
dance_order: Mapped[int] = mapped_column(Integer, default=1)
song: Mapped["Song"] = relationship("Song", back_populates="dances")
alternatives: Mapped[list["DanceAlternative"]] = relationship("DanceAlternative", foreign_keys="DanceAlternative.song_dance_id", back_populates="song_dance", cascade="all, delete-orphan")
class DanceAlternative(Base):
__tablename__ = "dance_alternatives"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
song_dance_id: Mapped[str] = mapped_column(String(36), ForeignKey("song_dances.id"), nullable=False)
alt_song_dance_id: Mapped[str] = mapped_column(String(36), ForeignKey("song_dances.id"), nullable=False)
created_by: Mapped[str] = mapped_column(String(36), ForeignKey("users.id"), nullable=False)
note: Mapped[str] = mapped_column(Text, default="")
bayesian_score: Mapped[float] = mapped_column(default=0.0) # genberegnes ved hver rating
song_dance: Mapped["SongDance"] = relationship("SongDance", foreign_keys=[song_dance_id], back_populates="alternatives")
alt_song_dance: Mapped["SongDance"] = relationship("SongDance", foreign_keys=[alt_song_dance_id])
creator: Mapped["User"] = relationship("User", foreign_keys=[created_by])
ratings: Mapped[list["DanceAlternativeRating"]] = relationship("DanceAlternativeRating", back_populates="alternative", cascade="all, delete-orphan")
class DanceAlternativeRating(Base):
__tablename__ = "dance_alternative_ratings"
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid)
alternative_id: Mapped[str] = mapped_column(String(36), ForeignKey("dance_alternatives.id"), nullable=False)
user_id: Mapped[str] = mapped_column(String(36), ForeignKey("users.id"), nullable=False)
score: Mapped[int] = mapped_column(Integer, nullable=False) # 1-5
alternative: Mapped["DanceAlternative"] = relationship("DanceAlternative", back_populates="ratings")
user: Mapped["User"] = relationship("User", foreign_keys=[user_id])