From 92a2a3e055cca5f6ca0a9513244fd8b9e1aace94 Mon Sep 17 00:00:00 2001 From: Calum Pinder Date: Wed, 30 Oct 2024 08:06:19 +0000 Subject: [PATCH] Add functionality to search for albums across playlists --- backend/src/controllers/music_data.py | 21 ++++++++++++ backend/src/database/crud/playlist.py | 49 +++++++++++++++++++++++++++ frontend/src/MainPage.tsx | 26 +++++++++----- frontend/src/api/index.ts | 16 ++++++++- 4 files changed, 103 insertions(+), 9 deletions(-) diff --git a/backend/src/controllers/music_data.py b/backend/src/controllers/music_data.py index 2eba1d5..190dc64 100644 --- a/backend/src/controllers/music_data.py +++ b/backend/src/controllers/music_data.py @@ -9,6 +9,7 @@ get_recent_user_playlists, get_user_playlists, search_playlist_names, + search_playlists_by_albums, update_playlist_info, ) from src.dataclasses.playlist import Playlist @@ -20,6 +21,26 @@ def music_controller(spotify: SpotifyClient): name="music_controller", import_name=__name__, url_prefix="/music" ) + @music_controller.route("playlist_album_search") + def album_search(): + user_id = request.cookies.get("user_id") + limit = request.args.get("limit", type=int) + offset = request.args.get("offset", type=int) + search = request.args.get("search") + sort_by = request.args.get("sort_by") + desc = request.args.get("desc") == "True" + return jsonify( + search_playlists_by_albums( + user_id=user_id, + limit=limit, + offset=offset, + search=search, + sort_by=sort_by, + desc=desc, + as_dicts=True, + ) + ) + @music_controller.route("playlists") def index(): user_id = request.cookies.get("user_id") diff --git a/backend/src/database/crud/playlist.py b/backend/src/database/crud/playlist.py index 55569ae..c83546f 100644 --- a/backend/src/database/crud/playlist.py +++ b/backend/src/database/crud/playlist.py @@ -155,6 +155,55 @@ def get_recent_user_playlists( return playlists +def search_playlists_by_albums( + user_id: str, + limit: Optional[int] = None, + offset: Optional[int] = None, + search: Optional[str] = None, + sort_by: Optional[str] = None, + desc: bool = True, + as_dicts: bool = False, +) -> List[DbPlaylist]: + # Subquery to find albums where the album name or artist name contains the search query + albums_with_artists_query = ( + DbAlbum.select(DbAlbum.id).join(AlbumArtistRelationship).join(DbArtist) + ) + + if search: + albums_with_artists_query = albums_with_artists_query.where( + (DbAlbum.name.contains(search)) | (DbArtist.name.contains(search)) + ) + + # Query to find playlists containing the matching albums + playlists_with_albums_query = ( + DbPlaylist.select(DbPlaylist) + .join(PlaylistAlbumRelationship) + .join(DbAlbum) + .where(DbAlbum.id.in_(albums_with_artists_query)) + ) + + if sort_by: + sort_field = getattr(DbPlaylist, sort_by) + if desc: + playlists_with_albums_query = playlists_with_albums_query.order_by( + sort_field.desc() + ) + else: + playlists_with_albums_query = playlists_with_albums_query.order_by( + sort_field.asc() + ) + + if limit is not None: + playlists_with_albums_query = playlists_with_albums_query.limit(limit) + if offset is not None: + playlists_with_albums_query = playlists_with_albums_query.offset(offset) + + if as_dicts: + return list(playlists_with_albums_query.dicts()) + else: + return list(playlists_with_albums_query.execute()) + + def get_playlist_albums(playlist_id: str) -> List[DbAlbum]: query = ( DbAlbum.select() diff --git a/frontend/src/MainPage.tsx b/frontend/src/MainPage.tsx index d106dd1..ed4579b 100644 --- a/frontend/src/MainPage.tsx +++ b/frontend/src/MainPage.tsx @@ -1,6 +1,6 @@ import React, { FC, useState } from "react"; import { useQuery } from "@tanstack/react-query"; -import { getPlaylists, getRecentPlaylists } from "./api"; +import { getRecentPlaylists, searchPlaylistsByAlbums } from "./api"; import { Playlist } from "./interfaces/Playlist"; import Box from "./components/Box"; import AddPlaylistForm from "./AddPlaylistForm"; @@ -16,26 +16,36 @@ interface PaginationState { export const Index: FC = () => { const { isMobileView } = useWindowSize(); - const [searchRecent, setSearchRecent] = useState("") - const [search, setSearch] = useState("") + const [playlistSearch, setPlaylistSearch] = useState("") + const [albumSearch, setAlbumSearch] = useState("") const [pagination, ] = useState({ pageIndex: 0, pageSize: isMobileView ? 8 : 8, }); + const playlistQuery = useQuery({ + queryKey: ["playlists", pagination, playlistSearch], + queryFn: () => { + return getRecentPlaylists(playlistSearch, pagination.pageIndex, pagination.pageSize); + }, + }); - const recentQuery = useQuery({ - queryKey: ["playlists", pagination, search], + const albumQuery = useQuery({ + queryKey: ["albums", pagination, albumSearch], queryFn: () => { - return getRecentPlaylists(search, pagination.pageIndex, pagination.pageSize); + return searchPlaylistsByAlbums(albumSearch, pagination.pageIndex, pagination.pageSize); }, }); return (
- - + + + + + + diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 7ffd92c..b6e2264 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -40,6 +40,20 @@ export const getCurrentUserDetails = async (): Promise => { ); }; +export const searchPlaylistsByAlbums = async ( + search: string, + offset: number, + limit: number, +): Promise => { + const searchParams = new URLSearchParams(); + searchParams.append("limit", String(limit)); + searchParams.append("offset", String(offset)); + if (search !== "") {searchParams.append("search", search);} + searchParams.toString(); + const endpoint = `music/playlist_album_search?${searchParams.toString()}`; + return jsonRequest(endpoint, RequestMethod.GET); +}; + export const getRecentPlaylists = async ( search: string, offset: number, @@ -49,7 +63,7 @@ export const getRecentPlaylists = async ( searchParams.append("limit", String(limit)); searchParams.append("offset", String(offset)); if (search !== "") {searchParams.append("search", search);} - searchParams.toString(); // "type=all&query=coins" + searchParams.toString(); const endpoint = `music/playlists/recent?${searchParams.toString()}`; return jsonRequest(endpoint, RequestMethod.GET); };