Skip to content

Commit

Permalink
Resturctures files and adds tests
Browse files Browse the repository at this point in the history
  • Loading branch information
sdkaraarslan committed Jan 21, 2024
1 parent 8452953 commit 3598bd9
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 4 deletions.
Empty file added app/__init__.py
Empty file.
94 changes: 94 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import secrets
from spotipy.oauth2 import SpotifyOAuth
from dotenv import load_dotenv
import os
from fastapi import FastAPI, Form, Request, Depends
from fastapi.responses import RedirectResponse
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.sessions import SessionMiddleware
from session_cache_handler import SessionCacheHandler
from fastapi.templating import Jinja2Templates
from typing import Annotated

# Jinja2 template engine
templates = Jinja2Templates(directory="templates")


load_dotenv("../.env")
CLIENT_SECRET = os.environ.get("CLIENT_SECRET")
CLIENT_ID = os.environ.get("CLIENT_ID")
REDIRECT_URI = os.environ.get("REDIRECT_URI")

app = FastAPI() # FastAPI instance
# Middlewares
app.add_middleware(SessionMiddleware, secret_key=secrets.token_urlsafe(32))
# For more info on CORS: https://fastapi.tiangolo.com/tutorial/cors/?h=cors
origins = [
"http://localhost:3000",
"http://localhost:8000",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)


# Dependencies
def get_spotify_oauth(request: Request):
return SpotifyOAuth(
scope="user-library-modify",
client_id=CLIENT_ID,
client_secret=CLIENT_SECRET,
redirect_uri=REDIRECT_URI,
cache_handler=SessionCacheHandler(request.session),
)


@app.get("/")
def index(
auth_manager: Annotated[SpotifyOAuth, Depends(get_spotify_oauth)], request: Request
):
# Check if we have a cached token
is_authenticated = bool(auth_manager.get_cached_token())
# Render index.html template
return templates.TemplateResponse(
"index.html", {"request": request, "is_authenticated": is_authenticated}
)


@app.get("/login")
def login(auth_manager: Annotated[SpotifyOAuth, Depends(get_spotify_oauth)]):
return RedirectResponse(auth_manager.get_authorize_url())


@app.get("/callback")
async def callback(
auth_manager: Annotated[SpotifyOAuth, Depends(get_spotify_oauth)], request: Request
):
# Authorization code that spotify sends back https://developer.spotify.com/documentation/web-api/tutorials/code-flow
code = request.query_params.get("code")
if not code:
return {"Error": "No code provided"}

# Exchange code for an access token
token_info = auth_manager.get_access_token(code)

if not token_info:
return {"Error": "Could not retrieve token"}

# Finally redirect to the index page
return RedirectResponse("/")


@app.post("/playlist")
def playlist(sentence: str = Form(...)):
return {"status": "ok"}


# Healthcheck endpoint to make sure the server is running
@app.get("/healthcheck")
def healthcheck():
return {"status": "ok"}
11 changes: 8 additions & 3 deletions playlistfy.py → app/playlistfy.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import spotipy
from dataclasses import dataclass


@dataclass
class Playlist:
name: str
Expand All @@ -9,11 +10,15 @@ class Playlist:


class Playlistify:
def __init__(self, spotify_client: spotipy.Spotify, playlist_name: str, playlist_description:str):
def __init__(
self,
spotify_client: spotipy.Spotify,
playlist_name: str,
playlist_description: str,
):
self.playlist_name = playlist_name
self.playlist_description = playlist_description
self.client = spotify_client


def create_playlist(self, sentence: str) -> Playlist:
pass
pass
3 changes: 2 additions & 1 deletion session_cache_handler.py → app/session_cache_handler.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from spotipy import CacheHandler


class SessionCacheHandler(CacheHandler):
def __init__(self, session):
self.session = session
Expand All @@ -8,4 +9,4 @@ def get_cached_token(self):
return self.session.get("spotify_token_info")

def save_token_to_cache(self, token_info):
self.session["spotify_token_info"] = token_info
self.session["spotify_token_info"] = token_info
43 changes: 43 additions & 0 deletions app/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet">
</head>
<body class="bg-gray-100 h-screen">
<div class="container mx-auto p-8">
<div class="max-w-md mx-auto bg-white rounded-lg overflow-hidden md:max-w-lg">
<div class="md:flex">
<div class="w-full p-4">
<div class="relative">
<h1 class="text-2xl font-bold text-gray-700 mb-2">Welcome to the Home Page</h1>
{% if is_authenticated %}
<form action="/playlist" method="post" class="pt-2">
{% else %}
<form action="/login" method="get" class="pt-2">
{% endif %}
<div class="mb-4">
<label class="block text-gray-700 text-sm font-bold mb-2" for="text_field">
Enter a sentence
</label>
<input type="text" name="sentence" id="text_field"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
placeholder="Enter text">
</div>
<div class="flex justify-end">
<button type="submit"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
{% if is_authenticated %}
Submit
{% else %}
Log in with Spotify
{% endif %}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
26 changes: 26 additions & 0 deletions app/test_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from fastapi.testclient import TestClient
from spotipy.oauth2 import SpotifyOAuth
from unittest.mock import Mock
from main import get_spotify_oauth

from main import app

client = TestClient(app, follow_redirects=False)


def test_login():
mock_auth_manager = Mock(spec=SpotifyOAuth)
expected_url = (
mock_auth_manager.get_authorize_url.return_value
) = "http://example.com"
app.dependency_overrides[get_spotify_oauth] = lambda: mock_auth_manager

response = client.get("/login")
assert response.status_code == 307
assert response.headers["location"] == expected_url


def test_healthcheck():
response = client.get("/healthcheck")
assert response.status_code == 200
assert response.json() == {"status": "ok"}
15 changes: 15 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
annotated-types==0.6.0
anyio==4.2.0
black==23.12.1
certifi==2023.11.17
charset-normalizer==3.3.2
click==8.1.7
fastapi==0.109.0
flake8==7.0.0
h11==0.14.0
httpcore==1.0.2
httptools==0.6.1
httpx==0.26.0
idna==3.6
iniconfig==2.0.0
itsdangerous==2.1.2
Jinja2==3.1.3
MarkupSafe==2.1.4
mccabe==0.7.0
mypy-extensions==1.0.0
packaging==23.2
pathspec==0.12.1
platformdirs==4.1.0
pluggy==1.3.0
pycodestyle==2.11.1
pydantic==2.5.3
pydantic_core==2.14.6
pyflakes==3.2.0
pytest==7.4.4
python-dotenv==1.0.0
python-multipart==0.0.6
PyYAML==6.0.1
redis==5.0.1
requests==2.31.0
Expand Down

0 comments on commit 3598bd9

Please sign in to comment.