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

Explorar #64

Merged
merged 7 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions Backend API/src/__tests__/test_API/api_test_song.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,11 @@ def patch_song_number_plays(name: str):

response = client.patch(patch_url)
return response


def get_songs_by_genre(genre : str):

get_url = f"/canciones/generos/{genre}"

response = client.get(get_url)
return response
34 changes: 33 additions & 1 deletion Backend API/src/__tests__/test_song.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from fastapi import UploadFile
from model.Genre import Genre
import io
from test_API.api_test_song import create_song,delete_song,get_song,get_songs,patch_song_number_plays
from test_API.api_test_song import create_song,delete_song,get_song,get_songs,patch_song_number_plays,get_songs_by_genre
from test_API.api_test_artist import get_artist,create_artist,delete_artist
import json
import pytest
Expand Down Expand Up @@ -293,6 +293,38 @@ def test_delete_song_uploaded_songs_updated(clear_test_data_db):
res_delete_artist = delete_artist(artista)
assert res_delete_artist.status_code == 202

def test_get_cancion_by_genre(clear_test_data_db):

song_name = "8232392323623823723989"
file_path = "__tests__/assets/song.mp3"
artista = "artista"
genero = "Rock"
foto = "https://foto"
password = "hola"

res_create_artist = create_artist(name=artista,password=password,photo=foto)
assert res_create_artist.status_code == 201

res_create_song = create_song(name=song_name,file_path=file_path,artista=artista,genero=genero,foto=foto)
assert res_create_song.status_code == 201

res_get_song_by_genre = get_songs_by_genre(genre=genero)
assert res_get_song_by_genre.status_code == 200
assert len(res_get_song_by_genre.json())==1

res_delete_song = delete_song(song_name)
assert res_delete_song.status_code == 202

res_delete_artist = delete_artist(artista)
assert res_delete_artist.status_code == 202

def test_get_cancion_by_genre_bad_genre(clear_test_data_db):

genero = "RockInventado"

res_get_song_by_genre = get_songs_by_genre(genre=genero)
assert res_get_song_by_genre.status_code == 422



@pytest.fixture()
Expand Down
5 changes: 4 additions & 1 deletion Backend API/src/model/Genre.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,7 @@ def checkValidGenre(genre : str) -> bool:
if Genre(genre) is None:
return False
return True


def getGenre(genre : str) -> str:
""" Returns genre string """
return str(genre.value)
33 changes: 33 additions & 0 deletions Backend API/src/routers/canciones.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,36 @@ def increase_number_plays_song(nombre: str) -> Response:

song_service.increase_number_plays(nombre)
return Response(None, 204)


@router.get("/generos/{genero}")
def get_cancion_por_genero(genero: Genre) -> Response:
""" Obtiene todas las playlist de un género

Parameters
----------
genero (Genre): Género para filtrar

Returns
-------
Response 200 OK

Raises
-------
Bad Request 400: Parámetros introducidos no són válidos o vacíos
Not Found 404: Género no existe
"""

filtered_songs = dto_service.get_songs_by_genero(genero)

filtered_songs_list = []
[filtered_songs_list.append(song.get_json()) for song in filtered_songs]

songs_dict = {}

songs_dict["songs"] = filtered_songs_list
songs_json = json.dumps(songs_dict)

print(songs_json)

return Response(songs_json, media_type="application/json", status_code=200)
46 changes: 41 additions & 5 deletions Backend API/src/services/dto_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from database.Database import Database
from model.Genre import Genre
from fastapi import HTTPException
from services.utils import checkValidParameterString
from sys import modules

if "pytest" in modules:
Expand All @@ -16,8 +17,6 @@
playlistCollection = Database().connection["playlist"]




def get_song(name: str) -> SongDTO:
""" Returns a song's metadata without his audio file"

Expand All @@ -34,7 +33,7 @@ def get_song(name: str) -> SongDTO:
-------
SongDTO
"""
if name is None or name == "":
if not checkValidParameterString(name):
raise HTTPException(
status_code=400, detail="El nombre de la canción es vacío")

Expand Down Expand Up @@ -67,7 +66,7 @@ def get_songs(song_names: list) -> list:

for name in song_names:

if name is None or name == "":
if not checkValidParameterString(name):
raise HTTPException(
status_code=400, detail="El nombre de la canción es vacío")

Expand Down Expand Up @@ -99,7 +98,7 @@ def get_playlist(name: str) -> PlaylistDTO:
PlaylistDTO
"""

if name is None or name == "":
if not checkValidParameterString(name):
raise HTTPException(
status_code=400, detail="El nombre de la playlist es vacío")

Expand All @@ -110,3 +109,40 @@ def get_playlist(name: str) -> PlaylistDTO:
status_code=404, detail="La playlist con ese nombre no existe")

return PlaylistDTO(name=playlist_data["name"], photo=playlist_data["photo"], description=playlist_data["description"], upload_date=playlist_data["upload_date"], song_names=playlist_data["song_names"], owner=playlist_data["owner"])


def get_songs_by_genero(genre: Genre) -> list:
""" Increase the number of plays of a song

Parameters
----------
name (str): Song's name

Raises
-------
400 : Bad Request
404 : Song Not Found

Returns
-------
List<Song>

"""

if not checkValidParameterString(genre.value):
raise HTTPException(status_code=400, detail="Parámetros no válidos")

if not Genre.checkValidGenre(genre.value):
raise HTTPException(status_code=404, detail="El género no existe")

result_get_song_by_genre = fileSongCollection.find(
{'genre': Genre.getGenre(genre)})

songs_by_genre = []

for song_data in result_get_song_by_genre:

songs_by_genre.append(SongDTO(name=song_data["name"], artist=song_data["artist"], photo=song_data["photo"], duration=song_data["duration"], genre=Genre(
song_data["genre"]).name, number_of_plays=song_data["number_of_plays"]))

return songs_by_genre
14 changes: 4 additions & 10 deletions Backend API/src/services/song_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from fastapi.responses import Response
from model.Genre import Genre
from model.Song import Song
from services.artist_service import check_artists_exists , add_song_artist , delete_song_artist
from services.artist_service import check_artists_exists, add_song_artist, delete_song_artist
import base64
import json
import io
Expand All @@ -26,9 +26,7 @@
fileSongCollection = Database().connection["cancion.files"]




def check_song_exists(name:str) -> bool:
def check_song_exists(name: str) -> bool:
""" Check if the song exists or not

Parameters
Expand All @@ -45,7 +43,6 @@ def check_song_exists(name:str) -> bool:
return True if fileSongCollection.find_one({'name': name}) else False



def get_song(name: str) -> Song:
""" Returns a Song file with attributes and a song encoded in base64 "

Expand Down Expand Up @@ -177,16 +174,14 @@ async def create_song(name: str, artist: str, genre: Genre, photo: str, file) ->
file_id = gridFsSong.put(
file, name=name, artist=artist, duration=duration, genre=str(genre.value), photo=photo, number_of_plays=0)


#! If its not a sound file
except:
duration = 0

file_id = gridFsSong.put(
file, name=name, artist=artist, duration=duration, genre=str(genre.value), photo=photo, number_of_plays=0)

add_song_artist(artist,name)

add_song_artist(artist, name)


def delete_song(name: str) -> None:
Expand All @@ -212,14 +207,13 @@ def delete_song(name: str) -> None:
result = fileSongCollection.find_one({'name': name})

if result and result["_id"]:
delete_song_artist(result["artist"],name)
delete_song_artist(result["artist"], name)
gridFsSong.delete(result["_id"])

else:
raise HTTPException(status_code=404, detail="La canción no existe")



def update_song(name: str, nuevo_nombre: str, photo: str, genre: Genre) -> None:
""" Updates a song with name, url of thumbnail, duration, genre and number of plays, if empty parameter is not being updated "

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ export default function ContextMenuPlaylist({
throw new Error('Unable to delete playlist');
} else if (response.status === 202) {
refreshSidebarData();
navigate(`/home`, { replace: true });
navigate(`/home`);
}

return null;
Expand Down
66 changes: 44 additions & 22 deletions Electron/src/componentes/Explorar/Explorar.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,52 @@
/* import useFetch from 'hooks/useFetch';
import { useEffect } from 'react';
import ContextMenuSong from 'componentes/ContextMenu/Song/ContextMenuSong';
import styles from './explorar.module.css'; */
import { useEffect, useState } from 'react';
import Global from 'global/global';
import styles from './explorar.module.css';
import GenreCard from './GenreCard/GenreCard';

interface PropsExplorar {
changeSongName: (songName: string) => void;
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export default function Explorar({ changeSongName }: PropsExplorar) {
// const {data} = useFetch("http://127.0.0.1:8000/canciones/p3")

// const { data, loading, error } = useFetch("http://127.0.0.1:8000/listas/");
/* interface PropsExplorar {
} */

/* useEffect(() => {
export default function Explorar() {
const [generos, setGeneros] = useState<{}>();

console.log(props.changeSongName)
}, []) */
const getGeneros = async () => {
fetch(encodeURI(`${Global.backendBaseUrl}generos/`))
.then((res) => res.json())
.then(async (res) => {
setGeneros(res);
return null;
})
.catch(() => console.log('Cannot get genres'));
};
useEffect(() => {
getGeneros();
}, []);

return (
<div className="container-fluid d-flex flex-column">
<br />
<br />
<br />
{/* <ContextMenuSong />
*/}{' '}
<div className={`container-fluid d-flex flex-column ${styles.principal}`}>
<div
className={`container-fluid d-flex flex-column ${styles.columnofGeneros}`}
>
<header className="container-fluid d-flex flex-row">
<div className={`container-fluid d-flex ${styles.columnTitle}`}>
<h4>Explorar Todo</h4>
</div>
</header>
<div
className={`container-fluid d-flex flex-row ${styles.cardContainer}`}
>
{generos &&
Object.values(generos).map((genero) => {
return (
<GenreCard
key={genero as string}
name={genero as string}
color={Global.genreColors[genero as string]}
/>
);
})}
</div>
</div>
</div>
);
}
27 changes: 27 additions & 0 deletions Electron/src/componentes/Explorar/GenreCard/GenreCard.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.card {
width: 20vw;
height: 200px;
margin: 10px;
border: none;
display: flex;
align-items: flex-start;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
border-radius: 8px;
}

.genreTitle {
font-size: 2rem;

color: var(--pure-white);
font-weight: 600;
text-wrap: balance;
margin-top: 15px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
word-break: break-all;


}
31 changes: 31 additions & 0 deletions Electron/src/componentes/Explorar/GenreCard/GenreCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useNavigate } from 'react-router-dom';
import styles from './GenreCard.module.css';

interface PropsGenreCard {
name: string;
color: string;
}

export default function GenreCard({ name, color }: PropsGenreCard) {
const navigate = useNavigate();

const backgroundColor = {
backgroundColor: color, // Use the provided background color or default to 'blue'
};

const handleClick = () => {
navigate(`/explorar/genre/${name}`);
};

return (
<button
type="button"
className={`${styles.card} ${backgroundColor}`}
style={backgroundColor}
onClick={handleClick}
>
{' '}
<div className={`${styles.genreTitle}`}>{name}</div>
</button>
);
}
Loading
Loading