diff --git a/README.md b/README.md index c823402b..46129b79 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ This assumes you have goscrobble-api and goscrobble-web cloned in the same folde Access API @ http://127.0.0.1:42069/api/v1 Access frontend @ http://127.0.0.1:3000 +pgAdmin @ http://127.0.0.1:5050 (admin@admin.com / root) ## Prod deployment cp .env.production .env # Fill in the blanks diff --git a/docker-compose.yml b/docker-compose.yml index 82116b40..9a60fcc3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -26,6 +26,8 @@ services: image: postgres:14.1 volumes: - database-data:/var/lib/postgresql/data/ + ports: + - 5432:5432 restart: always environment: - POSTGRES_USER=goscrobble @@ -37,6 +39,16 @@ services: ports: - "127.0.0.1:6379:6379" + pgadmin: + container_name: pgadmin4_container + image: dpage/pgadmin4 + restart: always + environment: + PGADMIN_DEFAULT_EMAIL: admin@admin.com + PGADMIN_DEFAULT_PASSWORD: root + ports: + - "5050:80" + volumes: database-data: data: \ No newline at end of file diff --git a/docs/migrate.php b/docs/migrate.php new file mode 100644 index 00000000..04f88a96 --- /dev/null +++ b/docs/migrate.php @@ -0,0 +1,95 @@ +connect_errno) { + die("Failed to connect to MySQL"); +} + +global $postgres; +$postgres = new PDO("pgsql:host=127.0.0.1;port=5432;dbname=goscrobble;user=goscrobble;password=supersecretdatabasepassword1"); + +function getArray($query): array +{ + global $mysqli; + if (!$result = $mysqli->query($query)) { + die($mysqli->error); + } + + while ($row = $result->fetch_assoc()) { + $data[] = $row; + } + return $data; +} + +echo PHP_EOL . "Skipping schema_migrations (Already exists)"; + +echo PHP_EOL . "Migrating config"; +// $config = getArray("SELECT * FROM config"); +// $update = $postgres->prepare("UPDATE config SET value = ? WHERE key = ?"); +// foreach ($config as $row) { +// $update->execute([ +// $row['value'], +// $row['key'], +// ]); +// } + +echo PHP_EOL . "Migrating users"; +$users = getArray("SELECT + BIN_TO_UUID(uuid, true) as uuid, + created_at, + inet_ntoa(conv(created_ip, 16, 10)) as created_ip, + modified_at, + inet_ntoa(modified_ip) as modified_ip, + username, + password, + email, + verified, + active, + admin, + `mod`, + token, + private, + timezone + FROM users;"); + +$update = $postgres->prepare("INSERT INTO users (uuid, created_at, created_ip, modified_at, modified_ip, username, password, email, verified, active, admin, mod, token, private, timezone) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); +foreach ($users as $row) { + echo PHP_EOL . $row['username']; + $update->execute([ + $row['uuid'], + $row['created_at'], + $row['created_ip'], + $row['modified_at'], + $row['modified_ip'], + $row['username'], + $row['password'], + $row['email'], + $row['verified'], + $row['active'], + $row['admin'], + $row['mod'], + $row['token'], + $row['private'], + $row['timezone'], + ]); +} + +// echo PHP_EOL . "Migrating albums"; +// echo PHP_EOL . "Migrating artists"; +// echo PHP_EOL . "Migrating tracks"; +// echo PHP_EOL . "Migrating genres"; +// echo PHP_EOL . "Migrating links"; +// echo PHP_EOL . "Migrating oauth_tokens"; +// echo PHP_EOL . "Migrating refresh_tokens"; +// echo PHP_EOL . "Migrating resettoken"; +// echo PHP_EOL . "Migrating album_artist"; +// echo PHP_EOL . "Migrating track_album"; +// echo PHP_EOL . "Migrating track_artist"; +// echo PHP_EOL . "Migrating scrobbles"; diff --git a/internal/goscrobble/album.go b/internal/goscrobble/album.go index cf1025f4..5c493003 100644 --- a/internal/goscrobble/album.go +++ b/internal/goscrobble/album.go @@ -73,7 +73,7 @@ func insertAlbum(name string, mbid string, spotifyId string, artists []string, i func getAlbumByCol(col string, val string, tx *sql.Tx) Album { var album Album err := tx.QueryRow( - "SELECT uuid, name, IFNULL(desc, ''), IFNULL(img,''), mbid, spotify_id FROM albums WHERE "+col+" = $1", + `SELECT uuid, name, COALESCE(desc, ''), COALESCE(img,''), mbid, spotify_id FROM albums WHERE "`+col+`" = $1`, val).Scan(&album.UUID, &album.Name, &album.Desc, &album.Img, &album.MusicBrainzID, &album.SpotifyID) if err != nil { @@ -92,8 +92,8 @@ func insertNewAlbum(album *Album, name string, mbid string, spotifyId string, im album.SpotifyID = spotifyId album.Img = img - _, err := tx.Exec("INSERT INTO albums (uuid, name, mbid, spotify_id, img) "+ - "VALUES ($1,$2,$3,$4,$5)", album.UUID, album.Name, album.MusicBrainzID, album.SpotifyID, album.Img) + _, err := tx.Exec(`INSERT INTO albums (uuid, name, mbid, spotify_id, img) `+ + `VALUES ($1,$2,$3,$4,$5)`, album.UUID, album.Name, album.MusicBrainzID, album.SpotifyID, album.Img) return err } @@ -101,8 +101,8 @@ func insertNewAlbum(album *Album, name string, mbid string, spotifyId string, im func (album *Album) linkAlbumToArtists(artists []string, tx *sql.Tx) error { var err error for _, artist := range artists { - _, err = tx.Exec("INSERT INTO album_artist (album, artist) "+ - "VALUES ($1,$2)", album.UUID, artist) + _, err = tx.Exec(`INSERT INTO album_artist (album, artist) `+ + `VALUES ($1,$2)`, album.UUID, artist) if err != nil { return err } @@ -112,14 +112,14 @@ func (album *Album) linkAlbumToArtists(artists []string, tx *sql.Tx) error { } func (album *Album) updateAlbum(col string, val string, tx *sql.Tx) error { - _, err := tx.Exec("UPDATE albums SET "+col+" = $1 WHERE uuid = $2", val, album.UUID) + _, err := tx.Exec(`UPDATE albums SET "`+col+`" = $1 WHERE uuid = $2`, val, album.UUID) return err } func getAlbumByUUID(uuid string) (Album, error) { var album Album - err := db.QueryRow("SELECT uuid, name, IFNULL(desc,''), IFNULL(img,''), mbid, spotify_id FROM albums WHERE uuid = $1", + err := db.QueryRow(`SELECT uuid, name, COALESCE(desc,''), COALESCE(img,''), mbid, spotify_id FROM albums WHERE uuid = $1`, uuid).Scan(&album.UUID, &album.Name, &album.Desc, &album.Img, &album.MusicBrainzID, &album.SpotifyID) if err != nil { diff --git a/internal/goscrobble/artist.go b/internal/goscrobble/artist.go index 4663e0ce..ae47e80b 100644 --- a/internal/goscrobble/artist.go +++ b/internal/goscrobble/artist.go @@ -83,7 +83,7 @@ func insertArtist(name string, mbid string, spotifyId string, img string, tx *sq func getArtistByCol(col string, val string, tx *sql.Tx) Artist { var artist Artist err := tx.QueryRow( - "SELECT uuid, name, IFNULL(desc,''), IFNULL(img,''), mbid, spotify_id FROM artists WHERE "+col+" = $1", + `SELECT uuid, name, COALESCE(desc,''), COALESCE(img,''), mbid, spotify_id FROM artists WHERE "`+col+`" = $1`, val).Scan(&artist.UUID, &artist.Name, &artist.Desc, &artist.Img, &artist.MusicBrainzID, &artist.SpotifyID) if err != nil { @@ -102,21 +102,21 @@ func insertNewArtist(artist *Artist, name string, mbid string, spotifyId string, artist.SpotifyID = spotifyId artist.Img = img - _, err := tx.Exec("INSERT INTO artists (uuid, name, mbid, spotify_id, img) "+ - "VALUES ($1,$2,$3,$4,$5)", artist.UUID, artist.Name, artist.MusicBrainzID, artist.SpotifyID, artist.Img) + _, err := tx.Exec(`INSERT INTO artists (uuid, name, mbid, spotify_id, img) `+ + `VALUES ($1,$2,$3,$4,$5)`, artist.UUID, artist.Name, artist.MusicBrainzID, artist.SpotifyID, artist.Img) return err } func (artist *Artist) updateArtist(col string, val string, tx *sql.Tx) error { - _, err := tx.Exec("UPDATE artists SET "+col+" = $1 WHERE uuid = $2", val, artist.UUID) + _, err := tx.Exec(`UPDATE artists SET "`+col+`" = $1 WHERE uuid = $2`, val, artist.UUID) return err } func getArtistByUUID(uuid string) (Artist, error) { var artist Artist - err := db.QueryRow("SELECT uuid, name, IFNULL(desc, ''), IFNULL(img,''), mbid, spotify_id FROM artists WHERE uuid = $1", + err := db.QueryRow(`SELECT uuid, name, COALESCE(desc, ''), COALESCE(img,''), mbid, spotify_id FROM artists WHERE uuid = $1`, uuid).Scan(&artist.UUID, &artist.Name, &artist.Desc, &artist.Img, &artist.MusicBrainzID, &artist.SpotifyID) if err == sql.ErrNoRows { @@ -129,15 +129,16 @@ func getArtistByUUID(uuid string) (Artist, error) { func getTopArtists(userUuid string) (TopArtists, error) { var topArtist TopArtists - rows, err := db.Query("SELECT artists.uuid, artists.name, IFNULL(artists.uuid,''), 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 scrobbles.user = $1 "+ - "GROUP BY artists.uuid "+ - "ORDER BY count(*) DESC "+ - "LIMIT 14;", + log.Println(userUuid) + rows, err := db.Query(`SELECT artists.uuid, artists.name, COALESCE(artists.uuid,''), 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 scrobbles."user" = $1 `+ + `GROUP BY artists.uuid `+ + `ORDER BY count(*) DESC `+ + `LIMIT 14;`, userUuid) if err != nil { log.Printf("Failed to fetch top artist: %+v", err) diff --git a/internal/goscrobble/config.go b/internal/goscrobble/config.go index 3b48f073..d971e1bf 100644 --- a/internal/goscrobble/config.go +++ b/internal/goscrobble/config.go @@ -20,7 +20,7 @@ func getAllConfigs() (Config, error) { config := Config{} configs := make(map[string]string) - rows, err := db.Query("SELECT key, value FROM config") + rows, err := db.Query(`SELECT key, value FROM config`) if err != nil { log.Printf("Failed to fetch config: %+v", err) return config, errors.New("Failed to fetch configs") @@ -53,7 +53,7 @@ func getAllConfigs() (Config, error) { } func updateConfigValue(key string, value string) error { - _, err := db.Exec("UPDATE config SET value = $1 WHERE key = $2", value, key) + _, err := db.Exec(`UPDATE config SET value = $1 WHERE key = $2`, value, key) if err != nil { fmt.Printf("Failed to update config: %+v", err) return errors.New("Failed to update config value.") @@ -65,8 +65,7 @@ func updateConfigValue(key string, value string) error { func getConfigValue(key string) (string, error) { var value string - err := db.QueryRow("SELECT value FROM config "+ - "WHERE key = $1", + err := db.QueryRow(`SELECT value FROM config WHERE key = $1`, key).Scan(&value) if err == sql.ErrNoRows { diff --git a/internal/goscrobble/genre.go b/internal/goscrobble/genre.go index cc4b8ef7..9a9cf717 100644 --- a/internal/goscrobble/genre.go +++ b/internal/goscrobble/genre.go @@ -13,7 +13,7 @@ type Genre struct { func getGenreByUUID(uuid string) Genre { var genre Genre err := db.QueryRow( - "SELECT uuid, name FROM artists WHERE uuid = $1", + `SELECT uuid, name FROM artists WHERE uuid = $1`, uuid).Scan(&genre.UUID, &genre.Name) if err != nil { @@ -28,7 +28,7 @@ func getGenreByUUID(uuid string) Genre { func getGenreByName(name string) Genre { var genre Genre err := db.QueryRow( - "SELECT uuid, name FROM artists WHERE name = $1", + `SELECT uuid, name FROM artists WHERE name = $1`, name).Scan(&genre.UUID, &genre.Name) if err != nil { @@ -41,7 +41,7 @@ func getGenreByName(name string) Genre { } func (genre *Genre) updateGenreName(name string, value string) error { - _, err := db.Exec("UPDATE genres SET name = $1 WHERE uuid = $2", name, genre.UUID) + _, err := db.Exec(`UPDATE genres SET name = $1 WHERE uuid = $2`, name, genre.UUID) return err } diff --git a/internal/goscrobble/ingress_spotify.go b/internal/goscrobble/ingress_spotify.go index 20ce0b9e..418ee5fd 100644 --- a/internal/goscrobble/ingress_spotify.go +++ b/internal/goscrobble/ingress_spotify.go @@ -17,7 +17,7 @@ import ( // updateSpotifyData - Pull data for all users func updateSpotifyData() { // Lets ignore if not configured - val, _ := getConfigValue("SPOTIFY_APP_SECRET") + val, _ := getConfigValue("SPOTIFY_API_SECRET") if val == "" { return } @@ -35,12 +35,12 @@ func updateSpotifyData() { } func getSpotifyAuthHandler() spotify.Authenticator { - appId, _ := getConfigValue("SPOTIFY_APP_ID") - appSecret, _ := getConfigValue("SPOTIFY_APP_SECRET") + appId, _ := getConfigValue("SPOTIFY_API_ID") + appSecret, _ := getConfigValue("SPOTIFY_API_SECRET") redirectUrl := os.Getenv("GOSCROBBLE_DOMAIN") + "/api/v1/link/spotify" if redirectUrl == "http://localhost:3000/api/v1/link/spotify" { - // Handle backend on a different port + // Handle backend on a different port if running in dev-env redirectUrl = "http://localhost:42069/api/v1/link/spotify" } @@ -217,7 +217,7 @@ func ParseSpotifyInput(userUUID string, data spotify.RecentlyPlayedItem, client // updateImageDataFromSpotify update artist/album images from spotify ;D func (user *User) updateImageDataFromSpotify() error { // Check that data is set before we attempt to pull - val, _ := getConfigValue("SPOTIFY_APP_SECRET") + val, _ := getConfigValue("SPOTIFY_API_SECRET") if val == "" { return nil } @@ -238,7 +238,7 @@ func (user *User) updateImageDataFromSpotify() error { client := auth.NewClient(token) client.AutoRetry = true - rows, err := db.Query("SELECT uuid, name FROM artists WHERE IFNULL(img,'') NOT IN ('pending', 'complete') LIMIT 100") + rows, err := db.Query(`SELECT uuid, name FROM artists WHERE COALESCE(img,'') NOT IN ('pending', 'complete') LIMIT 100`) if err != nil { log.Printf("Failed to fetch config: %+v", err) return errors.New("Failed to fetch artists") @@ -282,7 +282,7 @@ func (user *User) updateImageDataFromSpotify() error { } tx.Commit() - rows, err = db.Query("SELECT uuid, name FROM albums WHERE IFNULL(img,'') NOT IN ('pending', 'complete') LIMIT 100") + rows, err = db.Query("SELECT uuid, name FROM albums WHERE COALESCE(img,'') NOT IN ('pending', 'complete') LIMIT 100") if err != nil { log.Printf("Failed to fetch config: %+v", err) return errors.New("Failed to fetch artists") diff --git a/internal/goscrobble/oauth_tokens.go b/internal/goscrobble/oauth_tokens.go index 70903e0b..b7b7c929 100644 --- a/internal/goscrobble/oauth_tokens.go +++ b/internal/goscrobble/oauth_tokens.go @@ -20,8 +20,8 @@ type OauthToken struct { func getOauthToken(userUuid string, service string) (OauthToken, error) { var oauth OauthToken - err := db.QueryRow("SELECT user, service, access_token, refresh_token, expiry, username, last_synced, url FROM oauth_tokens "+ - "WHERE user = $1 AND service = $2", + err := db.QueryRow(`SELECT "user", service, access_token, refresh_token, expiry, username, last_synced, url FROM oauth_tokens `+ + `WHERE "user" = $1 AND service = $2`, userUuid, service).Scan(&oauth.UserUUID, &oauth.Service, &oauth.AccessToken, &oauth.RefreshToken, &oauth.Expiry, &oauth.Username, &oauth.LastSynced, &oauth.URL) if err == sql.ErrNoRows { @@ -32,14 +32,14 @@ func getOauthToken(userUuid string, service string) (OauthToken, error) { } func insertOauthToken(userUuid string, service string, token string, refresh string, expiry time.Time, username string, lastSynced time.Time, url string) error { - _, err := db.Exec("REPLACE INTO oauth_tokens (user, service, access_token, refresh_token, expiry, username, last_synced, url) "+ - "VALUES ($1,$2,$3,$4,$5,$6,$7,$8)", userUuid, service, token, refresh, expiry, username, lastSynced, url) + _, err := db.Exec(`REPLACE INTO oauth_tokens ("user", service, access_token, refresh_token, expiry, username, last_synced, url) `+ + `VALUES ($1,$2,$3,$4,$5,$6,$7,$8)`, userUuid, service, token, refresh, expiry, username, lastSynced, url) return err } func removeOauthToken(userUuid string, service string) error { - _, err := db.Exec("DELETE FROM oauth_tokens WHERE user = $1 AND service = $2", userUuid, service) + _, err := db.Exec(`DELETE FROM oauth_tokens WHERE "user" = $1 AND service = $2`, userUuid, service) return err } diff --git a/internal/goscrobble/scrobble.go b/internal/goscrobble/scrobble.go index 1f4bf85d..daf15360 100644 --- a/internal/goscrobble/scrobble.go +++ b/internal/goscrobble/scrobble.go @@ -58,8 +58,7 @@ func getScrobblesForUser(userUuid string, limit int, page int) (ScrobbleResponse var count int // Yeah this isn't great. But for now.. it works! Cache later - total, err := getDbCount( - "SELECT COUNT(*) FROM scrobbles WHERE user = $1", userUuid) + total, err := getDbCount(`SELECT COUNT(*) FROM scrobbles WHERE "user" = $1`, userUuid) if err != nil { log.Printf("Failed to fetch scrobble count: %+v", err) @@ -67,16 +66,16 @@ func getScrobblesForUser(userUuid string, limit int, page int) (ScrobbleResponse } rows, err := db.Query( - "SELECT scrobbles.uuid, scrobbles.created_at, artists.uuid, artists.name, albums.name, tracks.uuid, tracks.name, scrobbles.source FROM scrobbles "+ - "JOIN tracks ON scrobbles.track = tracks.uuid "+ - "JOIN track_artist ON track_artist.track = tracks.uuid "+ - "JOIN track_album ON track_album.track = tracks.uuid "+ - "JOIN artists ON track_artist.artist = artists.uuid "+ - "JOIN albums ON track_album.album = albums.uuid "+ - "JOIN users ON scrobbles.user = users.uuid "+ - "WHERE user = $1 "+ - "GROUP BY scrobbles.uuid, albums.uuid "+ - "ORDER BY scrobbles.created_at DESC LIMIT $2", + `SELECT scrobbles.uuid, scrobbles.created_at, artists.uuid, artists.name, albums.name, tracks.uuid, tracks.name, scrobbles.source FROM scrobbles `+ + `JOIN tracks ON scrobbles.track = tracks.uuid `+ + `JOIN track_artist ON track_artist.track = tracks.uuid `+ + `JOIN track_album ON track_album.track = tracks.uuid `+ + `JOIN artists ON track_artist.artist = artists.uuid `+ + `JOIN albums ON track_album.album = albums.uuid `+ + `JOIN users ON scrobbles.user = users.uuid `+ + `WHERE "user" = $1 `+ + `GROUP BY scrobbles.uuid, albums.uuid `+ + `ORDER BY scrobbles.created_at DESC LIMIT $2`, userUuid, limit) if err != nil { diff --git a/internal/goscrobble/server.go b/internal/goscrobble/server.go index e24d39d2..63c928b7 100644 --- a/internal/goscrobble/server.go +++ b/internal/goscrobble/server.go @@ -602,6 +602,8 @@ func getArtists(w http.ResponseWriter, r *http.Request) { return } + log.Println(uuid) + track, err := getTopArtists(uuid) if err != nil { throwOkError(w, err.Error()) @@ -702,7 +704,7 @@ func postSpotifyReponse(w http.ResponseWriter, r *http.Request) { // getSpotifyClientID - Returns public spotify APP ID func getSpotifyClientID(w http.ResponseWriter, r *http.Request, claims CustomClaims, v string) { - key, err := getConfigValue("SPOTIFY_APP_ID") + key, err := getConfigValue("SPOTIFY_API_ID") if err != nil { throwOkError(w, "Failed to get Spotify ID") return diff --git a/internal/goscrobble/stats.go b/internal/goscrobble/stats.go index b28b4dd1..c1c2e300 100644 --- a/internal/goscrobble/stats.go +++ b/internal/goscrobble/stats.go @@ -53,25 +53,25 @@ func getAllStats() (StatsRequest, error) { statsReq := StatsRequest{} var err error - statsReq.Users, err = getDbCount("SELECT COUNT(*) FROM users WHERE active = true") + statsReq.Users, err = getDbCount(`SELECT COUNT(*) FROM users WHERE active = true`) if err != nil { log.Printf("Failed to fetch user count: %+v", err) return statsReq, errors.New("Failed to fetch stats") } - statsReq.Scrobbles, err = getDbCount("SELECT COUNT(*) FROM scrobbles") + statsReq.Scrobbles, err = getDbCount(`SELECT COUNT(*) FROM scrobbles`) if err != nil { log.Printf("Failed to fetch scrobble count: %+v", err) return statsReq, errors.New("Failed to fetch stats") } - statsReq.Tracks, err = getDbCount("SELECT COUNT(*) FROM tracks") + statsReq.Tracks, err = getDbCount(`SELECT COUNT(*) FROM tracks`) if err != nil { log.Printf("Failed to fetch track count: %+v", err) return statsReq, errors.New("Failed to fetch stats") } - statsReq.Artists, err = getDbCount("SELECT COUNT(*) FROM artists") + statsReq.Artists, err = getDbCount(`SELECT COUNT(*) FROM artists`) if err != nil { log.Printf("Failed to fetch artist count: %+v", err) return statsReq, errors.New("Failed to fetch stats") diff --git a/internal/goscrobble/tokens.go b/internal/goscrobble/tokens.go index 98a37e0c..a045c230 100644 --- a/internal/goscrobble/tokens.go +++ b/internal/goscrobble/tokens.go @@ -29,7 +29,7 @@ func getUserUuidForToken(token string) (string, error) { var uuid string cachedKey := getRedisVal("user_token:" + token) if cachedKey == "" { - err := db.QueryRow("SELECT uuid FROM users WHERE token = $1 AND active = true", token).Scan(&uuid) + err := db.QueryRow(`SELECT uuid FROM users WHERE token = $1 AND active = true`, token).Scan(&uuid) if err != nil { return "", errors.New("Invalid Token") } @@ -50,14 +50,14 @@ func insertRefreshToken(userUuid string, token string) error { } func deleteRefreshToken(token string) error { - _, err := db.Exec("DELETE FROM refresh_tokens WHERE token = $1", token) + _, err := db.Exec(`DELETE FROM refresh_tokens WHERE token = $1`, token) return err } func isValidRefreshToken(refreshTokenStr string) (User, error) { var refresh RefreshToken - err := db.QueryRow("SELECT uuid, user, token, expiry FROM refresh_tokens WHERE token = $1", + err := db.QueryRow(`SELECT uuid, "user", token, expiry FROM refresh_tokens WHERE token = $1`, refreshTokenStr).Scan(&refresh.UUID, &refresh.User, &refresh.Token, &refresh.Expiry) if err != nil { return User{}, errors.New("Invalid Refresh Token") diff --git a/internal/goscrobble/track.go b/internal/goscrobble/track.go index f693d09d..cd88690a 100644 --- a/internal/goscrobble/track.go +++ b/internal/goscrobble/track.go @@ -98,7 +98,7 @@ func insertTrack(name string, legnth int, mbid string, spotifyId string, album s func getTrackByCol(col string, val string, tx *sql.Tx) Track { var track Track err := tx.QueryRow( - "SELECT uuid, name, IFNULL(desc,''), IFNULL(img,''), mbid FROM tracks WHERE "+col+" = $1 LIMIT 1", + `SELECT uuid, name, COALESCE(desc,''), COALESCE(img,''), mbid FROM tracks WHERE "`+col+`" = $1 LIMIT 1`, val).Scan(&track.UUID, &track.Name, &track.Desc, &track.Img, &track.MusicBrainzID) if err != nil { @@ -114,11 +114,11 @@ func getTrackWithArtists(name string, artists []string, album string, tx *sql.Tx var track Track artistString := strings.Join(artists, "','") err := tx.QueryRow( - "SELECT uuid, name, IFNULL(desc,''), IFNULL(img,''), mbid FROM tracks "+ - "LEFT JOIN track_artist ON tracks.uuid = track_artist.track "+ - "LEFT JOIN track_album ON tracks.uuid = track_album.track "+ - "WHERE name = $1 AND track_artist.artistIN ('"+artistString+"') "+ - "AND track_album.album = $2 LIMIT 1", + `SELECT uuid, name, COALESCE(desc,''), COALESCE(img,''), mbid FROM tracks `+ + `LEFT JOIN track_artist ON tracks.uuid = track_artist.track `+ + `LEFT JOIN track_album ON tracks.uuid = track_album.track `+ + `WHERE name = $1 AND track_artist.artistIN ('`+artistString+`') `+ + `AND track_album.album = $2 LIMIT 1`, name, album).Scan(&track.UUID, &track.Name, &track.Desc, &track.Img, &track.MusicBrainzID) if err != nil { @@ -137,8 +137,8 @@ func insertNewTrack(track *Track, name string, length int, mbid string, spotifyI track.MusicBrainzID = mbid track.SpotifyID = spotifyId - _, err := tx.Exec("INSERT INTO tracks (uuid, name, length, mbid, spotify_id) "+ - "VALUES ($1,$2,$3,$4,$5)", track.UUID, track.Name, track.Length, track.MusicBrainzID, track.SpotifyID) + _, err := tx.Exec(`INSERT INTO tracks (uuid, name, length, mbid, spotify_id) `+ + `VALUES ($1,$2,$3,$4,$5)`, track.UUID, track.Name, track.Length, track.MusicBrainzID, track.SpotifyID) return err } @@ -158,8 +158,8 @@ func (track *Track) linkTrack(album string, artists []string, tx *sql.Tx) error } func (track *Track) linkTrackToAlbum(albumUuid string, tx *sql.Tx) error { - _, err := tx.Exec("INSERT INTO track_album (track, album) "+ - "VALUES ($1, $2)", track.UUID, albumUuid) + _, err := tx.Exec(`INSERT INTO track_album (track, album) `+ + `VALUES ($1, $2)`, track.UUID, albumUuid) return err } @@ -167,8 +167,8 @@ func (track *Track) linkTrackToAlbum(albumUuid string, tx *sql.Tx) error { func (track *Track) linkTrackToArtists(artists []string, tx *sql.Tx) error { var err error for _, artist := range artists { - _, err = tx.Exec("INSERT INTO track_artist (track, artist) "+ - "VALUES ($1,$2)", track.UUID, artist) + _, err = tx.Exec(`INSERT INTO track_artist (track, artist) `+ + `VALUES ($1,$2)`, track.UUID, artist) if err != nil { return err } @@ -178,18 +178,18 @@ func (track *Track) linkTrackToArtists(artists []string, tx *sql.Tx) error { } func (track *Track) updateTrack(col string, val string, tx *sql.Tx) error { - _, err := tx.Exec("UPDATE tracks SET "+col+" = $1 WHERE uuid = $2", val, track.UUID) + _, err := tx.Exec(`UPDATE tracks SET "`+col+`" = $1 WHERE uuid = $2`, val, track.UUID) return err } func getTrackByUUID(uuid string) (Track, error) { var track Track - err := db.QueryRow("SELECT tracks.uuid, tracks.name, IFNULL(albums.desc,''), IFNULL(albums.uuid,''), tracks.length, tracks.mbid, tracks.spotify_id "+ - "FROM tracks "+ - "LEFT JOIN track_album ON track_album.track = tracks.uuid "+ - "LEFT JOIN albums ON track_album.album = albums.uuid "+ - "WHERE tracks.uuid = $1", + err := db.QueryRow(`SELECT tracks.uuid, tracks.name, COALESCE(albums.desc,''), COALESCE(albums.uuid,''), tracks.length, tracks.mbid, tracks.spotify_id `+ + `FROM tracks `+ + `LEFT JOIN track_album ON track_album.track = tracks.uuid `+ + `LEFT JOIN albums ON track_album.album = albums.uuid `+ + `WHERE tracks.uuid = $1`, uuid).Scan(&track.UUID, &track.Name, &track.Desc, &track.Img, &track.Length, &track.MusicBrainzID, &track.SpotifyID) if err != nil { @@ -203,15 +203,15 @@ func getTrackByUUID(uuid string) (Track, error) { func getTopTracks(userUuid string) (TopTracks, error) { var topTracks TopTracks - rows, err := db.Query("SELECT tracks.uuid, tracks.name, IFNULL(albums.uuid,''), count(*) "+ - "FROM scrobbles "+ - "JOIN tracks ON tracks.uuid = scrobbles.track "+ - "JOIN track_album ON track_album.track = tracks.uuid "+ - "JOIN albums ON track_album.album = albums.uuid "+ - "WHERE user = $1 "+ - "GROUP BY scrobbles.track "+ - "ORDER BY count(*) DESC "+ - "LIMIT 14", + rows, err := db.Query(`SELECT tracks.uuid, tracks.name, COALESCE(albums.uuid,''), count(*) `+ + `FROM scrobbles `+ + `JOIN tracks ON tracks.uuid = scrobbles.track `+ + `JOIN track_album ON track_album.track = tracks.uuid `+ + `JOIN albums ON track_album.album = albums.uuid `+ + `WHERE "user" = $1 `+ + `GROUP BY scrobbles.track `+ + `ORDER BY count(*) DESC `+ + `LIMIT 14`, userUuid) if err != nil { log.Printf("Failed to fetch top tracks: %+v", err) @@ -251,9 +251,9 @@ func (track *Track) loadExtraTrackInfo() error { func (track *Track) getArtistsForTrack() error { artists := []Artist{} - rows, err := db.Query("SELECT track_artist.artist "+ - "FROM track_artist "+ - "WHERE track_artist.track = $1", + rows, err := db.Query(`SELECT track_artist.artist `+ + `FROM track_artist `+ + `WHERE track_artist.track = $1`, track.UUID) if err != nil { log.Printf("Failed to fetch artists for track: %+v", err) @@ -283,9 +283,9 @@ func (track *Track) getArtistsForTrack() error { func (track *Track) getAlbumsForTrack() error { albums := []Album{} - rows, err := db.Query("SELECT track_album.album "+ - "FROM track_album "+ - "WHERE track_album.track = $1", + rows, err := db.Query(`SELECT track_album.album `+ + `FROM track_album `+ + `WHERE track_album.track = $1`, track.UUID) if err != nil { log.Printf("Failed to fetch album for track: %+v", err) @@ -320,7 +320,7 @@ func getTopUsersForTrackUUID(trackUUID string, limit int, page int) (TopUserTrac // Yeah this isn't great. But for now.. it works! Cache later // TODO: This is counting total scrobbles, not unique users total, err := getDbCount( - "SELECT COUNT(*) FROM scrobbles WHERE track = $1 GROUP BY track, user", trackUUID) + `SELECT COUNT(*) FROM scrobbles WHERE track = $1 GROUP BY track, "user"`, trackUUID) if err != nil { log.Printf("Failed to fetch scrobble count: %+v", err) @@ -328,12 +328,12 @@ func getTopUsersForTrackUUID(trackUUID string, limit int, page int) (TopUserTrac } rows, err := db.Query( - "SELECT scrobbles.user, users.username, COUNT(*) "+ - "FROM scrobbles "+ - "JOIN users ON scrobbles.user = users.uuid "+ - "WHERE track = $1 "+ - "GROUP BY scrobbles.user "+ - "ORDER BY COUNT(*) DESC LIMIT $2", + `SELECT scrobbles.user, users.username, COUNT(*) `+ + `FROM scrobbles `+ + `JOIN users ON scrobbles."user" = users.uuid `+ + `WHERE track = $1 `+ + `GROUP BY scrobbles."user" `+ + `ORDER BY COUNT(*) DESC LIMIT $2`, trackUUID, limit) if err != nil { diff --git a/internal/goscrobble/user.go b/internal/goscrobble/user.go index 12118f7f..d37afed2 100644 --- a/internal/goscrobble/user.go +++ b/internal/goscrobble/user.go @@ -99,7 +99,7 @@ func loginUser(logReq *RequestRequest, ip net.IP) ([]byte, error) { } if strings.Contains(logReq.Username, "@") { - err := db.QueryRow("SELECT uuid, username, email, password, admin, mod FROM users WHERE email = $1 AND active = true", + err := db.QueryRow(`SELECT uuid, username, email, password, admin, mod FROM users WHERE email = $1 AND active = true`, logReq.Username).Scan(&user.UUID, &user.Username, &user.Email, &user.Password, &user.Admin, &user.Mod) if err != nil { if err == sql.ErrNoRows { @@ -107,7 +107,7 @@ func loginUser(logReq *RequestRequest, ip net.IP) ([]byte, error) { } } } else { - err := db.QueryRow("SELECT uuid, username, email, password, admin, mod FROM users WHERE username = $1 AND active = true", + err := db.QueryRow(`SELECT uuid, username, email, password, admin, mod FROM users WHERE username = $1 AND active = true`, logReq.Username).Scan(&user.UUID, &user.Username, &user.Email, &user.Password, &user.Admin, &user.Mod) if err == sql.ErrNoRows { return resp, errors.New("Invalid Username or Password") @@ -140,20 +140,20 @@ func insertUser(username string, email string, password []byte, ip net.IP) error log.Printf(ip.String()) - _, err := db.Exec("INSERT INTO users (uuid, created_at, created_ip, modified_at, modified_ip, username, email, password, token) "+ - "VALUES ($1,NOW(),$2,NOW(),$3,$4,$5,$6,$7)", uuid, ip.String(), ip.String(), username, email, password, token) + _, err := db.Exec(`INSERT INTO users (uuid, created_at, created_ip, modified_at, modified_ip, username, email, password, token) `+ + `VALUES ($1,NOW(),$2,NOW(),$3,$4,$5,$6,$7)`, uuid, ip.String(), ip.String(), username, email, password, token) return err } func (user *User) updateUser(field string, value string, ip net.IP) error { - _, err := db.Exec("UPDATE users SET "+field+" = $1, modified_at = NOW(), modified_ip = $2 WHERE uuid = $3", value, ip, user.UUID) + _, err := db.Exec(`UPDATE users SET "`+field+`" = $1, modified_at = NOW(), modified_ip = $2 WHERE uuid = $3`, value, ip, user.UUID) return err } func (user *User) updateUserDirect(field string, value string) error { - _, err := db.Exec("UPDATE users SET "+field+" = $1 WHERE uuid = $2", value, user.UUID) + _, err := db.Exec(`UPDATE users SET "`+field+`" = $1 WHERE uuid = $2`, value, user.UUID) return err } @@ -176,7 +176,7 @@ func isValidPassword(password string, user User) bool { // userAlreadyExists - Returns bool indicating if a record exists for either username or email // Using two look ups to make use of DB indexes. func userAlreadyExists(req *RequestRequest) bool { - count, err := getDbCount("SELECT COUNT(*) FROM users WHERE username = $1", req.Username) + count, err := getDbCount(`SELECT COUNT(*) FROM users WHERE username = $1`, req.Username) if err != nil { fmt.Printf("Error querying for duplicate users: %v", err) return true @@ -188,7 +188,7 @@ func userAlreadyExists(req *RequestRequest) bool { if req.Email != "" { // Only run email check if there's an email... - count, err = getDbCount("SELECT COUNT(*) FROM users WHERE email = $1", req.Email) + count, err = getDbCount(`SELECT COUNT(*) FROM users WHERE email = $1`, req.Email) } if err != nil { @@ -201,7 +201,7 @@ func userAlreadyExists(req *RequestRequest) bool { func getUserByUUID(uuid string) (User, error) { var user User - err := db.QueryRow("SELECT uuid, created_at, created_ip, modified_at, modified_ip, username, email, password, verified, admin, mod, timezone, token FROM users WHERE uuid = $1 AND active = true", + err := db.QueryRow(`SELECT uuid, created_at, created_ip, modified_at, modified_ip, username, email, password, verified, admin, mod, timezone, token FROM users WHERE uuid = $1 AND active = true`, uuid).Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Mod, &user.Timezone, &user.Token) if err == sql.ErrNoRows { @@ -213,7 +213,7 @@ func getUserByUUID(uuid string) (User, error) { func getUserByUsername(username string) (User, error) { var user User - err := db.QueryRow("SELECT uuid, created_at, created_ip, modified_at, modified_ip, username, email, password, verified, admin, mod, timezone, token FROM users WHERE username = $1 AND active = true", + err := db.QueryRow(`SELECT uuid, created_at, created_ip, modified_at, modified_ip, username, email, password, verified, admin, mod, timezone, token FROM users WHERE username = $1 AND active = true`, username).Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Mod, &user.Timezone, &user.Token) if err == sql.ErrNoRows { @@ -225,7 +225,7 @@ func getUserByUsername(username string) (User, error) { func getUserByEmail(email string) (User, error) { var user User - err := db.QueryRow("SELECT uuid, created_at, created_ip, modified_at, modified_ip, username, email, password, verified, admin, mod, timezone, token FROM users WHERE email = $1 AND active = true", + err := db.QueryRow(`SELECT uuid, created_at, created_ip, modified_at, modified_ip, username, email, password, verified, admin, mod, timezone, token FROM users WHERE email = $1 AND active = true`, email).Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Mod, &user.Timezone, &user.Token) if err == sql.ErrNoRows { @@ -237,8 +237,8 @@ func getUserByEmail(email string) (User, error) { func getUserByResetToken(token string) (User, error) { var user User - err := db.QueryRow("SELECT users.uuid, created_at, created_ip, modified_at, modified_ip, username, email, password, verified, admin, mod, timezone, token FROM users "+ - "JOIN resettoken ON resettoken.user = users.uuid WHERE resettoken.token = $1 AND active = true", + err := db.QueryRow(`SELECT users.uuid, created_at, created_ip, modified_at, modified_ip, username, email, password, verified, admin, mod, timezone, token FROM users `+ + `JOIN resettoken ON resettoken.user = users.uuid WHERE resettoken.token = $1 AND active = true`, token).Scan(&user.UUID, &user.CreatedAt, &user.CreatedIp, &user.ModifiedAt, &user.ModifiedIP, &user.Username, &user.Email, &user.Password, &user.Verified, &user.Admin, &user.Mod, &user.Timezone, &user.Token) if err == sql.ErrNoRows { @@ -269,26 +269,26 @@ func (user *User) sendResetEmail(ip net.IP) error { } func (user *User) saveResetToken(token string, expiry time.Time) error { - _, _ = db.Exec("DELETE FROM resettoken WHERE user = $1", user.UUID) - _, err := db.Exec("INSERT INTO resettoken (user, token, expiry) "+ - "VALUES ($1,$2,$3)", user.UUID, token, expiry) + _, _ = db.Exec(`DELETE FROM resettoken WHERE "user" = $1`, user.UUID) + _, err := db.Exec(`INSERT INTO resettoken ("user", token, expiry) `+ + `VALUES ($1,$2,$3)`, user.UUID, token, expiry) return err } func clearOldResetTokens() { - _, _ = db.Exec("DELETE FROM resettoken WHERE expiry < NOW()") + _, _ = db.Exec(`DELETE FROM resettoken WHERE expiry < NOW()`) } func clearResetToken(token string) error { - _, err := db.Exec("DELETE FROM resettoken WHERE token = $1", token) + _, err := db.Exec(`DELETE FROM resettoken WHERE token = $1`, token) return err } // checkResetToken - If a token exists check it func checkResetToken(token string) (bool, error) { - count, err := getDbCount("SELECT COUNT(*) FROM resettoken WHERE token = $1", token) + count, err := getDbCount(`SELECT COUNT(*) FROM resettoken WHERE token = $1`, token) if err != nil { return false, err @@ -303,7 +303,7 @@ func (user *User) updatePassword(newPassword string, ip net.IP) error { return errors.New("Bad password") } - _, err = db.Exec("UPDATE users SET password = $1 WHERE uuid = $2", hash, user.UUID) + _, err = db.Exec(`UPDATE users SET password = $1 WHERE uuid = $2`, hash, user.UUID) if err != nil { return errors.New("Failed to update password") } @@ -321,8 +321,8 @@ func (user *User) getNavidromeTokens() (OauthToken, error) { func getAllSpotifyUsers() ([]User, error) { users := make([]User, 0) - rows, err := db.Query("SELECT users.uuid, created_at, created_ip, modified_at, modified_ip, users.username, email, password, verified, admin, mod, timezone FROM users " + - "JOIN oauth_tokens ON oauth_tokens.user = users.uuid AND oauth_tokens.service = 'spotify' WHERE users.active = true") + rows, err := db.Query(`SELECT users.uuid, created_at, created_ip, modified_at, modified_ip, users.username, email, password, verified, admin, mod, timezone FROM users ` + + `JOIN oauth_tokens ON oauth_tokens."user" = users.uuid AND oauth_tokens.service = 'spotify' WHERE users.active = true`) if err != nil { log.Printf("Failed to fetch spotify users: %+v", err) @@ -353,8 +353,8 @@ func getAllSpotifyUsers() ([]User, error) { func getAllNavidromeUsers() ([]User, error) { users := make([]User, 0) - rows, err := db.Query("SELECT users.uuid, created_at, created_ip, modified_at, modified_ip, users.username, email, password, verified, admin, mod, timezone FROM users " + - "JOIN oauth_tokens ON oauth_tokens.user = users.uuid AND oauth_tokens.service = 'navidrome' WHERE users.active = true") + rows, err := db.Query(`SELECT users.uuid, created_at, created_ip, modified_at, modified_ip, users.username, email, password, verified, admin, mod, timezone FROM users ` + + `JOIN oauth_tokens ON oauth_tokens."user" = users.uuid AND oauth_tokens.service = 'navidrome' WHERE users.active = true`) if err != nil { log.Printf("Failed to fetch navidrome users: %+v", err) diff --git a/migrations/9_spotify.down.sql b/migrations/9_spotify.down.sql index 5de6bda2..5b5a0066 100644 --- a/migrations/9_spotify.down.sql +++ b/migrations/9_spotify.down.sql @@ -1,7 +1,6 @@ START TRANSACTION; ALTER TABLE tracks DROP COLUMN spotify_id; -ALTER TABLE users DROP COLUMN spotify_id; ALTER TABLE albums DROP COLUMN spotify_id; ALTER TABLE artists DROP COLUMN spotify_id; diff --git a/migrations/9_spotify.up.sql b/migrations/9_spotify.up.sql index 9bdfc8cb..d2e1e411 100644 --- a/migrations/9_spotify.up.sql +++ b/migrations/9_spotify.up.sql @@ -1,11 +1,9 @@ START TRANSACTION; -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 ''; -CREATE INDEX usersSpotifyLookup ON users (spotify_id); CREATE INDEX albumsSpotifyLookup ON albums (spotify_id); CREATE INDEX artistsSpotifyLookup ON artists (spotify_id); CREATE INDEX tracksSpotifyLookup ON tracks (spotify_id);