From 4771d991868423a1fee76f4039e731be4fefbd8f Mon Sep 17 00:00:00 2001 From: Carsten Kvist Date: Sat, 11 Apr 2026 00:32:20 +0200 Subject: [PATCH] 1 --- app/__pycache__/__init__.cpython-312.pyc | Bin 165 -> 0 bytes app/__pycache__/main.cpython-312.pyc | Bin 1503 -> 0 bytes app/core/__pycache__/config.cpython-312.pyc | Bin 766 -> 0 bytes app/core/__pycache__/database.cpython-312.pyc | Bin 1011 -> 0 bytes app/core/__pycache__/security.cpython-312.pyc | Bin 2907 -> 0 bytes app/core/config.py | 11 -- app/core/database.py | 21 --- app/core/security.py | 53 ------- app/models/__init__.py | 137 ------------------ .../__pycache__/__init__.cpython-312.pyc | Bin 9804 -> 0 bytes 10 files changed, 222 deletions(-) delete mode 100644 app/__pycache__/__init__.cpython-312.pyc delete mode 100644 app/__pycache__/main.cpython-312.pyc delete mode 100644 app/core/__pycache__/config.cpython-312.pyc delete mode 100644 app/core/__pycache__/database.cpython-312.pyc delete mode 100644 app/core/__pycache__/security.cpython-312.pyc delete mode 100644 app/core/config.py delete mode 100644 app/core/database.py delete mode 100644 app/core/security.py delete mode 100644 app/models/__init__.py delete mode 100644 app/models/__pycache__/__init__.cpython-312.pyc diff --git a/app/__pycache__/__init__.cpython-312.pyc b/app/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 82fbe022f059a8f3b7e48b085685f11091b99f94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 165 zcmX@j%ge<81Yf$ZXM*U*AOanHW&w&!XQ*V*Wb|9fP{ah}eFmxd<)WXFpPQl|NpO>odlbM&Al9-pA>X=qskeQPMluIlq(2tML%*!l^ lkJl@x{Ka9Do1apelWJGQ3N(}vh>JmtkIamWj77{q769FQDt`a~ diff --git a/app/__pycache__/main.cpython-312.pyc b/app/__pycache__/main.cpython-312.pyc deleted file mode 100644 index d1c2e28d36ddf59bbbd7f29b852ac082e6a18b12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1503 zcmbVM&2Jk;6o0dy{#p~qY7)|x8lkju5q68HT9k;=27!=DL6p;8qRGz0S!X}2nQ@}@ z(pE(($K-&55C^U(`A@hs!cwe}qEd0<<|;j)oS3m=JE-E4k>>61eZP5cXMXp*9H>?J z@gV%!1o%rc!{wfUqkj;<4iF$Hf*AP-X>3GBZ2D#&n~@dUz8yQh)90vu{er+5g8YdnT{knY{96@+$u44AnQ! zNJbXqt?yy?#K1qdIZ-YBt+^_a?&p|`JKub%U~%#5Zy$UW5)#o3%xKk+4ow{*4^}9G&qY0_3q^uGREk3|2ycI?fWtFs1>1SFKTx_y1X;e*Vi%>*Vy6cqj zFim8hP#&H>-w7#0g zv>sr_1x@OorEBe2RYO_*i%={^X+rCx9^Dx>ppILudW^%Q*4mU7OH;x0^}3`s*GV74 zpHs8HKHAUUd~V%j7nJ0FbxOJ`YM{6kGF`NsKH+NdXs)#&Wt>@BTT&S`OKSuRysU=I z^krg>R_3*GFiFnztHCmwWHr&xi#(?dHs~@>)f`1F#tCjv#`0R1-QY_DUzt&T#yY;t zsUcuNm()b0shnjI6S6d}Ocui=h}wh>UCjm^oVnl1{t?+l75+H05T~Ra(NEa>iq#JZ zf2l~%L8OWfb3`96Yl_K_F?9ymFFfzdhf9OI_7-aw!RSZ{`G=pVh%>Xbe27@E)tVeW_$B8r5JLT?gCULxe# z#0|0=9c;|7A2)-LQS-oO)N|Q@HipsI z4+6Mz$76F4a_~!PsE?;2pO(YrC+o1I0>BBH*RH?Qx$K3n19S|XUy(vdLK#4)QOre! z5SOtqc4I$e5g}ZL5US7O#En;^c@kw!GC3~^$bpD%1H3`=^^J3FV_vPjKAdlDf6{Kf zubpe#zcrlO{DlBgcj+k~Yi&G*o??GMqHI>UjFkix`aj*}yi0v2eb@W^8W_v3if#h@ eP%*||(47k{k2l}Gyg+dLtfJD@(|fPd=KlgvvbVzk diff --git a/app/core/__pycache__/database.cpython-312.pyc b/app/core/__pycache__/database.cpython-312.pyc deleted file mode 100644 index 6ca0fae9aae2cb52bcdc374f5d4d8c0f6e0c9ed4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1011 zcmah{O=uHA6n?Y2*>1L*Hf?ROAVsN{T$+=RgD9!BRa$zm_26EX&F(bac7M{DjWtjq zNDtMbf(LUIJW1AT%tBiWj|A(nBeNzDb%EPY%pC?|t*$oA)#CGZ`Il{n_60 zE=U01h0nTB zU3Ew9Fs6oNuG%#OXgbkgQDF*xPii^9NOr3^hZPUF}M%Y4zxVtVM3sYH=)RT#A@$R|cy zWz?xTKAq0ViEdZp$cci$i<7jFm;5SUPLz@ty3T1~0OVL{87MS>f=OI3r5eFU6p_q4 zM$;@Sv;%5ciEddzq4!Q9$#K&3H`1{8Y4B4QLosmY?zB zYkm~c{2W&8+Mz>>OT6OwKHk}tO5TYW&AWDNm+;kzN{wYOUK@nK#ZWh)sc3Al%l}h| zg6uf%R*W!**_>xXh>2RJvE`Q7SyZyPc@E(R9M<7edU$)R zKDjT+si9_H_L25g)1PV&wa5D5medh`^D}7uptnGtDC6*Kpg>*@UXtI)1x&z5biU@> zzOzh&8WZ&;eW%1k!~!N7#Y8uger)@MXgfl<= pAM_}?R9Al)kUjUJ`Zo1$`eXlW-8fJPNpD>}06bc1iVU@1`U@#P@8AFc diff --git a/app/core/__pycache__/security.cpython-312.pyc b/app/core/__pycache__/security.cpython-312.pyc deleted file mode 100644 index 18f9c558572e158bc6c6386e7b91e95dd77437d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2907 zcmai0T}&I<6~5yck8Q@r1`GrUsar^bQ@{z!c2_9ciXS(m?26fZ396F_pC9%DhI<45dTvST+T?WW$Q+jmeU;GImq7?KO*OQ&eS?f!vp( zwhgK<^xM>N^ujW#%gfZsXiHpP%Hl~}QcI2nH}Too6>3tOw`jD;XcjK6Z7K;L=n#iimc4#3nW384<3U*_OYq~RS zfPoY5>IJH)g)E)S*+tzjU`{O-6IqMVgiW(0raK#>#SI?9-80R{uzuJ-f(La#Qz1{4 zt^=j7s`PEo?knf_r1M80Cfr*zR2@8lYpIrP01u~#1b=+v(I_=)3|wmRq5ZRJ3ro6hWDgl zb`BRx08I5YXL&*FUioh@fGeLkGXLtsU=GQ_2fCDR#(z@)ebbRnpec0R{DE9P*p$8O zcZKGPO%QGgPPjRjM=zWros`KJg61g;y6}bY%FgYl6oGZn7B20Ij~k#%k+?LE@nPzP zk^_Jj>3R|PfNCsT0EiVct}xW)V!>KtXcsP)oGimPvsPsLCa0&9sZ?g+#$0kfle}~D z`mJQTYw zX7id zM{$p~^cs}Wzb-BcfC$L%V0p;z!WQIp2W{h$bAp1rzbSj!YoaD;UR~Ign?BeQ1H@69 z8sCJ8GI7w|GstiTgYT>nVEN;J4(mTtB}or^oRuY0;)^vuCbUDA1U`x=Ldos)nvnMq{qqHY2xh-yXq202xrn0gDaBoS|vyPkn82arIaZ{D++86$-fX|Xa-(d?dh2_eA1Pz!-5WXT~RWkM!IroeV z)yL1sm1pET&&VMB{@&WVGrKeKhYL?yCo9UAk->-NlgQOd=<8rdB~X(E;llQGjX<|E zeS}@jF9;*sZ=h)8q3CwMlW=CF=9h%K!nRf;*zed!ZvQ+&+IzP9_uDRh3H^m%B%XV_ Ugvg=NS`(rF+8;hf-hImd04EKuTL1t6 diff --git a/app/core/config.py b/app/core/config.py deleted file mode 100644 index d6272904..00000000 --- a/app/core/config.py +++ /dev/null @@ -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() diff --git a/app/core/database.py b/app/core/database.py deleted file mode 100644 index 7eb83441..00000000 --- a/app/core/database.py +++ /dev/null @@ -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() diff --git a/app/core/security.py b/app/core/security.py deleted file mode 100644 index 2f51eaea..00000000 --- a/app/core/security.py +++ /dev/null @@ -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 diff --git a/app/models/__init__.py b/app/models/__init__.py deleted file mode 100644 index b2c654ec..00000000 --- a/app/models/__init__.py +++ /dev/null @@ -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]) diff --git a/app/models/__pycache__/__init__.cpython-312.pyc b/app/models/__pycache__/__init__.cpython-312.pyc deleted file mode 100644 index 8fabe48e70316a2e55e89e363b78661cc1eaf8e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9804 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