restructure
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,3 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
.venv
|
.venv
|
||||||
.vscode
|
.vscode
|
||||||
service_account.json
|
|
||||||
db.sqlite
|
|
||||||
67
app/crud.py
Normal file
67
app/crud.py
Normal 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
25
app/database.py
Normal 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
|
||||||
|
}
|
||||||
@@ -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()
|
|
||||||
15
app/main.py
15
app/main.py
@@ -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:
|
||||||
|
|||||||
@@ -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]
|
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -88,7 +89,8 @@ async def create_upload_file():
|
|||||||
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()
|
|
||||||
|
|||||||
@@ -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
18
app/schemas.py
Normal 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]
|
||||||
@@ -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)
|
|
||||||
@@ -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%);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user