diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8c76eab8..157e1ebf 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,7 @@ stages: - bundle variables: - VERSION: 0.0.17 + VERSION: 0.0.18 build-go: image: golang:1.16.2 diff --git a/docs/changelog.md b/docs/changelog.md index a6cc9de1..0b64ab50 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,7 @@ +# 0.0.18 +- Add MBID/Spotify Autolinking if track exists +- Add Genre table + .go files + # 0.0.17 - Add check for registration_enabled on /register endpoint - Made songlookup check artist name as well diff --git a/internal/goscrobble/album.go b/internal/goscrobble/album.go index c4fa70e9..038c5d00 100644 --- a/internal/goscrobble/album.go +++ b/internal/goscrobble/album.go @@ -12,8 +12,8 @@ type Album struct { Name string `json:"name"` Desc sql.NullString `json:"desc"` Img sql.NullString `json:"img"` - MusicBrainzID sql.NullString `json:"mbid"` - SpotifyID sql.NullString `json:"spotify_id"` + MusicBrainzID string `json:"mbid"` + SpotifyID string `json:"spotify_id"` } // insertAlbum - This will return if it exists or create it based on MBID > Name @@ -61,6 +61,16 @@ func insertAlbum(name string, mbid string, spotifyId string, artists []string, t return album, errors.New("Unable to fetch album!") } + if album.MusicBrainzID != mbid { + album.MusicBrainzID = mbid + album.updateAlbum("mbid", mbid, tx) + } + + if album.SpotifyID != spotifyId { + album.SpotifyID = spotifyId + album.updateAlbum("spotify_id", spotifyId, tx) + } + return album, nil } @@ -100,8 +110,8 @@ func (album *Album) linkAlbumToArtists(artists []string, tx *sql.Tx) error { return err } -func updateAlbum(uuid string, col string, val string, tx *sql.Tx) error { - _, err := tx.Exec("UPDATE `albums` SET `"+col+"` = ? WHERE `uuid` = UUID_TO_BIN(?,true)", val, uuid) +func (album *Album) updateAlbum(col string, val string, tx *sql.Tx) error { + _, err := tx.Exec("UPDATE `albums` SET `"+col+"` = ? WHERE `uuid` = UUID_TO_BIN(?,true)", val, album.Uuid) return err } diff --git a/internal/goscrobble/artist.go b/internal/goscrobble/artist.go index 39109346..5e4331e5 100644 --- a/internal/goscrobble/artist.go +++ b/internal/goscrobble/artist.go @@ -11,8 +11,8 @@ type Artist struct { Name string `json:"name"` Desc sql.NullString `json:"desc"` Img sql.NullString `json:"img"` - MusicBrainzID sql.NullString `json:"mbid"` - SpotifyID sql.NullString `json:"spotify_id"` + MusicBrainzID string `json:"mbid"` + SpotifyID string `json:"spotify_id"` } // insertArtist - This will return if it exists or create it based on MBID > Name @@ -55,6 +55,16 @@ func insertArtist(name string, mbid string, spotifyId string, tx *sql.Tx) (Artis return artist, errors.New("Unable to fetch artist!") } + if artist.MusicBrainzID != mbid { + artist.MusicBrainzID = mbid + artist.updateArtist("mbid", mbid, tx) + } + + if artist.SpotifyID != spotifyId { + artist.SpotifyID = spotifyId + artist.updateArtist("spotify_id", spotifyId, tx) + } + return artist, nil } @@ -80,8 +90,8 @@ func insertNewArtist(name string, mbid string, spotifyId string, tx *sql.Tx) err return err } -func updateArtist(uuid string, col string, val string, tx *sql.Tx) error { - _, err := tx.Exec("UPDATE `artists` SET `"+col+"` = ? WHERE `uuid` = UUID_TO_BIN(?,true)", val, uuid) +func (artist *Artist) updateArtist(col string, val string, tx *sql.Tx) error { + _, err := tx.Exec("UPDATE `artists` SET `"+col+"` = ? WHERE `uuid` = UUID_TO_BIN(?,true)", val, artist.Uuid) return err } diff --git a/internal/goscrobble/genre.go b/internal/goscrobble/genre.go new file mode 100644 index 00000000..57468ec6 --- /dev/null +++ b/internal/goscrobble/genre.go @@ -0,0 +1,47 @@ +package goscrobble + +import ( + "database/sql" + "log" +) + +type Genre struct { + UUID string `json:"uuid"` + Name string `json:"name"` +} + +func getGenre(uuid string) Genre { + var genre Genre + err := db.QueryRow( + "SELECT BIN_TO_UUID(`uuid`, true), `name` FROM `artists` WHERE `uuid` = UUID_TO_BIN(?,true)", + uuid).Scan(&genre.UUID, &genre.Name) + + if err != nil { + if err != sql.ErrNoRows { + log.Printf("Error fetching artists: %+v", err) + } + } + + return genre +} + +func getGenreByName(name string) Genre { + var genre Genre + err := db.QueryRow( + "SELECT BIN_TO_UUID(`uuid`, true), `name` FROM `artists` WHERE `name` = ?", + name).Scan(&genre.UUID, &genre.Name) + + if err != nil { + if err != sql.ErrNoRows { + log.Printf("Error fetching artists: %+v", err) + } + } + + return genre +} + +func (genre *Genre) updateGenreName(name string, value string) error { + _, err := db.Exec("UPDATE `genres` SET `name` = ? WHERE uuid = UUID_TO_BIN(?, true)", name, genre.UUID) + + return err +} diff --git a/internal/goscrobble/server.go b/internal/goscrobble/server.go index 6fabb963..c0bc1c9a 100644 --- a/internal/goscrobble/server.go +++ b/internal/goscrobble/server.go @@ -252,6 +252,7 @@ func handleIngress(w http.ResponseWriter, r *http.Request, userUuid string) { err = ParseJellyfinInput(userUuid, jfInput, ip, tx) if err != nil { + fmt.Println(err) tx.Rollback() throwOkError(w, err.Error()) return @@ -477,7 +478,7 @@ func fetchServerInfo(w http.ResponseWriter, r *http.Request) { } info := ServerInfo{ - Version: "0.0.17", + Version: "0.0.18", RegistrationEnabled: cachedRegistrationEnabled, } diff --git a/internal/goscrobble/track.go b/internal/goscrobble/track.go index 1f3566b7..1b7ce44b 100644 --- a/internal/goscrobble/track.go +++ b/internal/goscrobble/track.go @@ -3,6 +3,7 @@ package goscrobble import ( "database/sql" "errors" + "fmt" "log" "strings" ) @@ -13,8 +14,8 @@ type Track struct { Length int `json:"length"` Desc sql.NullString `json:"desc"` Img sql.NullString `json:"img"` - MusicBrainzID sql.NullString `json:"mbid"` - SpotifyID sql.NullString `json:"spotify_id"` + MusicBrainzID string `json:"mbid"` + SpotifyID string `json:"spotify_id"` } // insertTrack - This will return if it exists or create it based on MBID > Name @@ -24,14 +25,16 @@ func insertTrack(name string, legnth int, mbid string, spotifyId string, album s // Try locate our track if mbid != "" { track = fetchTrack("mbid", mbid, tx) + fmt.Printf("Fetech mbid: %s", mbid) } else if spotifyId != "" { track = fetchTrack("spotify_id", spotifyId, tx) + fmt.Printf("Fetech spotify: %s", spotifyId) } // If it didn't match above, lookup by name if track.Uuid == "" { // TODO: add artist check here too - track = fetchTrackWithArtists(name, artists, tx) + track = fetchTrackWithArtists(name, artists, album, tx) } // If we can't find it. Lets add it! @@ -49,7 +52,7 @@ func insertTrack(name string, legnth int, mbid string, spotifyId string, album s } if track.Uuid == "" { - track = fetchTrackWithArtists(name, artists, tx) + track = fetchTrackWithArtists(name, artists, album, tx) } err = track.linkTrack(album, artists, tx) @@ -62,13 +65,23 @@ func insertTrack(name string, legnth int, mbid string, spotifyId string, album s return track, errors.New("Unable to fetch track!") } + if track.MusicBrainzID != mbid { + track.MusicBrainzID = mbid + track.updateTrack("mbid", mbid, tx) + } + + if track.SpotifyID != spotifyId { + track.SpotifyID = spotifyId + track.updateTrack("spotify_id", spotifyId, tx) + } + return track, nil } func fetchTrack(col string, val string, tx *sql.Tx) Track { var track Track err := tx.QueryRow( - "SELECT BIN_TO_UUID(`uuid`, true), `name`, `desc`, `img`, `mbid` FROM `tracks` WHERE `"+col+"` = ?", + "SELECT BIN_TO_UUID(`uuid`, true), `name`, `desc`, `img`, `mbid` FROM `tracks` WHERE `"+col+"` = ? LIMIT 1", val).Scan(&track.Uuid, &track.Name, &track.Desc, &track.Img, &track.MusicBrainzID) if err != nil { @@ -80,15 +93,16 @@ func fetchTrack(col string, val string, tx *sql.Tx) Track { return track } -func fetchTrackWithArtists(name string, artists []string, tx *sql.Tx) Track { +func fetchTrackWithArtists(name string, artists []string, album string, tx *sql.Tx) Track { var track Track artistString := strings.Join(artists, "','") - err := tx.QueryRow( "SELECT BIN_TO_UUID(`uuid`, true), `name`, `desc`, `img`, `mbid` FROM `tracks` "+ "LEFT JOIN `track_artist` ON `tracks`.`uuid` = `track_artist`.`track` "+ - "WHERE `name` = ? AND BIN_TO_UUID(`track_artist`.`artist`, true) IN ('`"+artistString+"`')", - name).Scan(&track.Uuid, &track.Name, &track.Desc, &track.Img, &track.MusicBrainzID) + "LEFT JOIN `track_album` ON `tracks`.`uuid` = `track_album`.`track` "+ + "WHERE `name` = ? AND BIN_TO_UUID(`track_artist`.`artist`, true) IN ('"+artistString+"') "+ + "AND BIN_TO_UUID(`track_album`.`album`,true) = ? LIMIT 1", + name, album).Scan(&track.Uuid, &track.Name, &track.Desc, &track.Img, &track.MusicBrainzID) if err != nil { if err != sql.ErrNoRows { @@ -138,8 +152,8 @@ func (track Track) linkTrackToArtists(artists []string, tx *sql.Tx) error { return nil } -func updateTrack(uuid string, col string, val string, tx *sql.Tx) error { - _, err := tx.Exec("UPDATE `tracks` SET `"+col+"` = ? WHERE `uuid` = UUID_TO_BIN(?,true)", val, uuid) +func (track *Track) updateTrack(col string, val string, tx *sql.Tx) error { + _, err := tx.Exec("UPDATE `tracks` SET `"+col+"` = ? WHERE `uuid` = UUID_TO_BIN(?,true)", val, track.Uuid) return err } diff --git a/migrations/11_genres.down.sql b/migrations/11_genres.down.sql new file mode 100644 index 00000000..893e6e8c --- /dev/null +++ b/migrations/11_genres.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS `genres`; \ No newline at end of file diff --git a/migrations/11_genres.up.sql b/migrations/11_genres.up.sql new file mode 100644 index 00000000..96d39e59 --- /dev/null +++ b/migrations/11_genres.up.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS `genres` ( + `uuid` BINARY(16) PRIMARY KEY, + `name` VARCHAR(255) NOT NULL, + KEY `nameLookup` (`name`) +) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci; \ No newline at end of file diff --git a/migrations/4_mbid.up.sql b/migrations/4_mbid.up.sql index e7627442..62a34f60 100644 --- a/migrations/4_mbid.up.sql +++ b/migrations/4_mbid.up.sql @@ -1,7 +1,7 @@ START TRANSACTION; -ALTER TABLE albums ADD COLUMN `mbid` VARCHAR(36) DEFAULT NULL; -ALTER TABLE artists ADD COLUMN `mbid` VARCHAR(36) DEFAULT NULL; -ALTER TABLE tracks ADD COLUMN `mbid` VARCHAR(36) DEFAULT NULL; +ALTER TABLE albums ADD COLUMN `mbid` VARCHAR(36) DEFAULT ''; +ALTER TABLE artists ADD COLUMN `mbid` VARCHAR(36) DEFAULT ''; +ALTER TABLE tracks ADD COLUMN `mbid` VARCHAR(36) DEFAULT ''; COMMIT; diff --git a/migrations/9_spotify.up.sql b/migrations/9_spotify.up.sql index 6c828d98..8b3e89fc 100644 --- a/migrations/9_spotify.up.sql +++ b/migrations/9_spotify.up.sql @@ -1,9 +1,9 @@ START TRANSACTION; -ALTER TABLE `users` ADD COLUMN `spotify_id` VARCHAR(255) DEFAULT ''; -ALTER TABLE `albums` ADD COLUMN `spotify_id` VARCHAR(255) DEFAULT ''; -ALTER TABLE `artists` ADD COLUMN `spotify_id` VARCHAR(255) DEFAULT ''; -ALTER TABLE `tracks` ADD COLUMN `spotify_id` VARCHAR(255) DEFAULT ''; +ALTER TABLE `users` ADD COLUMN `spotify_id` VARCHAR(255) NOT NULL DEFAULT ''; +ALTER TABLE `albums` ADD COLUMN `spotify_id` VARCHAR(255) NOT NULL DEFAULT ''; +ALTER TABLE `artists` ADD COLUMN `spotify_id` VARCHAR(255) NOT NULL DEFAULT ''; +ALTER TABLE `tracks` ADD COLUMN `spotify_id` VARCHAR(255) NOT NULL DEFAULT ''; ALTER TABLE `users` ADD INDEX `spotifyLookup` (`spotify_id`); ALTER TABLE `albums` ADD INDEX `spotifyLookup` (`spotify_id`); diff --git a/web/src/App.css b/web/src/App.css index 5616be07..e4ac4b25 100644 --- a/web/src/App.css +++ b/web/src/App.css @@ -44,6 +44,11 @@ html, body { color: white; } +.pageBody { + padding: 20px 5px 5px 5px; + font-size: 16pt; +} + .App-link { color: #61dafb; } diff --git a/web/src/App.js b/web/src/App.js index 4cf1fb2c..bd10e51a 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -3,6 +3,9 @@ import Home from './Pages/Home'; import About from './Pages/About'; import Dashboard from './Pages/Dashboard'; import Profile from './Pages/Profile'; +import Artist from './Pages/Artist'; +import Album from './Pages/Album'; +import Track from './Pages/Track'; import User from './Pages/User'; import Admin from './Pages/Admin'; import Login from './Pages/Login'; @@ -33,6 +36,9 @@ const App = () => { + + + diff --git a/web/src/Pages/About.css b/web/src/Pages/About.css index 7c0a2a6e..e69de29b 100644 --- a/web/src/Pages/About.css +++ b/web/src/Pages/About.css @@ -1,4 +0,0 @@ -.aboutBody { - padding: 20px 0px 0px 0px; - font-size: 16pt; -} \ No newline at end of file diff --git a/web/src/Pages/About.js b/web/src/Pages/About.js index cfd74942..ef2bcc2a 100644 --- a/web/src/Pages/About.js +++ b/web/src/Pages/About.js @@ -12,7 +12,7 @@ const About = () => { Used to track your listening history and build a profile to discover new music.

{

Admin Panel

-
+
postConfigs(values, toggle)} diff --git a/web/src/Pages/Album.css b/web/src/Pages/Album.css new file mode 100644 index 00000000..e69de29b diff --git a/web/src/Pages/Album.js b/web/src/Pages/Album.js new file mode 100644 index 00000000..5a937210 --- /dev/null +++ b/web/src/Pages/Album.js @@ -0,0 +1,59 @@ +import React, { useState, useEffect } from 'react'; +import '../App.css'; +import './Album.css'; +import ScaleLoader from 'react-spinners/ScaleLoader'; +import ScrobbleTable from '../Components/ScrobbleTable' + +const Album = (route) => { + const [loading, setLoading] = useState(true); + const [profile, setProfile] = useState({}); + + let album = false; + if (route && route.match && route.match.params && route.match.params.uuid) { + album = route.match.params.uuid; + } else { + album = false; + } + + useEffect(() => { + if (!album) { + return false; + } + + // getProfile(username) + // .then(data => { + // setProfile(data); + // console.log(data) + // setLoading(false); + // }) + }, [album]) + + if (loading) { + return ( +
+ +
+ ) + } + + if (!album || !album) { + return ( +
+ Unable to fetch user +
+ ) + } + + return ( +
+

+ {album} +

+
+ Album +
+
+ ); +} + +export default Album; \ No newline at end of file diff --git a/web/src/Pages/Artist.css b/web/src/Pages/Artist.css new file mode 100644 index 00000000..e69de29b diff --git a/web/src/Pages/Artist.js b/web/src/Pages/Artist.js new file mode 100644 index 00000000..f8cd60a1 --- /dev/null +++ b/web/src/Pages/Artist.js @@ -0,0 +1,59 @@ +import React, { useState, useEffect } from 'react'; +import '../App.css'; +import './Artist.css'; +import ScaleLoader from 'react-spinners/ScaleLoader'; +import ScrobbleTable from '../Components/ScrobbleTable' + +const Artist = (route) => { + const [loading, setLoading] = useState(true); + const [profile, setProfile] = useState({}); + + let artist = false; + if (route && route.match && route.match.params && route.match.params.uuid) { + artist = route.match.params.uuid; + } else { + artist = false; + } + + useEffect(() => { + if (!artist) { + return false; + } + + // getProfile(username) + // .then(data => { + // setProfile(data); + // console.log(data) + // setLoading(false); + // }) + }, [artist]) + + if (loading) { + return ( +
+ +
+ ) + } + + if (!artist || !artist) { + return ( +
+ Unable to fetch user +
+ ) + } + + return ( +
+

+ {artist} +

+
+ Artist +
+
+ ); +} + +export default Artist; \ No newline at end of file diff --git a/web/src/Pages/Dashboard.css b/web/src/Pages/Dashboard.css index 5c221551..e69de29b 100644 --- a/web/src/Pages/Dashboard.css +++ b/web/src/Pages/Dashboard.css @@ -1,4 +0,0 @@ -.dashboardBody { - padding: 20px 5px 5px 5px; - font-size: 16pt; -} \ No newline at end of file diff --git a/web/src/Pages/Dashboard.js b/web/src/Pages/Dashboard.js index 069ab202..a9be5628 100644 --- a/web/src/Pages/Dashboard.js +++ b/web/src/Pages/Dashboard.js @@ -42,7 +42,7 @@ const Dashboard = () => {

{user.username}'s Dashboard!

-
+
{loading ? : diff --git a/web/src/Pages/Login.css b/web/src/Pages/Login.css index 223f2c12..185f453f 100644 --- a/web/src/Pages/Login.css +++ b/web/src/Pages/Login.css @@ -1,9 +1,3 @@ -.loginBody { - padding: 20px 5px 5px 5px; - font-size: 16pt; - width: 300px; -} - .loginFields { width: 100%; } diff --git a/web/src/Pages/Login.js b/web/src/Pages/Login.js index 61e271c7..524aed3d 100644 --- a/web/src/Pages/Login.js +++ b/web/src/Pages/Login.js @@ -25,7 +25,7 @@ const Login = () => {

Login

-
+
Login(values)} diff --git a/web/src/Pages/Profile.css b/web/src/Pages/Profile.css index 8a1acb4f..e69de29b 100644 --- a/web/src/Pages/Profile.css +++ b/web/src/Pages/Profile.css @@ -1,4 +0,0 @@ -.profileBody { - padding: 20px 5px 5px 5px; - font-size: 16pt; -} \ No newline at end of file diff --git a/web/src/Pages/Profile.js b/web/src/Pages/Profile.js index ca95aa42..c2af7400 100644 --- a/web/src/Pages/Profile.js +++ b/web/src/Pages/Profile.js @@ -50,7 +50,7 @@ const Profile = (route) => {

{profile.username}'s Profile

-
+
Last 10 scrobbles...
diff --git a/web/src/Pages/Register.css b/web/src/Pages/Register.css index b4aedf6c..6ac97981 100644 --- a/web/src/Pages/Register.css +++ b/web/src/Pages/Register.css @@ -1,9 +1,3 @@ -.registerBody { - padding: 20px 5px 5px 5px; - font-size: 16pt; - width: 300px; -} - .registerFields { width: 100%; } diff --git a/web/src/Pages/Register.js b/web/src/Pages/Register.js index 0e1bda94..30b8da64 100644 --- a/web/src/Pages/Register.js +++ b/web/src/Pages/Register.js @@ -47,7 +47,7 @@ const Register = () => {

Register

-
+
Register(values)} diff --git a/web/src/Pages/Reset.css b/web/src/Pages/Reset.css index 64b62e2d..17549a52 100644 --- a/web/src/Pages/Reset.css +++ b/web/src/Pages/Reset.css @@ -1,9 +1,3 @@ -.resetBody { - padding: 20px 5px 5px 5px; - font-size: 16pt; - width: 300px; -} - .resetFields { width: 100%; } diff --git a/web/src/Pages/Reset.js b/web/src/Pages/Reset.js index 5590334e..53a7ab72 100644 --- a/web/src/Pages/Reset.js +++ b/web/src/Pages/Reset.js @@ -64,7 +64,7 @@ const Reset = (route) => {

Reset Password

-
+
sendReset(values)} diff --git a/web/src/Pages/Settings.js b/web/src/Pages/Settings.js index 791009e2..81d5e4f6 100644 --- a/web/src/Pages/Settings.js +++ b/web/src/Pages/Settings.js @@ -8,7 +8,7 @@ const Settings = () => {

Settings

-
+

All the settings

diff --git a/web/src/Pages/Track.css b/web/src/Pages/Track.css new file mode 100644 index 00000000..e69de29b diff --git a/web/src/Pages/Track.js b/web/src/Pages/Track.js new file mode 100644 index 00000000..c992cb4e --- /dev/null +++ b/web/src/Pages/Track.js @@ -0,0 +1,59 @@ +import React, { useState, useEffect } from 'react'; +import '../App.css'; +import './Track.css'; +import ScaleLoader from 'react-spinners/ScaleLoader'; +import ScrobbleTable from '../Components/ScrobbleTable' + +const Track = (route) => { + const [loading, setLoading] = useState(true); + const [profile, setProfile] = useState({}); + + let artist = false; + if (route && route.match && route.match.params && route.match.params.uuid) { + artist = route.match.params.uuid; + } else { + artist = false; + } + + useEffect(() => { + if (!artist) { + return false; + } + + // getProfile(username) + // .then(data => { + // setProfile(data); + // console.log(data) + // setLoading(false); + // }) + }, [artist]) + + if (loading) { + return ( +
+ +
+ ) + } + + if (!artist || !artist) { + return ( +
+ Unable to fetch user +
+ ) + } + + return ( +
+

+ {artist} +

+
+ Artist +
+
+ ); +} + +export default Track; \ No newline at end of file diff --git a/web/src/Pages/User.css b/web/src/Pages/User.css index 4c4e8de7..26b27658 100644 --- a/web/src/Pages/User.css +++ b/web/src/Pages/User.css @@ -1,8 +1,3 @@ -.userBody { - padding: 20px 5px 5px 5px; - font-size: 16pt; -} - .userDropdown { color: #282C34; font-size: 12pt; diff --git a/web/src/Pages/User.js b/web/src/Pages/User.js index c6091f63..8a35dda3 100644 --- a/web/src/Pages/User.js +++ b/web/src/Pages/User.js @@ -95,7 +95,7 @@ const User = () => {

Welcome {userdata.username}

-

+

Timezone