diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e4f1ce50..dfbbf278 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ stages: - bundle variables: - VERSION: 0.0.22 + VERSION: 0.0.23 build-go: image: golang:1.16.2 diff --git a/docs/changelog.md b/docs/changelog.md index a4c322a5..0b6cd3b5 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,6 @@ +# 0.0.23 +- Get top tracks pulling correctly :) + # 0.0.22 - Rework navbar + user pages diff --git a/internal/goscrobble/server.go b/internal/goscrobble/server.go index 74393455..8085e23d 100644 --- a/internal/goscrobble/server.go +++ b/internal/goscrobble/server.go @@ -50,8 +50,11 @@ func HandleRequests(port string) { // No Auth v1.HandleFunc("/stats", limitMiddleware(handleStats, lightLimiter)).Methods("GET") v1.HandleFunc("/profile/{username}", limitMiddleware(getProfile, lightLimiter)).Methods("GET") + v1.HandleFunc("/artist/top/{uuid}", limitMiddleware(getArtists, lightLimiter)).Methods("GET") v1.HandleFunc("/artist/{uuid}", limitMiddleware(getArtist, lightLimiter)).Methods("GET") + v1.HandleFunc("/album/top/{uuid}", limitMiddleware(getArtists, lightLimiter)).Methods("GET") v1.HandleFunc("/album/{uuid}", limitMiddleware(getAlbum, lightLimiter)).Methods("GET") + v1.HandleFunc("/track/top/{uuid}", limitMiddleware(getTracks, lightLimiter)).Methods("GET") v1.HandleFunc("/track/{uuid}", limitMiddleware(getTrack, lightLimiter)).Methods("GET") v1.HandleFunc("/register", limitMiddleware(handleRegister, heavyLimiter)).Methods("POST") @@ -533,6 +536,81 @@ func getTrack(w http.ResponseWriter, r *http.Request) { w.Write(json) } +// getArtists - Returns artist data for a user +func getArtists(w http.ResponseWriter, r *http.Request) { + var uuid string + for k, v := range mux.Vars(r) { + if k == "uuid" { + uuid = v + } + } + + if uuid == "" { + throwOkError(w, "Invalid UUID") + return + } + + artist, err := getArtistByUUID(uuid) + if err != nil { + throwOkError(w, err.Error()) + return + } + + json, _ := json.Marshal(&artist) + w.WriteHeader(http.StatusOK) + w.Write(json) +} + +// getAlbums - Returns album data for a user +func getAlbums(w http.ResponseWriter, r *http.Request) { + var uuid string + for k, v := range mux.Vars(r) { + if k == "uuid" { + uuid = v + } + } + + if uuid == "" { + throwOkError(w, "Invalid UUID") + return + } + + album, err := getAlbumByUUID(uuid) + if err != nil { + throwOkError(w, err.Error()) + return + } + + json, _ := json.Marshal(&album) + w.WriteHeader(http.StatusOK) + w.Write(json) +} + +// getTracks - Returns track data for a user +func getTracks(w http.ResponseWriter, r *http.Request) { + var uuid string + for k, v := range mux.Vars(r) { + if k == "uuid" { + uuid = v + } + } + + if uuid == "" { + throwOkError(w, "Invalid UUID") + return + } + + track, err := getTopTracks(uuid) + if err != nil { + throwOkError(w, err.Error()) + return + } + + json, _ := json.Marshal(&track) + w.WriteHeader(http.StatusOK) + w.Write(json) +} + // postSpotifyResponse - Oauth Response from Spotify func postSpotifyReponse(w http.ResponseWriter, r *http.Request) { err := connectSpotifyResponse(r) @@ -587,7 +665,7 @@ func getServerInfo(w http.ResponseWriter, r *http.Request) { } info := ServerInfo{ - Version: "0.0.22", + Version: "0.0.23", RegistrationEnabled: cachedRegistrationEnabled, } diff --git a/internal/goscrobble/track.go b/internal/goscrobble/track.go index a2d9d439..4958fe06 100644 --- a/internal/goscrobble/track.go +++ b/internal/goscrobble/track.go @@ -17,6 +17,16 @@ type Track struct { SpotifyID string `json:"spotify_id"` } +type TopTrack struct { + UUID string `json:"uuid"` + Name string `json:"name"` + Img string `json:"img"` + Plays int `json:"plays"` +} +type TopTracks struct { + Tracks map[int]TopTrack `json:"tracks"` +} + // insertTrack - This will return if it exists or create it based on MBID > Name func insertTrack(name string, legnth int, mbid string, spotifyId string, album string, artists []string, tx *sql.Tx) (Track, error) { track := Track{} @@ -164,3 +174,42 @@ func getTrackByUUID(uuid string) (Track, error) { return track, nil } + +func getTopTracks(userUuid string) (TopTracks, error) { + var topTracks TopTracks + + rows, err := db.Query("SELECT BIN_TO_UUID(`tracks`.`uuid`, true), `tracks`.`name`, IFNULL(`artists`.`img`,''), count(*) "+ + "FROM `scrobbles` "+ + "JOIN `tracks` ON `tracks`.`uuid` = `scrobbles`.`track` "+ + "JOIN track_artist ON track_artist.track = tracks.uuid "+ + "JOIN artists ON track_artist.artist = artists.uuid "+ + "WHERE `user` = UUID_TO_BIN(?, true) "+ + "GROUP BY `scrobbles`.`track` "+ + "ORDER BY count(*) DESC "+ + "LIMIT 14", + userUuid) + if err != nil { + log.Printf("Failed to fetch top tracks: %+v", err) + return topTracks, errors.New("Failed to fetch top tracks") + } + defer rows.Close() + + i := 1 + tempTracks := make(map[int]TopTrack) + + for rows.Next() { + var track TopTrack + err := rows.Scan(&track.UUID, &track.Name, &track.Img, &track.Plays) + if err != nil { + log.Printf("Failed to fetch track: %+v", err) + return topTracks, errors.New("Failed to fetch track") + } + + tempTracks[i] = track + i++ + } + + topTracks.Tracks = tempTracks + + return topTracks, nil +} diff --git a/web/src/Api/index.js b/web/src/Api/index.js index dca3d660..c84184c0 100644 --- a/web/src/Api/index.js +++ b/web/src/Api/index.js @@ -317,3 +317,12 @@ export const getTrack = (uuid) => { return handleErrorResp(error) }); }; + +export const getTopTracks = (uuid) => { + return axios.get(process.env.REACT_APP_API_URL + "track/top/" + uuid).then( + (data) => { + return data.data; + }).catch((error) => { + return handleErrorResp(error) + }); +} diff --git a/web/src/Components/TopTable.js b/web/src/Components/TopTable.js index 5fd7fa4e..a7559ae2 100644 --- a/web/src/Components/TopTable.js +++ b/web/src/Components/TopTable.js @@ -3,41 +3,121 @@ import './TopTable.css' import TopTableBox from './TopTableBox'; const TopTable = (props) => { + if (!props.items || !props.items.tracks) { + return ( + No data. + ) + } + + let tracks = props.items.tracks; + return (
- Top {props.type} + Top {props.type}s
+ + + - - -
- - - - - - - - - + + + + + + + + +
diff --git a/web/src/Components/TopTableBox.css b/web/src/Components/TopTableBox.css index 63792bd4..c1e1a861 100644 --- a/web/src/Components/TopTableBox.css +++ b/web/src/Components/TopTableBox.css @@ -11,8 +11,9 @@ img { .topOverlay { position: absolute; margin: 5px; - padding: 0 5px 0 5px; + padding: 2px 5px 5px 5px; background-color: rgba(0, 0, 0, 0.6); + line-height: 0.6em; } .topText { diff --git a/web/src/Components/TopTableBox.js b/web/src/Components/TopTableBox.js index 8ba2efa4..a29f9059 100644 --- a/web/src/Components/TopTableBox.js +++ b/web/src/Components/TopTableBox.js @@ -9,7 +9,7 @@ const TopTableBox = (props) => { } return ( - +
{ height: `${props.size}px`, float: `left`, }} > -
- #{props.number} {props.title} +
+ #{props.number} {props.title}
diff --git a/web/src/Pages/Profile.js b/web/src/Pages/Profile.js index 65e43838..d9110bff 100644 --- a/web/src/Pages/Profile.js +++ b/web/src/Pages/Profile.js @@ -2,13 +2,14 @@ import React, { useState, useEffect } from 'react'; import '../App.css'; import './Profile.css'; import ScaleLoader from 'react-spinners/ScaleLoader'; -import { getProfile } from '../Api/index' +import { getProfile, getTopTracks } from '../Api/index' import ScrobbleTable from '../Components/ScrobbleTable' import TopTable from '../Components/TopTable' const Profile = (route) => { const [loading, setLoading] = useState(true); const [profile, setProfile] = useState({}); + const [topTracks, setTopTracks] = useState({}) let username = false; if (route && route.match && route.match.params && route.match.params.uuid) { @@ -25,8 +26,17 @@ const Profile = (route) => { getProfile(username) .then(data => { setProfile(data); + + // Fetch top tracks + getTopTracks(data.uuid) + .then(data => { + setTopTracks(data) + } + ) + setLoading(false); }) + }, [username]) if (loading) { @@ -51,7 +61,7 @@ const Profile = (route) => { {profile.username}'s Profile
- +
Last 10 scrobbles...