Skip to content

Commit 05ee50a

Browse files
committed
WIP
1 parent 447e02b commit 05ee50a

34 files changed

+498
-151
lines changed

backend/src/controllers/database.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
get_playlist_albums,
1414
get_playlist_by_id_or_none,
1515
get_user_playlists,
16-
update_playlist,
16+
update_playlist_with_albums,
1717
)
1818
from src.database.crud.user import get_or_create_user
1919
from src.musicbrainz import MusicbrainzClient
@@ -59,7 +59,7 @@ def populate_user():
5959
access_token=access_token, id=simplified_playlist.id
6060
),
6161
]
62-
update_playlist(playlist, albums)
62+
update_playlist_with_albums(playlist, albums)
6363

6464
return make_response("Playlist data populated", 201)
6565

backend/src/controllers/music_data.py

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
from logging import Logger
22
from flask import Blueprint, jsonify, make_response, request
3-
from src.database.crud.playlist import get_user_playlists
3+
from src.database.crud.playlist import (
4+
get_playlist_albums,
5+
get_playlist_albums_with_genres,
6+
get_playlist_by_id_or_none,
7+
get_recent_user_playlists,
8+
get_user_playlists,
9+
update_playlist_info,
10+
update_playlist_with_albums,
11+
)
412
from src.dataclasses.playback_info import PlaybackInfo
513
from src.dataclasses.playback_request import StartPlaybackRequest
614
from src.dataclasses.playlist import Playlist
@@ -33,17 +41,38 @@ def index():
3341
)
3442
)
3543

44+
@music_controller.route("playlists/recent")
45+
def recent_playlists():
46+
user_id = request.cookies.get("user_id")
47+
limit = request.args.get("limit", type=int)
48+
offset = request.args.get("offset", type=int)
49+
search = request.args.get("search")
50+
51+
return jsonify(
52+
get_recent_user_playlists(
53+
user_id=user_id,
54+
limit=limit,
55+
offset=offset,
56+
search=search
57+
)
58+
)
59+
3660
@music_controller.route("playlist/<id>", methods=["GET"])
3761
def get_playlist(id):
38-
access_token = request.cookies.get("spotify_access_token")
39-
playlist = spotify.get_playlist(access_token=access_token, id=id)
40-
return playlist.model_dump()
62+
db_playlist = get_playlist_by_id_or_none(id)
63+
if db_playlist is not None:
64+
return jsonify(db_playlist.__data__)
65+
else:
66+
access_token = request.cookies.get("spotify_access_token")
67+
playlist = spotify.get_playlist(access_token=access_token, id=id)
68+
return playlist.model_dump()
4169

4270
@music_controller.route("playlist/<id>", methods=["POST"])
4371
def post_edit_playlist(id):
4472
access_token = request.cookies.get("spotify_access_token")
4573
name = request.json.get("name")
4674
description = request.json.get("description")
75+
update_playlist_info(id=id, name=name, description=description)
4776
spotify.update_playlist(
4877
access_token=access_token,
4978
id=id,
@@ -54,13 +83,18 @@ def post_edit_playlist(id):
5483

5584
@music_controller.route("playlist/<id>/albums", methods=["GET"])
5685
def get_playlist_album_info(id):
57-
access_token = request.cookies.get("spotify_access_token")
58-
return [
59-
album.model_dump()
60-
for album in spotify.get_playlist_album_info(
61-
access_token=access_token, id=id
62-
)
63-
]
86+
db_playlist = get_playlist_by_id_or_none(id)
87+
if db_playlist is not None:
88+
album_info_list = get_playlist_albums_with_genres(id)
89+
return jsonify(album_info_list)
90+
else:
91+
access_token = request.cookies.get("spotify_access_token")
92+
return [
93+
album.model_dump()
94+
for album in spotify.get_playlist_album_info(
95+
access_token=access_token, id=id
96+
)
97+
]
6498

6599
@music_controller.route("find_associated_playlists", methods=["POST"])
66100
def find_associated_playlists():

backend/src/database/crud/playlist.py

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
11
from typing import List, Optional
22
from src.database.crud.album import create_album_or_none
3-
from src.database.models import DbAlbum, DbPlaylist, DbUser, PlaylistAlbumRelationship
3+
from src.database.models import (
4+
AlbumArtistRelationship,
5+
AlbumGenreRelationship,
6+
DbAlbum,
7+
DbArtist,
8+
DbGenre,
9+
DbPlaylist,
10+
DbUser,
11+
PlaylistAlbumRelationship,
12+
peewee_model_to_dict,
13+
)
414
from src.dataclasses.album import Album
515
from src.dataclasses.playlist import Playlist
16+
from peewee import fn
17+
import re
18+
from datetime import datetime
619

720

821
def get_playlist_by_id_or_none(id: str):
@@ -27,7 +40,34 @@ def create_playlist(playlist: Playlist, albums: List[Album], user: DbUser):
2740
return playlist
2841

2942

30-
def update_playlist(playlist: Playlist, albums: List[Album]):
43+
def update_playlist_info(
44+
id: str,
45+
name: Optional[str] = None,
46+
description: Optional[str] = None,
47+
snapshot_id: Optional[str] = None,
48+
uri: Optional[str] = None,
49+
) -> DbPlaylist | None:
50+
update_data = {}
51+
if name is not None:
52+
update_data["name"] = name
53+
if description is not None:
54+
update_data["description"] = description
55+
if snapshot_id is not None:
56+
update_data["snapshot_id"] = snapshot_id
57+
if uri is not None:
58+
update_data["uri"] = uri
59+
60+
if update_data:
61+
query = DbPlaylist.update(update_data).where(DbPlaylist.id == id)
62+
query.execute()
63+
64+
updated_playlist = DbPlaylist.get_by_id(id)
65+
return updated_playlist
66+
67+
return None
68+
69+
70+
def update_playlist_with_albums(playlist: Playlist, albums: List[Album]):
3171
playlist = DbPlaylist.update(
3272
id=playlist.id,
3373
description=playlist.description,
@@ -78,6 +118,48 @@ def get_user_playlists(
78118
return list(query.execute())
79119

80120

121+
def get_recent_user_playlists(
122+
user_id: str,
123+
limit: Optional[int] = None,
124+
offset: Optional[int] = None,
125+
search: Optional[str] = None,
126+
) -> List[DbPlaylist]:
127+
128+
# Use TO_DATE function in PostgreSQL to convert the extracted date into a proper date
129+
query = (
130+
DbPlaylist.select()
131+
.where(
132+
(
133+
DbPlaylist.name.startswith("New Albums")
134+
| DbPlaylist.name.startswith("Best Albums")
135+
)
136+
)
137+
# Convert the date string into a proper date format for ordering
138+
.order_by(fn.TO_DATE(fn.RIGHT(DbPlaylist.name, 8), "DD/MM/YY").desc())
139+
.limit(limit)
140+
.offset(offset)
141+
)
142+
143+
if search:
144+
query = query.where(DbPlaylist.name.contains(search))
145+
146+
playlists = []
147+
for playlist in query:
148+
# Add playlist to the result with parsed date
149+
playlists.append(
150+
{
151+
"id": playlist.id,
152+
"name": playlist.name,
153+
"description": playlist.description,
154+
"image_url": playlist.image_url,
155+
"user_id": playlist.user.id,
156+
"snapshot_id": playlist.snapshot_id,
157+
"uri": playlist.uri,
158+
}
159+
)
160+
return playlists
161+
162+
81163
def get_playlist_albums(playlist_id: str) -> List[DbAlbum]:
82164
query = (
83165
DbAlbum.select()
@@ -86,3 +168,51 @@ def get_playlist_albums(playlist_id: str) -> List[DbAlbum]:
86168
.where(DbPlaylist.id == playlist_id)
87169
)
88170
return list(query)
171+
172+
173+
def get_playlist_albums_with_genres(playlist_id: str) -> List[dict]:
174+
# Step 1: Retrieve all albums associated with the given playlist
175+
albums_query = (
176+
DbAlbum.select()
177+
.join(
178+
PlaylistAlbumRelationship,
179+
on=(PlaylistAlbumRelationship.album == DbAlbum.id),
180+
)
181+
.join(DbPlaylist, on=(PlaylistAlbumRelationship.playlist == DbPlaylist.id))
182+
.where(DbPlaylist.id == playlist_id)
183+
)
184+
185+
# Step 2: Retrieve genres and artists for each album
186+
albums_with_details = []
187+
for album in albums_query:
188+
genres = (
189+
DbGenre.select(DbGenre.name)
190+
.join(
191+
AlbumGenreRelationship, on=(AlbumGenreRelationship.genre == DbGenre.id)
192+
)
193+
.where(AlbumGenreRelationship.album == album.id)
194+
.execute()
195+
)
196+
197+
artists = (
198+
DbArtist.select()
199+
.join(
200+
AlbumArtistRelationship,
201+
on=(AlbumArtistRelationship.artist == DbArtist.id),
202+
)
203+
.where(AlbumArtistRelationship.album == album.id)
204+
)
205+
206+
album_details = {
207+
"id": album.id,
208+
"name": album.name,
209+
"uri": album.uri,
210+
"image_url": album.image_url,
211+
"release_date": album.release_date,
212+
"total_tracks": album.total_tracks,
213+
"genres": [genre.name for genre in genres],
214+
"artists": [peewee_model_to_dict(artist) for artist in artists],
215+
}
216+
albums_with_details.append(album_details)
217+
218+
return albums_with_details

backend/src/database/models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
database = PostgresqlDatabase(Config().DB_CONNECTION_STRING)
1212

13+
def peewee_model_to_dict(model_instance):
14+
return {field: getattr(model_instance, field) for field in model_instance._meta.fields}
1315

1416
class BaseModel(Model):
1517
class Meta:

frontend/eslint.config.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ export default [
1010
pluginJs.configs.recommended,
1111
...tseslint.configs.recommended,
1212
pluginReact.configs.flat.recommended,
13-
{
14-
rules: {"unused-imports/no-unused-imports": "error"}
15-
},
13+
// {
14+
// rules: {"unused-imports/no-unused-imports": "error"}
15+
// },
1616
{
1717
ignores: ["public/bundle.js"],
1818
},

frontend/package-lock.json

Lines changed: 40 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"homepage": "https://github.com/CalPinSW/playlist-manager#readme",
2929
"devDependencies": {
3030
"@eslint/js": "^9.9.0",
31+
"@types/lodash": "^4.17.7",
3132
"@types/node": "^20.12.7",
3233
"@types/react-dom": "^18.2.18",
3334
"esbuild": "^0.23.1",
@@ -41,9 +42,11 @@
4142
"typescript-eslint": "^8.2.0"
4243
},
4344
"dependencies": {
44-
"@tanstack/react-table": "^8.20.1",
4545
"@tanstack/react-query": "^5.51.21",
46+
"@tanstack/react-table": "^8.20.1",
4647
"dotenv": "^16.4.5",
48+
"embla-carousel-react": "^8.2.1",
49+
"lodash": "^4.17.21",
4750
"moment": "^2.30.1",
4851
"react": "^18.3.1",
4952
"react-dom": "^18.3.1",

frontend/public/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!DOCTYPE html>
2-
<html lang="en" style="height: 100%">
2+
<html lang="en" style="height: 100%" >
33
<head>
44
<meta charset="UTF-8" />
55
<meta

0 commit comments

Comments
 (0)