1
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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()
|
|
||||||
@@ -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()
|
|
||||||
@@ -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
|
|
||||||
@@ -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])
|
|
||||||
Binary file not shown.
Reference in New Issue
Block a user