restructure

This commit is contained in:
matthias@matsewe.de
2024-05-22 19:44:24 +02:00
parent 32d2170a1b
commit f6016f5736
11 changed files with 195 additions and 115 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,3 @@
__pycache__ __pycache__
.venv .venv
.vscode .vscode
service_account.json
db.sqlite

67
app/crud.py Normal file
View File

@@ -0,0 +1,67 @@
import app.models as models
from sqlalchemy import func
def get_songs_and_vote_for_user(db, user_id) -> list[models.Song]:
votes = db.query(models.Vote).filter(models.Vote.user_id == user_id).subquery()
songs_and_votes = db.query(
models.Song, votes.c.vote
).filter(
models.Song.singable == True
).join(votes, isouter=True).filter().all()
return songs_and_votes
def get_all_songs_and_votes(db) -> dict[int, dict[int, int]]:
_v = db.query(models.Vote.song_id, models.Vote.vote, func.count(models.Vote.song_id)).group_by(models.Vote.song_id, models.Vote.vote).all()
votes = {}
for v in _v:
if v[0] not in votes:
votes[v[0]] = {-1 : 0, 0 : 0, 1 : 0}
votes[v[0]][v[1]] = v[2]
return votes
def create_song(db,
og_artist,
aca_artist,
title,
url,
yt_id,
spfy_id,
thumbnail,
is_aca,
arng_url,
categories,
main_category,
singable
):
s = models.Song(og_artist=og_artist,
aca_artist=aca_artist,
title=title,
url=url,
yt_id=yt_id,
spfy_id=spfy_id,
thumbnail=thumbnail,
is_aca=is_aca,
arng_url=arng_url,
categories=categories,
main_category=main_category,
singable=singable)
db.add(s)
db.commit()
def create_or_update_vote(db, song_id, user_id, vote):
vote_entry = db.query(models.Vote).filter(
(models.Vote.user_id == user_id) & (models.Vote.song_id == song_id)).first()
if vote_entry:
vote_entry.vote = str(vote) # type: ignore
else:
vote_entry = models.Vote(song_id=song_id, user_id=user_id, vote=vote)
db.add(vote_entry)
db.commit()

25
app/database.py Normal file
View File

@@ -0,0 +1,25 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase
from sqlalchemy.types import PickleType
import os
SQLALCHEMY_DATABASE_URL = "sqlite:///" + os.environ.get("DATABASE_URL", "/data/db.sqlite")
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
async def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
class Base(DeclarativeBase):
type_annotation_map = {
dict[str, bool]: PickleType
}

View File

@@ -1,6 +0,0 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
engine = create_engine('sqlite:///db.sqlite')
Session = sessionmaker(engine)
session = Session()

View File

@@ -1,11 +1,13 @@
from fastapi import FastAPI, Request from fastapi import FastAPI, Request, Depends
from app.routers import admin, user, songs from app.routers import admin, user, songs
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from app.dependencies import engine from app.database import engine, Base, get_db
from app.sql_models import Base from app.crud import get_songs_and_vote_for_user
from app.routers.songs import get_songs from sqlalchemy.orm import Session
from typing import Annotated
from app.schemas import Song
Base.metadata.create_all(engine) Base.metadata.create_all(engine)
@@ -20,13 +22,14 @@ app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates") templates = Jinja2Templates(directory="templates")
@app.get("/", response_class=HTMLResponse) @app.get("/", response_class=HTMLResponse)
async def root(request: Request, session_id : str = ""): async def root(request: Request, session_id : str = "", db: Annotated[Session, Depends(get_db)] = None):
if session_id == "": if session_id == "":
return templates.TemplateResponse( return templates.TemplateResponse(
request=request, name="landing.html" request=request, name="landing.html"
) )
else: else:
songs = await get_songs(session_id) songs = [Song(**s.__dict__, vote=v) for s, v in get_songs_and_vote_for_user(db, session_id)]
songs_by_category = {} songs_by_category = {}
all_categories = set() all_categories = set()
for song in songs: for song in songs:

View File

@@ -1,26 +1,35 @@
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
from sqlalchemy import Integer, ForeignKey
from sqlalchemy.sql import func
from typing import Optional from typing import Optional
from pydantic import BaseModel from datetime import datetime
class GoogleFile(BaseModel):
file_id: str
file_name: str
class Genre(BaseModel): class Song(Base):
genre_id: Optional[int] __tablename__ = 'songs'
genre_name: str id: Mapped[int] = mapped_column(primary_key=True)
og_artist: Mapped[Optional[str]]
aca_artist: Mapped[Optional[str]]
title: Mapped[Optional[str]]
url: Mapped[Optional[str]]
yt_id: Mapped[Optional[str]]
spfy_id: Mapped[Optional[str]]
thumbnail: Mapped[Optional[str]]
is_aca: Mapped[Optional[bool]]
arng_url: Mapped[Optional[str]]
categories: Mapped[Optional[dict[str, bool]]]
main_category: Mapped[Optional[str]]
singable: Mapped[Optional[bool]]
class Song(BaseModel):
id: int class Vote(Base):
og_artist: Optional[str] __tablename__ = 'votes'
aca_artist: Optional[str] id: Mapped[int] = mapped_column(primary_key=True)
title: Optional[str] song_id: Mapped[int] = mapped_column(Integer, ForeignKey("songs.id"))
url: Optional[str] user_id: Mapped[int]
yt_id: Optional[str] vote: Mapped[Optional[int]]
spfy_id: Optional[str] time_created: Mapped[datetime] = mapped_column(server_default=func.now())
thumbnail: Optional[str] time_updated: Mapped[Optional[datetime]
is_aca: bool ] = mapped_column(onupdate=func.now())
arng_url: Optional[str]
categories: dict[str, bool]
main_category: str
singable: bool
vote: Optional[int]

View File

@@ -1,17 +1,14 @@
from fastapi import APIRouter, Security
from app.sql_models import SqlSong
from app.dependencies import session
from app.dependencies import engine
from app.routers.user import get_current_user
import pandas as pd import pandas as pd
import numpy as np import numpy as np
import re import re
import requests import requests
import os import os
from app.sql_models import Base from fastapi import APIRouter, Security, Depends
from sqlalchemy.orm import Session
from app.database import get_db, engine, Base
from app.routers.user import get_current_user
from app.crud import create_song
router = APIRouter( router = APIRouter(
prefix="/admin", prefix="/admin",
@@ -28,6 +25,7 @@ def get_main_category(categories) -> int:
else: else:
return np.argmax(categories != None, axis=0) return np.argmax(categories != None, axis=0)
def get_youtube_id(url): def get_youtube_id(url):
if url is None: if url is None:
return None return None
@@ -43,6 +41,7 @@ def get_youtube_id(url):
return None return None
def get_thumbnail(url): def get_thumbnail(url):
if url is None: if url is None:
return "/static/cover.jpg" return "/static/cover.jpg"
@@ -56,6 +55,7 @@ def get_thumbnail(url):
else: else:
return "/static/cover.jpg" return "/static/cover.jpg"
def get_spotify_id(url): def get_spotify_id(url):
if url is None: if url is None:
return None return None
@@ -64,8 +64,9 @@ def get_spotify_id(url):
else: else:
return None return None
@router.post("/load_list") @router.post("/load_list")
async def create_upload_file(): async def create_upload_file(db: Session = Depends(get_db)):
Base.metadata.drop_all(engine) Base.metadata.drop_all(engine)
Base.metadata.create_all(engine) Base.metadata.create_all(engine)
@@ -83,12 +84,13 @@ async def create_upload_file():
spfy_id = get_spotify_id(row[3]) spfy_id = get_spotify_id(row[3])
categories = {n: v for n, v in zip( categories = {n: v for n, v in zip(
category_names, row[6:19] != None)} category_names, row[6:19] != None)}
if not np.any(list(categories.values())): if not np.any(list(categories.values())):
continue continue
s = SqlSong(og_artist=row[0], create_song(db,
og_artist=row[0],
aca_artist=row[1], aca_artist=row[1],
title=row[2], title=row[2],
url=row[3], url=row[3],
@@ -101,6 +103,3 @@ async def create_upload_file():
main_category=category_names[get_main_category(row[6:19])], main_category=category_names[get_main_category(row[6:19])],
singable=row[19] != "nein" singable=row[19] != "nein"
) )
session.add(s)
session.commit()

View File

@@ -1,39 +1,29 @@
from fastapi import APIRouter from typing import Annotated
from app.models import Song from fastapi import APIRouter, Depends
from app.sql_models import SqlSong, SqlVote from sqlalchemy.orm import Session
from app.dependencies import session
import app.models as models
from app.database import get_db
from app.schemas import Song
from app.crud import get_songs_and_vote_for_user, create_or_update_vote, get_all_songs_and_votes
router = APIRouter( router = APIRouter(
prefix="/songs", prefix="/songs",
#dependencies=[Security(get_current_user, scopes=["public"])], # dependencies=[Security(get_current_user, scopes=["public"])],
responses={404: {"description": "Not found"}}, responses={404: {"description": "Not found"}},
) )
async def songify(s, votes):
return Song(**s.__dict__, vote=votes.get(s.id, None))
@router.get("/") @router.get("/")
async def get_songs(user_id : str = "") -> list[Song]: async def get_songs(user_id: str = "", db: Annotated[Session, Depends(get_db)] = None) -> list[Song]:
sqlsongs = session.query(SqlSong).filter(SqlSong.singable == True).all() return [Song(**s.__dict__, vote=v) for s, v in get_songs_and_vote_for_user(db, user_id)]
votes = session.query(SqlVote).filter(SqlVote.user_id == user_id).all()
votes = {v.song_id : v.vote for v in votes}
songs = []
for s in sqlsongs:
try:
songs.append(Song(**s.__dict__, vote=votes.get(s.id, None)))
except:
print(s.__dict__)
pass
return songs
#return [Song(**s.__dict__, vote=votes.get(s.id, None)) for s in sqlsongs] # type: ignore
@router.post("/{song_id}/vote") @router.post("/{song_id}/vote")
async def vote(song_id : str, user_id : str, vote : int): async def vote(song_id: str, user_id: str, vote: int, db: Annotated[Session, Depends(get_db)]):
vote_entry = session.query(SqlVote).filter((SqlVote.user_id == user_id) & (SqlVote.song_id == song_id)).first() create_or_update_vote(db, song_id, user_id, vote)
if vote_entry:
vote_entry.vote = str(vote) # type: ignore
else: @router.get("/evaluation")
vote_entry = SqlVote(song_id=song_id, user_id=user_id, vote=vote) async def get_evaluation(db: Annotated[Session, Depends(get_db)] = None) -> dict[int, dict[int, int]]:
session.add(vote_entry) return get_all_songs_and_votes(db)
session.commit()

18
app/schemas.py Normal file
View File

@@ -0,0 +1,18 @@
from typing import Optional
from pydantic import BaseModel
class Song(BaseModel):
id: int
og_artist: Optional[str]
aca_artist: Optional[str]
title: Optional[str]
url: Optional[str]
yt_id: Optional[str]
spfy_id: Optional[str]
thumbnail: Optional[str]
is_aca: Optional[bool]
arng_url: Optional[str]
categories: Optional[dict[str, bool]]
main_category: Optional[str]
singable: Optional[bool]
vote: Optional[int]

View File

@@ -1,30 +0,0 @@
from sqlalchemy import Column, String, Integer, Boolean, PickleType
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class SqlSong(Base):
__tablename__ = 'songs'
id = Column(Integer, primary_key=True)
og_artist = Column(String)
aca_artist = Column(String)
title = Column(String)
url = Column(String)
yt_id = Column(String)
spfy_id = Column(String)
thumbnail = Column(String)
is_aca = Column(Boolean)
arng_url = Column(String)
categories = Column(PickleType)
main_category = Column(String)
singable = Column(Boolean)
class SqlVote(Base):
__tablename__ = 'votes'
id = Column(Integer, primary_key=True)
song_id = Column(Integer)
user_id = Column(String)
vote = Column(Integer, nullable=True)

View File

@@ -14,7 +14,13 @@
} }
@media only screen and (min-resolution: 200dpi) { @media only screen and (min-resolution: 2dppx) {
body {
font-size: 3.2vmin;
}
}
@media screen and (-webkit-min-device-pixel-ratio: 2) {
body { body {
font-size: 3.2vmin; font-size: 3.2vmin;
} }
@@ -199,7 +205,8 @@
color: white; color: white;
max-width: 10em; max-width: 10em;
overflow: clip; overflow: clip;
background-color: hsl(calc(var(--hue) * 360), 100%, 40%); /*color-mix(in srgb, var(--main-color) 60%, transparent);*/ background-color: hsl(calc(var(--hue) * 360), 100%, 40%);
/*color-mix(in srgb, var(--main-color) 60%, transparent);*/
} }
.vote-buttons { .vote-buttons {
@@ -232,7 +239,7 @@ h1 {
border-bottom: 0.3rem solid hsl(calc(var(--hue) * 360), 100%, 40%); border-bottom: 0.3rem solid hsl(calc(var(--hue) * 360), 100%, 40%);
} }
body > h1 { body>h1 {
background-color: color-mix(in srgb, hsl(0, 0%, 40%) 50%, transparent); background-color: color-mix(in srgb, hsl(0, 0%, 40%) 50%, transparent);
border-bottom: 0.3rem solid hsl(0, 0%, 40%); border-bottom: 0.3rem solid hsl(0, 0%, 40%);
} }