This commit is contained in:
2026-04-11 00:32:20 +02:00
parent 18b0e4d8d9
commit 4771d99186
10 changed files with 0 additions and 222 deletions

Binary file not shown.

View File

@@ -1,11 +0,0 @@
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
DATABASE_URL: str
SECRET_KEY: str
ACCESS_TOKEN_EXPIRE_MINUTES: int = 10080 # 7 dage
class Config:
env_file = ".env"
settings = Settings()

View File

@@ -1,21 +0,0 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase, sessionmaker
from app.core.config import settings
engine = create_engine(
settings.DATABASE_URL,
pool_pre_ping=True, # genforbinder hvis connection er død
pool_recycle=3600, # genbruger ikke forbindelser ældre end 1 time
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
class Base(DeclarativeBase):
pass
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()

View File

@@ -1,53 +0,0 @@
from datetime import datetime, timedelta, timezone
from jose import JWTError, jwt
from passlib.context import CryptContext
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.orm import Session
from app.core.config import settings
from app.core.database import get_db
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")
ALGORITHM = "HS256"
def hash_password(password: str) -> str:
return pwd_context.hash(password)
def verify_password(plain: str, hashed: str) -> bool:
return pwd_context.verify(plain, hashed)
def create_access_token(data: dict) -> str:
expire = datetime.now(timezone.utc) + timedelta(
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
)
return jwt.encode({**data, "exp": expire}, settings.SECRET_KEY, algorithm=ALGORITHM)
def get_current_user(
token: str = Depends(oauth2_scheme),
db: Session = Depends(get_db),
):
from app.models.user import User
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Kunne ikke validere token",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[ALGORITHM])
user_id: str = payload.get("sub")
if user_id is None:
raise credentials_exception
except JWTError:
raise credentials_exception
user = db.query(User).filter(User.id == user_id).first()
if user is None:
raise credentials_exception
return user

View File

@@ -1,137 +0,0 @@
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])