diff --git a/app/dependencies.py b/app/dependencies.py index dbc78c6..a29a7a1 100644 --- a/app/dependencies.py +++ b/app/dependencies.py @@ -1,6 +1,11 @@ import sqlalchemy +from sqlalchemy.ext.automap import automap_base +from sqlalchemy.orm import Session + +Base = automap_base() dbEngine = sqlalchemy.create_engine('sqlite:///db.sqlite') -def get_token_header(): - pass \ No newline at end of file +dbSession = Session(dbEngine) + +Base.prepare(dbEngine, reflect=True) diff --git a/app/main.py b/app/main.py index 30744e0..19528c7 100644 --- a/app/main.py +++ b/app/main.py @@ -1,16 +1,30 @@ -from fastapi import FastAPI -from app.routers import admin, user +from fastapi import FastAPI, Request +from app.routers import admin, user, songs +from fastapi.staticfiles import StaticFiles +from fastapi.responses import HTMLResponse +from fastapi.templating import Jinja2Templates app = FastAPI() app.include_router(admin.router) app.include_router(user.router) +app.include_router(songs.router) -@app.get("/") -def root(): - return {"message": "Hello World"} +app.mount("/static", StaticFiles(directory="static"), name="static") +templates = Jinja2Templates(directory="templates") + +@app.get("/", response_class=HTMLResponse) +async def root(request: Request, session_id : str = ""): + if session_id == "": + return templates.TemplateResponse( + request=request, name="landing.html" + ) + else: + return templates.TemplateResponse( + request=request, name="voting.html" + ) # 1PMy17eraogNUz436w3aZKxyij39G1didaN02Ka_-45Q -# 71046222 +# 71046222 \ No newline at end of file diff --git a/app/routers/admin.py b/app/routers/admin.py index 561f1f6..54e393f 100644 --- a/app/routers/admin.py +++ b/app/routers/admin.py @@ -1,8 +1,7 @@ -from fastapi import APIRouter, Depends, HTTPException, Security -from app.models import Genre, Song, GoogleFile -from app.dependencies import get_token_header, dbEngine -from app.routers.user import get_current_user, User -from typing import Annotated +from fastapi import APIRouter, HTTPException, Security, File, UploadFile +from app.models import GoogleFile +from app.dependencies import dbEngine +from app.routers.user import get_current_user import gspread from gspread.urls import DRIVE_FILES_API_V3_URL import pandas as pd @@ -93,3 +92,8 @@ def process_worksheets(): song_list.to_sql(name='songs', con=dbEngine, index=False, if_exists='append') # song_list.to_csv("song-list.csv") + + +@router.post("/process_file") +async def create_upload_file(file: UploadFile): + return {"filename": file.filename} \ No newline at end of file diff --git a/app/routers/songs.py b/app/routers/songs.py new file mode 100644 index 0000000..39da8c0 --- /dev/null +++ b/app/routers/songs.py @@ -0,0 +1,16 @@ +from fastapi import APIRouter, HTTPException, Security +from app.models import Song +from app.dependencies import dbEngine, Base, dbSession +from app.routers.user import get_current_user, User +from typing import Annotated + +router = APIRouter( + prefix="/songs", + #dependencies=[Security(get_current_user, scopes=["public"])], + responses={404: {"description": "Not found"}}, +) + + +@router.get("/") +async def get_songs() -> list[dict]: + return dbSession.query(Base.songs).all() diff --git a/app/routers/user.py b/app/routers/user.py index 36a738c..bd2ff26 100644 --- a/app/routers/user.py +++ b/app/routers/user.py @@ -12,7 +12,7 @@ from app.secrets import SECRET_KEY, fake_users_db # openssl rand -hex 32 ALGORITHM = "HS256" -ACCESS_TOKEN_EXPIRE_MINUTES = 30 +ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 31 class Token(BaseModel): @@ -42,7 +42,8 @@ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") oauth2_scheme = OAuth2PasswordBearer( tokenUrl="user/token", scopes={ - "admin": "Perform admin actions." + "admin": "Perform admin actions.", + "public": "Perform public actions." } ) @@ -65,6 +66,7 @@ def get_user(db, username: str): return UserInDB(**user_dict) + def authenticate_user(fake_db, username: str, password: str): user = get_user(fake_db, username) if not user: @@ -84,7 +86,6 @@ def create_access_token(data: dict, expires_delta: timedelta | None = None): encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt - async def get_current_user( security_scopes: SecurityScopes, token: Annotated[str, Depends(oauth2_scheme)] ): @@ -144,3 +145,11 @@ async def login_for_access_token( data={"sub": user.username, "scopes": user.scopes}, expires_delta=access_token_expires ) return Token(access_token=access_token, token_type="bearer") + +# @router.get("/public_token") +# async def get_public_access_token(secret_identity : str) -> Token: +# access_token_expires = timedelta(minutes=60*24*365) +# access_token = create_access_token( +# data={"sub": "public", "secret_identity" : secret_identity, "scopes": ["public"]}, expires_delta=access_token_expires +# ) +# return Token(access_token=access_token, token_type="bearer") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 8a7a57b..447a959 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ numpy sqlalchemy python-jose[cryptography] passlib[bcrypt] -python-multipart \ No newline at end of file +python-multipart +jinja2 \ No newline at end of file diff --git a/static/colors.css b/static/colors.css new file mode 100644 index 0000000..9859662 --- /dev/null +++ b/static/colors.css @@ -0,0 +1,90 @@ +.cat-1 { + background-color: #1f77b4; +} +.cat-2 { + background-color: #ff7f0e; +} +.cat-3 { + background-color: #2ca02c; +} +.cat-4 { + background-color: #d62728; +} +.cat-5 { + background-color: #9467bd; +} +.cat-6 { + background-color: #8c564b; +} +.cat-7 { + background-color: #e377c2; +} +.cat-8 { + background-color: #7f7f7f; +} +.cat-9 { + background-color: #bcbd22; +} +.cat-10 { + background-color: #17becf; +} +.cat-11 { + background-color: #a1c9f4; +} +.cat-12 { + background-color: #ffb482; +} +.cat-13 { + background-color: #8de5a1; +} +.cat-14 { + background-color: #ff9f9b; +} +.cat-15 { + background-color: #d0bbff; +} +.cat-16 { + background-color: #debb9b; +} +.cat-17 { + background-color: #fab0e4; +} +.cat-18 { + background-color: #cfcfcf; +} +.cat-19 { + background-color: #fffea3; +} +.cat-20 { + background-color: #b9f2f0; +} +.cat-21 { + background-color: #001c7f; +} +.cat-22 { + background-color: #b1400d; +} +.cat-23 { + background-color: #12711c; +} +.cat-24 { + background-color: #8c0800; +} +.cat-25 { + background-color: #591e71; +} +.cat-26 { + background-color: #592f0d; +} +.cat-27 { + background-color: #a23582; +} +.cat-28 { + background-color: #3c3c3c; +} +.cat-29 { + background-color: #b8850a; +} +.cat-30 { + background-color: #006374; +} diff --git a/static/cover.jpg b/static/cover.jpg new file mode 100644 index 0000000..1f3d723 Binary files /dev/null and b/static/cover.jpg differ diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..7db222f --- /dev/null +++ b/static/index.html @@ -0,0 +1,15 @@ + + + + Hello jQuery + + + + + +
+

The ID is

+

The content is

+
+ + diff --git a/static/landing_script.js b/static/landing_script.js new file mode 100644 index 0000000..d822fbc --- /dev/null +++ b/static/landing_script.js @@ -0,0 +1,8 @@ +$(document).ready(function () { + var s_id = localStorage.getItem('session_id') + if (s_id === null) { + s_id = window.crypto.randomUUID(); + localStorage.setItem('session_id', s_id) + } + $('.vote-from-existing').attr('href', '?session_id=' + s_id); +}); diff --git a/static/neutral.svg b/static/neutral.svg new file mode 100644 index 0000000..1441229 --- /dev/null +++ b/static/neutral.svg @@ -0,0 +1,22 @@ + + + + + + + diff --git a/static/no.svg b/static/no.svg new file mode 100644 index 0000000..83bfc4c --- /dev/null +++ b/static/no.svg @@ -0,0 +1,22 @@ + + + + + + + diff --git a/static/play.svg b/static/play.svg new file mode 100644 index 0000000..c229371 --- /dev/null +++ b/static/play.svg @@ -0,0 +1,23 @@ + + + + + + + diff --git a/static/site.css b/static/site.css new file mode 100644 index 0000000..899a81d --- /dev/null +++ b/static/site.css @@ -0,0 +1,184 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + padding: 0.5em; +} + +@media only screen and (min-resolution: 200dpi) { + body { + font-size: 3.2vmin; + } +} + +.clear { + clear: both; +} + +.song { + background-color: #f0f0f0; + padding: 0.4em; + border-radius: 0.5em; + width: 30em; + font-family: sans-serif; + margin-bottom: 1em; +} + + +.cover-container { + position: relative; + width: 10.67em; + height: 6em; + float: left; + margin-right: 1em; +} + +.cover { + display: block; + width: 100%; + height: auto; +} + + +.overlay { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + height: 100%; + width: 100%; + opacity: 0; + transition: .3s ease; +} + +.cover-container:hover { + cursor: pointer; +} + +.cover-container:hover .overlay { + opacity: 1; + filter: drop-shadow(0px 0px 1em black); +} + +.overlay img { + height: 50%; + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + filter: invert(); + transition: .1s ease; +} + +.cover-container:active .overlay img { + height: 60%; +} + + +.vote-buttons { + display: inline-block; +} + +.button { + height: 2em; + width: 3em; + display: inline-block; + text-align: center; + vertical-align: middle; + font-size: 1.5em; + position: relative; +} + +.button img { + height: 30%; + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + transition: .1s ease; +} + +.button:active img { + height: 40%; +} + +.button-no { + background-color: #e1412f; + border-top-left-radius: 1em; + border-bottom-left-radius: 1em; +} + +.button-neutral { + background-color: #f5bb00; + margin-left: 2px; + margin-right: 2px; +} + +.button-yes { + background-color: #48a84f; + border-top-right-radius: 1em; + border-bottom-right-radius: 1em; +} + +.button:not(.selected):not(:hover) { + background-color: #b0b0b0; +} + +.button:hover { + filter: drop-shadow(2px 2px 2px) brightness(95%); + cursor: pointer; +} + +.button span { + position: relative; +} + +.button-no span { + top: 10%; +} + +.button-yes span { + top: 10%; +} + +.button-neutral span { + top: 20%; +} + +.categories { + width: 60%; + overflow: hidden; + cursor: grab; + white-space: nowrap; + font-size: 0.7em; + line-height: 1.2em; + margin-top: 0.2em; +} + +.categories span { + border-radius: 1.2em; + padding: 0 0.5em 0 0.5em; + margin-right: 0.4em; + display: inline-block; + color: white; + max-width: 10em; + overflow: clip; +} + +.vote-buttons { + margin-top: 0.5em; +} + +.song-data { + width: 100%; + overflow: clip; + white-space: nowrap; +} \ No newline at end of file diff --git a/static/voting_script.js b/static/voting_script.js new file mode 100644 index 0000000..dade61a --- /dev/null +++ b/static/voting_script.js @@ -0,0 +1,70 @@ +document.addEventListener('DOMContentLoaded', function () { + const eles = document.getElementsByClassName('categories'); + Array.prototype.forEach.call(eles, ele => { + + + //ele.style.cursor = 'grab'; + + let pos = { top: 0, left: 0, x: 0, y: 0 }; + + const mouseDownHandler = function (e) { + ele.style.cursor = 'grabbing'; + ele.style.userSelect = 'none'; + + pos = { + left: ele.scrollLeft, + top: ele.scrollTop, + // Get the current mouse position + x: e.clientX, + y: e.clientY, + }; + + document.addEventListener('mousemove', mouseMoveHandler); + document.addEventListener('mouseup', mouseUpHandler); + }; + + const mouseMoveHandler = function (e) { + // How far the mouse has been moved + const dx = e.clientX - pos.x; + const dy = e.clientY - pos.y; + + // Scroll the element + ele.scrollTop = pos.top - dy; + ele.scrollLeft = pos.left - dx; + }; + + const mouseUpHandler = function () { + ele.style.cursor = 'grab'; + ele.style.removeProperty('user-select'); + + document.removeEventListener('mousemove', mouseMoveHandler); + document.removeEventListener('mouseup', mouseUpHandler); + }; + + // Attach the handler + ele.addEventListener('mousedown', mouseDownHandler); + }); +}); + +const getQueryParameter = (param) => new URLSearchParams(document.location.search.substring(1)).get(param); + +$(document).ready(function () { + var session_id = getQueryParameter("session_id"); + + $(".greeting-id").append(session_id); + $(".greeting-id").append("Foo"); + $.ajax({ + url: "/songs" + }).then(function (user_list) { + $('.greeting-id').append(user_list.total); + localStorage.setItem("test-storage", user_list.total); + var users = []; + $.each(user_list.data, function (key, user) { + users.push("
  • " + user.first_name + "
  • "); + }); + $("