Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add database to project #47

Merged
merged 10 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ansible/.ansible-secrets.template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ spotify_client_id: spotify_client_id
spotify_secret: spotify_secret
debug_mode: false
flask_secret_key: flask_secret_key
musicbrainz_url: musicbrainz_url
musicbrainz_user_agent: musicbrainz_user_agent
db_connection_string: db_connection_string
7 changes: 7 additions & 0 deletions backend/.env.j2
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,10 @@ SECRET_KEY={{flask_secret_key}}
SPOTIFY_CLIENT_ID={{spotify_client_id}}
SPOTIFY_SECRET={{spotify_secret}}
SPOTIFY_REDIRECT_URI="{{backend_url}}/auth/get-user-code"

# Musicbrainz Api
MUSICBRAINZ_URL={{musicbrainz_url}}
MUSICBRAINZ_USER_AGENT={{musicbrainz_user_agent}}

# Database Connection String
DB_CONNECTION_STRING={{db_connection_string}}
7 changes: 7 additions & 0 deletions backend/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,10 @@ SECRET_KEY="secret-key"
SPOTIFY_CLIENT_ID="https://developer.spotify.com/dashboard"
SPOTIFY_SECRET="https://developer.spotify.com/dashboard"
SPOTIFY_REDIRECT_URI="http://localhost:8080/spotify-redirect"

# Musicbrainz Api
MUSICBRAINZ_URL=https://musicbrainz.org/ws/2
MUSICBRAINZ_USER_AGENT="Application PlaylistManager/1.0 - Hobby project (put maintainers email here)"

# Database Connection String
DB_CONNECTION_STRING=postgresql://username:password@hostname:port/database
7 changes: 7 additions & 0 deletions backend/.env.test
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,10 @@ SECRET_KEY="secret-key"
SPOTIFY_CLIENT_ID="https://developer.spotify.com/dashboard"
SPOTIFY_SECRET="https://developer.spotify.com/dashboard"
SPOTIFY_REDIRECT_URI="http://localhost:8080/spotify-redirect"

# Musicbrainz Api
MUSICBRAINZ_URL=https://musicbrainz.org/ws/2
MUSICBRAINZ_USER_AGENT="Application PlaylistManager/1.0 - Hobby project (put maintainers email here)"

# Database Connection String
DB_CONNECTION_STRING=postgresql://username:password@hostname:port/database
87 changes: 83 additions & 4 deletions backend/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ packages = [
python = "^3.10"
flask = "^3.0.3"
python-dotenv = "^1.0.0"
flask-cors = "^4.0.1"
flask-cors = "^5.0.0"
pydantic = "^2.6.4"
requests = "^2.32.3"
gunicorn = "^22.0.0"
peewee = "^3.17.6"
psycopg = "^3.2.1"
asyncio = "^3.4.3"
psycopg2 = "^2.9.9"
CalPinSW marked this conversation as resolved.
Show resolved Hide resolved

[tool.poetry.group.dev.dependencies]
pytest = "^8.1.1"
Expand Down
10 changes: 9 additions & 1 deletion backend/src/app.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
from flask import Flask, make_response
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On autoformatting:

  • I can't remember the exact details we discussed, but what I see is that auto-formatting works in JS but not in Python, which I think matches what you see
  • First I checked that I do have a Python formatter installed, I actively added the Black-formatter extension from Microsoft but there was a default one already
  • But looking at the Black formatter output (in the "output" tab of the terminal)

2024-09-13 11:09:15.445 [info] The language client requires VS Code version ^1.82.0 but received version 1.79.2

Updating my VSCode (and it was about time I did!) fixed the issue, so maybe you're seeing the same?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact it's actually the JS side that's not working for me 😁 . I'll have a bit more of a look into it. Aware that trying to get it in in this PR might cause a lot of unnecessary formatting changes to clutter up the actual changes.

from flask_cors import CORS
from src.controllers.database import database_controller
from src.controllers.spotify import spotify_controller
from src.exceptions.Unauthorized import UnauthorizedException
from src.flask_config import Config
from src.musicbrainz import MusicbrainzClient
from src.spotify import SpotifyClient
from src.controllers.auth import auth_controller


def create_app():
app = Flask(__name__)
spotify = SpotifyClient()
musicbrainz = MusicbrainzClient()

app.config.from_object(Config())
app.config["CORS_HEADERS"] = "Content-Type"

Expand All @@ -21,7 +25,7 @@ def create_app():
SESSION_COOKIE_SECURE="True",
)

cors = CORS(
CORS(
app,
resources={
r"/*": {
Expand All @@ -44,4 +48,8 @@ def handle_unauthorized_exception(_):

app.register_blueprint(auth_controller(spotify=spotify))
app.register_blueprint(spotify_controller(spotify=spotify))
app.register_blueprint(
database_controller(spotify=spotify, musicbrainz=musicbrainz)
)

return app
152 changes: 152 additions & 0 deletions backend/src/controllers/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
from flask import Blueprint, make_response, request

from src.database.crud.album import (
add_genres_to_album,
get_album_artists,
get_album_genres,
get_user_albums,
update_album,
)
from src.database.crud.genre import create_genre
from src.database.crud.playlist import (
create_playlist,
get_playlist_albums,
get_playlist_by_id_or_none,
get_user_playlists,
update_playlist,
)
from src.database.crud.user import get_or_create_user
from src.musicbrainz import MusicbrainzClient
from src.spotify import SpotifyClient
from time import sleep


def database_controller(spotify: SpotifyClient, musicbrainz: MusicbrainzClient):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor, but conceptually this name bugs me because it's very much exposing internal details as API details... but I can't immediately offer better! The conceptual tie between the things we own vs Spotify is strong

I'll leave this as a thought, but I wouldn't worry about changing it

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it doesn't quite seem right. I think once I've got the database management happening independently I can get rid of it entirely. Will leave as is for now though.

database_controller = Blueprint(
name="database_controller", import_name=__name__, url_prefix="/database"
)

@database_controller.route("populate_user", methods=["GET"])
def populate_user():
access_token = request.cookies.get("spotify_access_token")
user = spotify.get_current_user(access_token)
simplified_playlists = spotify.get_all_playlists(
user_id=user.id, access_token=access_token
)
(db_user,) = get_or_create_user(user)

for simplified_playlist in simplified_playlists:
if "Albums" in simplified_playlist.name:
db_playlist = get_playlist_by_id_or_none(simplified_playlist.id)

if db_playlist is None:
[playlist, albums] = [
spotify.get_playlist(
access_token=access_token, id=simplified_playlist.id
),
spotify.get_playlist_album_info(
access_token=access_token, id=simplified_playlist.id
),
]
create_playlist(playlist, albums, db_user)
else:
if db_playlist.snapshot_id != simplified_playlist.snapshot_id:
[playlist, albums] = [
spotify.get_playlist(
access_token=access_token, id=simplified_playlist.id
),
spotify.get_playlist_album_info(
access_token=access_token, id=simplified_playlist.id
),
]
update_playlist(playlist, albums)

return make_response("Playlist data populated", 201)

@database_controller.route(
"populate_additional_album_details_from_playlist", methods=["GET"]
)
def populate_additional_album_details_from_playlist():
access_token = request.cookies.get("spotify_access_token")
user = spotify.get_current_user(access_token)
playlists = get_user_playlists(user.id)

for playlist in playlists:
albums = get_playlist_albums(playlist.id)
if albums == []:
continue
batch_albums = split_list(albums, 20)
for album_chunk in batch_albums:
sleep(0.5)
albums = spotify.get_multiple_albums(
access_token=access_token, ids=[album.id for album in album_chunk]
)
for db_album in albums:
album = spotify.get_album(access_token=access_token, id=db_album.id)
update_album(album)

return make_response("Playlist data populated", 201)

@database_controller.route("populate_additional_album_details", methods=["GET"])
def populate_additional_album_details():
access_token = request.cookies.get("spotify_access_token")
user = spotify.get_current_user(access_token)
albums = get_user_albums(user.id)
albums_without_label = [album for album in albums] # if album.label is None]
if albums_without_label == []:
return make_response("No Albums to process", 204)
batch_albums = split_list(albums_without_label, 20)
for album_chunk in batch_albums:
sleep(0.5)
albums = spotify.get_multiple_albums(
access_token=access_token, ids=[album.id for album in album_chunk]
)
for db_album in albums:
db_album.genres = musicbrainz.get_album_genres(
db_album.artists[0].name, db_album.name
)
update_album(db_album)

return make_response("Playlist details populated", 201)

@database_controller.route("populate_universal_genre_list", methods=["GET"])
def populate_universal_genre_list():
genre_list = musicbrainz.get_genre_list()
[create_genre(genre) for genre in genre_list]
return make_response("Genre data populated", 201)

@database_controller.route("populate_user_album_genres", methods=["GET"])
def populate_user_album_genres():
access_token = request.cookies.get("spotify_access_token")
user = spotify.get_current_user(access_token)
populate_album_genres_by_user_id(user.id, musicbrainz)
return make_response("User album genres populated", 201)

return database_controller


def split_list(input_list, max_length=20):
return [
input_list[i : i + max_length] for i in range(0, len(input_list), max_length)
]


def populate_album_genres_by_user_id(
user_id: str, musicbrainz: MusicbrainzClient = MusicbrainzClient()
):
albums = get_user_albums(user_id=user_id)
print(f"processing album {0} of {len(albums)}")
skip_count = 0
for idx, db_album in enumerate(albums):
print("\033[A \033[A")
print(f"processing album {idx} of {len(albums)}, skipped {skip_count}")
if get_album_genres(db_album.id) != []:
skip_count += 1
continue
album_artists = get_album_artists(db_album)
genres = musicbrainz.get_album_genres(
artist_name=album_artists[0].name, album_title=db_album.name
)
add_genres_to_album(db_album, genres)
print("\033[A \033[A")
print(f"completed. Processed {len(albums)} albums. Skipped ")
2 changes: 2 additions & 0 deletions backend/src/controllers/spotify.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from logging import Logger
from flask import Blueprint, make_response, request
from src.dataclasses.playback_info import PlaybackInfo
from src.dataclasses.playback_request import StartPlaybackRequest
from src.dataclasses.playlist import Playlist
from src.spotify import SpotifyClient
import sys


def spotify_controller(spotify: SpotifyClient):
Expand Down
Loading