mirror of
https://github.com/riwiwa/muzi.git
synced 2026-03-04 00:51:59 -08:00
Add search and pages for tracks, albums, and artists
This commit is contained in:
621
db/entities.go
Normal file
621
db/entities.go
Normal file
@@ -0,0 +1,621 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgtype"
|
||||
)
|
||||
|
||||
type Artist struct {
|
||||
Id int
|
||||
UserId int
|
||||
Name string
|
||||
ImageUrl string
|
||||
Bio string
|
||||
SpotifyId string
|
||||
MusicbrainzId string
|
||||
}
|
||||
|
||||
type Album struct {
|
||||
Id int
|
||||
UserId int
|
||||
Title string
|
||||
ArtistId int
|
||||
CoverUrl string
|
||||
SpotifyId string
|
||||
MusicbrainzId string
|
||||
}
|
||||
|
||||
type Song struct {
|
||||
Id int
|
||||
UserId int
|
||||
Title string
|
||||
ArtistId int
|
||||
AlbumId int
|
||||
DurationMs int
|
||||
SpotifyId string
|
||||
MusicbrainzId string
|
||||
}
|
||||
|
||||
func GetOrCreateArtist(userId int, name string) (int, bool, error) {
|
||||
if name == "" {
|
||||
return 0, false, nil
|
||||
}
|
||||
|
||||
var id int
|
||||
err := Pool.QueryRow(context.Background(),
|
||||
"SELECT id FROM artists WHERE user_id = $1 AND name = $2",
|
||||
userId, name).Scan(&id)
|
||||
if err == nil {
|
||||
return id, false, nil
|
||||
}
|
||||
|
||||
err = Pool.QueryRow(context.Background(),
|
||||
`INSERT INTO artists (user_id, name) VALUES ($1, $2)
|
||||
ON CONFLICT (user_id, name) DO UPDATE SET name = EXCLUDED.name
|
||||
RETURNING id`,
|
||||
userId, name).Scan(&id)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating artist: %v\n", err)
|
||||
return 0, false, err
|
||||
}
|
||||
return id, true, nil
|
||||
}
|
||||
|
||||
func GetArtistById(id int) (Artist, error) {
|
||||
var artist Artist
|
||||
var imageUrlPg, bioPg, spotifyIdPg, musicbrainzIdPg pgtype.Text
|
||||
err := Pool.QueryRow(context.Background(),
|
||||
"SELECT id, user_id, name, image_url, bio, spotify_id, musicbrainz_id FROM artists WHERE id = $1",
|
||||
id).Scan(&artist.Id, &artist.UserId, &artist.Name, &imageUrlPg, &bioPg, &spotifyIdPg, &musicbrainzIdPg)
|
||||
if err != nil {
|
||||
return Artist{}, err
|
||||
}
|
||||
if imageUrlPg.Status == pgtype.Present {
|
||||
artist.ImageUrl = imageUrlPg.String
|
||||
}
|
||||
if bioPg.Status == pgtype.Present {
|
||||
artist.Bio = bioPg.String
|
||||
}
|
||||
if spotifyIdPg.Status == pgtype.Present {
|
||||
artist.SpotifyId = spotifyIdPg.String
|
||||
}
|
||||
if musicbrainzIdPg.Status == pgtype.Present {
|
||||
artist.MusicbrainzId = musicbrainzIdPg.String
|
||||
}
|
||||
return artist, nil
|
||||
}
|
||||
|
||||
func GetArtistByName(userId int, name string) (Artist, error) {
|
||||
var artist Artist
|
||||
var imageUrlPg, bioPg, spotifyIdPg, musicbrainzIdPg pgtype.Text
|
||||
err := Pool.QueryRow(context.Background(),
|
||||
"SELECT id, user_id, name, image_url, bio, spotify_id, musicbrainz_id FROM artists WHERE user_id = $1 AND name = $2",
|
||||
userId, name).Scan(&artist.Id, &artist.UserId, &artist.Name, &imageUrlPg, &bioPg, &spotifyIdPg, &musicbrainzIdPg)
|
||||
if err != nil {
|
||||
return Artist{}, err
|
||||
}
|
||||
if imageUrlPg.Status == pgtype.Present {
|
||||
artist.ImageUrl = imageUrlPg.String
|
||||
}
|
||||
if bioPg.Status == pgtype.Present {
|
||||
artist.Bio = bioPg.String
|
||||
}
|
||||
if spotifyIdPg.Status == pgtype.Present {
|
||||
artist.SpotifyId = spotifyIdPg.String
|
||||
}
|
||||
if musicbrainzIdPg.Status == pgtype.Present {
|
||||
artist.MusicbrainzId = musicbrainzIdPg.String
|
||||
}
|
||||
return artist, nil
|
||||
}
|
||||
|
||||
func UpdateArtist(id int, name, imageUrl, bio, spotifyId, musicbrainzId string) error {
|
||||
_, err := Pool.Exec(context.Background(),
|
||||
`UPDATE artists SET name = $1, image_url = $2, bio = $3, spotify_id = $4, musicbrainz_id = $5 WHERE id = $6`,
|
||||
name, imageUrl, bio, spotifyId, musicbrainzId, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func SearchArtists(userId int, query string) ([]Artist, error) {
|
||||
likePattern := "%" + query + "%"
|
||||
rows, err := Pool.Query(context.Background(),
|
||||
`SELECT id, user_id, name, image_url, bio, spotify_id, musicbrainz_id
|
||||
FROM artists WHERE user_id = $1 AND (similarity(name, $2) > 0.1 OR LOWER(name) LIKE LOWER($3))
|
||||
ORDER BY similarity(name, $2) DESC LIMIT 20`,
|
||||
userId, query, likePattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var artists []Artist
|
||||
for rows.Next() {
|
||||
var a Artist
|
||||
var imageUrlPg, bioPg, spotifyIdPg, musicbrainzIdPg pgtype.Text
|
||||
err := rows.Scan(&a.Id, &a.UserId, &a.Name, &imageUrlPg, &bioPg, &spotifyIdPg, &musicbrainzIdPg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if imageUrlPg.Status == pgtype.Present {
|
||||
a.ImageUrl = imageUrlPg.String
|
||||
}
|
||||
if bioPg.Status == pgtype.Present {
|
||||
a.Bio = bioPg.String
|
||||
}
|
||||
if spotifyIdPg.Status == pgtype.Present {
|
||||
a.SpotifyId = spotifyIdPg.String
|
||||
}
|
||||
if musicbrainzIdPg.Status == pgtype.Present {
|
||||
a.MusicbrainzId = musicbrainzIdPg.String
|
||||
}
|
||||
artists = append(artists, a)
|
||||
}
|
||||
return artists, nil
|
||||
}
|
||||
|
||||
func GetOrCreateAlbum(userId int, title string, artistId int) (int, bool, error) {
|
||||
if title == "" {
|
||||
return 0, false, nil
|
||||
}
|
||||
|
||||
var id int
|
||||
err := Pool.QueryRow(context.Background(),
|
||||
"SELECT id FROM albums WHERE user_id = $1 AND title = $2 AND (artist_id = $3 OR (artist_id IS NULL AND $3 IS NULL))",
|
||||
userId, title, artistId).Scan(&id)
|
||||
if err == nil {
|
||||
return id, false, nil
|
||||
}
|
||||
|
||||
err = Pool.QueryRow(context.Background(),
|
||||
`INSERT INTO albums (user_id, title, artist_id) VALUES ($1, $2, $3)
|
||||
ON CONFLICT (user_id, title, artist_id) DO UPDATE SET title = EXCLUDED.title
|
||||
RETURNING id`,
|
||||
userId, title, artistId).Scan(&id)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating album: %v\n", err)
|
||||
return 0, false, err
|
||||
}
|
||||
return id, true, nil
|
||||
}
|
||||
|
||||
func GetAlbumById(id int) (Album, error) {
|
||||
var album Album
|
||||
err := Pool.QueryRow(context.Background(),
|
||||
"SELECT id, user_id, title, artist_id, cover_url, spotify_id, musicbrainz_id FROM albums WHERE id = $1",
|
||||
id).Scan(&album.Id, &album.UserId, &album.Title, &album.ArtistId, &album.CoverUrl,
|
||||
&album.SpotifyId, &album.MusicbrainzId)
|
||||
if err != nil {
|
||||
return Album{}, err
|
||||
}
|
||||
return album, nil
|
||||
}
|
||||
|
||||
func GetAlbumByName(userId int, title string, artistId int) (Album, error) {
|
||||
var album Album
|
||||
var artistIdVal int
|
||||
var coverUrlPg, spotifyIdPg, musicbrainzIdPg pgtype.Text
|
||||
|
||||
var query string
|
||||
var args []interface{}
|
||||
if artistId > 0 {
|
||||
query = `SELECT id, user_id, title, artist_id, cover_url, spotify_id, musicbrainz_id
|
||||
FROM albums WHERE user_id = $1 AND title = $2 AND artist_id = $3`
|
||||
args = []interface{}{userId, title, artistId}
|
||||
} else {
|
||||
query = `SELECT id, user_id, title, artist_id, cover_url, spotify_id, musicbrainz_id
|
||||
FROM albums WHERE user_id = $1 AND title = $2`
|
||||
args = []interface{}{userId, title}
|
||||
}
|
||||
|
||||
err := Pool.QueryRow(context.Background(), query, args...).Scan(
|
||||
&album.Id, &album.UserId, &album.Title, &artistIdVal, &coverUrlPg,
|
||||
&spotifyIdPg, &musicbrainzIdPg)
|
||||
if err != nil {
|
||||
return Album{}, err
|
||||
}
|
||||
album.ArtistId = artistIdVal
|
||||
if coverUrlPg.Status == pgtype.Present {
|
||||
album.CoverUrl = coverUrlPg.String
|
||||
}
|
||||
if spotifyIdPg.Status == pgtype.Present {
|
||||
album.SpotifyId = spotifyIdPg.String
|
||||
}
|
||||
if musicbrainzIdPg.Status == pgtype.Present {
|
||||
album.MusicbrainzId = musicbrainzIdPg.String
|
||||
}
|
||||
return album, nil
|
||||
}
|
||||
|
||||
func UpdateAlbum(id int, title, coverUrl, spotifyId, musicbrainzId string) error {
|
||||
_, err := Pool.Exec(context.Background(),
|
||||
`UPDATE albums SET title = $1, cover_url = $2, spotify_id = $3, musicbrainz_id = $4 WHERE id = $5`,
|
||||
title, coverUrl, spotifyId, musicbrainzId, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func SearchAlbums(userId int, query string) ([]Album, error) {
|
||||
likePattern := "%" + query + "%"
|
||||
rows, err := Pool.Query(context.Background(),
|
||||
`SELECT id, user_id, title, artist_id, cover_url, spotify_id, musicbrainz_id
|
||||
FROM albums WHERE user_id = $1 AND (similarity(title, $2) > 0.1 OR LOWER(title) LIKE LOWER($3))
|
||||
ORDER BY similarity(title, $2) DESC LIMIT 20`,
|
||||
userId, query, likePattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var albums []Album
|
||||
for rows.Next() {
|
||||
var a Album
|
||||
var artistIdVal int
|
||||
var coverUrlPg, spotifyIdPg, musicbrainzIdPg pgtype.Text
|
||||
err := rows.Scan(&a.Id, &a.UserId, &a.Title, &artistIdVal, &coverUrlPg, &spotifyIdPg, &musicbrainzIdPg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.ArtistId = artistIdVal
|
||||
if coverUrlPg.Status == pgtype.Present {
|
||||
a.CoverUrl = coverUrlPg.String
|
||||
}
|
||||
if spotifyIdPg.Status == pgtype.Present {
|
||||
a.SpotifyId = spotifyIdPg.String
|
||||
}
|
||||
if musicbrainzIdPg.Status == pgtype.Present {
|
||||
a.MusicbrainzId = musicbrainzIdPg.String
|
||||
}
|
||||
albums = append(albums, a)
|
||||
}
|
||||
return albums, nil
|
||||
}
|
||||
|
||||
func GetOrCreateSong(userId int, title string, artistId int, albumId int) (int, bool, error) {
|
||||
if title == "" {
|
||||
return 0, false, nil
|
||||
}
|
||||
|
||||
var id int
|
||||
err := Pool.QueryRow(context.Background(),
|
||||
"SELECT id FROM songs WHERE user_id = $1 AND title = $2 AND (artist_id = $3 OR (artist_id IS NULL AND $3 IS NULL))",
|
||||
userId, title, artistId).Scan(&id)
|
||||
if err == nil {
|
||||
return id, false, nil
|
||||
}
|
||||
|
||||
err = Pool.QueryRow(context.Background(),
|
||||
`INSERT INTO songs (user_id, title, artist_id, album_id) VALUES ($1, $2, $3, $4)
|
||||
ON CONFLICT (user_id, title, artist_id) DO UPDATE SET title = EXCLUDED.title
|
||||
RETURNING id`,
|
||||
userId, title, artistId, albumId).Scan(&id)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error creating song: %v\n", err)
|
||||
return 0, false, err
|
||||
}
|
||||
return id, true, nil
|
||||
}
|
||||
|
||||
func GetSongById(id int) (Song, error) {
|
||||
var song Song
|
||||
err := Pool.QueryRow(context.Background(),
|
||||
"SELECT id, user_id, title, artist_id, album_id, duration_ms, spotify_id, musicbrainz_id FROM songs WHERE id = $1",
|
||||
id).Scan(&song.Id, &song.UserId, &song.Title, &song.ArtistId, &song.AlbumId,
|
||||
&song.DurationMs, &song.SpotifyId, &song.MusicbrainzId)
|
||||
if err != nil {
|
||||
return Song{}, err
|
||||
}
|
||||
return song, nil
|
||||
}
|
||||
|
||||
func GetSongByName(userId int, title string, artistId int) (Song, error) {
|
||||
var song Song
|
||||
var artistIdVal, albumIdVal int
|
||||
var durationMs *int
|
||||
var spotifyIdPg, musicbrainzIdPg pgtype.Text
|
||||
|
||||
var query string
|
||||
var args []interface{}
|
||||
if artistId > 0 {
|
||||
query = `SELECT id, user_id, title, artist_id, album_id, duration_ms, spotify_id, musicbrainz_id
|
||||
FROM songs WHERE user_id = $1 AND title = $2 AND artist_id = $3`
|
||||
args = []interface{}{userId, title, artistId}
|
||||
} else {
|
||||
query = `SELECT id, user_id, title, artist_id, album_id, duration_ms, spotify_id, musicbrainz_id
|
||||
FROM songs WHERE user_id = $1 AND title = $2`
|
||||
args = []interface{}{userId, title}
|
||||
}
|
||||
|
||||
err := Pool.QueryRow(context.Background(), query, args...).Scan(
|
||||
&song.Id, &song.UserId, &song.Title, &artistIdVal, &albumIdVal,
|
||||
&durationMs, &spotifyIdPg, &musicbrainzIdPg)
|
||||
if err != nil {
|
||||
return Song{}, err
|
||||
}
|
||||
song.ArtistId = artistIdVal
|
||||
song.AlbumId = albumIdVal
|
||||
if durationMs != nil {
|
||||
song.DurationMs = *durationMs
|
||||
}
|
||||
if spotifyIdPg.Status == pgtype.Present {
|
||||
song.SpotifyId = spotifyIdPg.String
|
||||
}
|
||||
if musicbrainzIdPg.Status == pgtype.Present {
|
||||
song.MusicbrainzId = musicbrainzIdPg.String
|
||||
}
|
||||
return song, nil
|
||||
}
|
||||
|
||||
func UpdateSong(id int, title string, durationMs int, spotifyId, musicbrainzId string) error {
|
||||
_, err := Pool.Exec(context.Background(),
|
||||
`UPDATE songs SET title = $1, duration_ms = $2, spotify_id = $3, musicbrainz_id = $4 WHERE id = $5`,
|
||||
title, durationMs, spotifyId, musicbrainzId, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func SearchSongs(userId int, query string) ([]Song, error) {
|
||||
likePattern := "%" + query + "%"
|
||||
rows, err := Pool.Query(context.Background(),
|
||||
`SELECT id, user_id, title, artist_id, album_id, duration_ms, spotify_id, musicbrainz_id
|
||||
FROM songs WHERE user_id = $1 AND (similarity(title, $2) > 0.1 OR LOWER(title) LIKE LOWER($3))
|
||||
ORDER BY similarity(title, $2) DESC LIMIT 20`,
|
||||
userId, query, likePattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var songs []Song
|
||||
for rows.Next() {
|
||||
var s Song
|
||||
var artistIdVal, albumIdVal, durationMsVal int
|
||||
var spotifyIdPg, musicbrainzIdPg pgtype.Text
|
||||
err := rows.Scan(&s.Id, &s.UserId, &s.Title, &artistIdVal, &albumIdVal, &durationMsVal, &spotifyIdPg, &musicbrainzIdPg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.ArtistId = artistIdVal
|
||||
s.AlbumId = albumIdVal
|
||||
s.DurationMs = durationMsVal
|
||||
if spotifyIdPg.Status == pgtype.Present {
|
||||
s.SpotifyId = spotifyIdPg.String
|
||||
}
|
||||
if musicbrainzIdPg.Status == pgtype.Present {
|
||||
s.MusicbrainzId = musicbrainzIdPg.String
|
||||
}
|
||||
songs = append(songs, s)
|
||||
}
|
||||
return songs, nil
|
||||
}
|
||||
|
||||
func GetArtistStats(userId, artistId int) (int, error) {
|
||||
var count int
|
||||
err := Pool.QueryRow(context.Background(),
|
||||
"SELECT COUNT(*) FROM history WHERE user_id = $1 AND artist_id = $2",
|
||||
userId, artistId).Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
func GetSongStats(userId, songId int) (int, error) {
|
||||
var count int
|
||||
err := Pool.QueryRow(context.Background(),
|
||||
"SELECT COUNT(*) FROM history WHERE user_id = $1 AND song_id = $2",
|
||||
userId, songId).Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
func MergeArtists(userId int, fromArtistId, toArtistId int) error {
|
||||
_, err := Pool.Exec(context.Background(),
|
||||
`UPDATE history SET artist_id = $1 WHERE user_id = $2 AND artist_id = $3`,
|
||||
toArtistId, userId, fromArtistId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = Pool.Exec(context.Background(),
|
||||
`UPDATE songs SET artist_id = $1 WHERE user_id = $2 AND artist_id = $3`,
|
||||
toArtistId, userId, fromArtistId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = Pool.Exec(context.Background(),
|
||||
`DELETE FROM artists WHERE id = $1 AND user_id = $2`,
|
||||
fromArtistId, userId)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetHistoryForArtist(userId, artistId int, limit, offset int) ([]ScrobbleEntry, error) {
|
||||
rows, err := Pool.Query(context.Background(),
|
||||
`SELECT h.timestamp, h.song_name, h.album_name, h.ms_played, h.platform,
|
||||
(SELECT name FROM artists WHERE id = h.artist_id) as artist_name
|
||||
FROM history h WHERE h.user_id = $1 AND h.artist_id = $2
|
||||
ORDER BY h.timestamp DESC LIMIT $3 OFFSET $4`,
|
||||
userId, artistId, limit, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var entries []ScrobbleEntry
|
||||
for rows.Next() {
|
||||
var e ScrobbleEntry
|
||||
err := rows.Scan(&e.Timestamp, &e.SongName, &e.AlbumName, &e.MsPlayed, &e.Platform, &e.ArtistName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entries = append(entries, e)
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func GetHistoryForSong(userId, songId int, limit, offset int) ([]ScrobbleEntry, error) {
|
||||
rows, err := Pool.Query(context.Background(),
|
||||
`SELECT h.timestamp, h.song_name, h.album_name, h.ms_played, h.platform,
|
||||
(SELECT name FROM artists WHERE id = h.artist_id) as artist_name
|
||||
FROM history h WHERE h.user_id = $1 AND h.song_id = $2
|
||||
ORDER BY h.timestamp DESC LIMIT $3 OFFSET $4`,
|
||||
userId, songId, limit, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var entries []ScrobbleEntry
|
||||
for rows.Next() {
|
||||
var e ScrobbleEntry
|
||||
err := rows.Scan(&e.Timestamp, &e.SongName, &e.AlbumName, &e.MsPlayed, &e.Platform, &e.ArtistName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entries = append(entries, e)
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
type ScrobbleEntry struct {
|
||||
Timestamp time.Time
|
||||
SongName string
|
||||
ArtistName string
|
||||
AlbumName string
|
||||
MsPlayed int
|
||||
Platform string
|
||||
}
|
||||
|
||||
func MigrateHistoryEntities() error {
|
||||
rows, err := Pool.Query(context.Background(),
|
||||
`SELECT DISTINCT h.user_id, h.artist, h.song_name, h.album_name
|
||||
FROM history h
|
||||
WHERE h.artist_id IS NULL OR h.song_id IS NULL`)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error fetching history for migration: %v\n", err)
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
type UniqueEntity struct {
|
||||
UserId int
|
||||
Artist string
|
||||
SongName string
|
||||
Album string
|
||||
}
|
||||
|
||||
var entities []UniqueEntity
|
||||
seen := make(map[string]bool)
|
||||
for rows.Next() {
|
||||
var r UniqueEntity
|
||||
if err := rows.Scan(&r.UserId, &r.Artist, &r.SongName, &r.Album); err != nil {
|
||||
continue
|
||||
}
|
||||
key := fmt.Sprintf("%d-%s-%s-%s", r.UserId, r.Artist, r.SongName, r.Album)
|
||||
if !seen[key] {
|
||||
seen[key] = true
|
||||
entities = append(entities, r)
|
||||
}
|
||||
}
|
||||
|
||||
artistIds := make(map[string]int)
|
||||
albumIds := make(map[string]int)
|
||||
|
||||
for _, e := range entities {
|
||||
if e.Artist == "" {
|
||||
continue
|
||||
}
|
||||
key := fmt.Sprintf("%d-%s", e.UserId, e.Artist)
|
||||
if _, exists := artistIds[key]; !exists {
|
||||
id, _, err := GetOrCreateArtist(e.UserId, e.Artist)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
artistIds[key] = id
|
||||
}
|
||||
}
|
||||
|
||||
for _, e := range entities {
|
||||
if e.Album == "" || e.Artist == "" {
|
||||
continue
|
||||
}
|
||||
artistKey := fmt.Sprintf("%d-%s", e.UserId, e.Artist)
|
||||
artistId, ok := artistIds[artistKey]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
albumKey := fmt.Sprintf("%d-%s-%d", e.UserId, e.Album, artistId)
|
||||
if _, exists := albumIds[albumKey]; !exists {
|
||||
id, _, err := GetOrCreateAlbum(e.UserId, e.Album, artistId)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
albumIds[albumKey] = id
|
||||
}
|
||||
}
|
||||
|
||||
for _, e := range entities {
|
||||
if e.SongName == "" || e.Artist == "" {
|
||||
continue
|
||||
}
|
||||
artistKey := fmt.Sprintf("%d-%s", e.UserId, e.Artist)
|
||||
artistId, ok := artistIds[artistKey]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
var albumId int
|
||||
if e.Album != "" {
|
||||
albumKey := fmt.Sprintf("%d-%s-%d", e.UserId, e.Album, artistId)
|
||||
albumId = albumIds[albumKey]
|
||||
}
|
||||
|
||||
songId, _, err := GetOrCreateSong(e.UserId, e.SongName, artistId, albumId)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = Pool.Exec(context.Background(),
|
||||
`UPDATE history SET artist_id = $1, song_id = $2
|
||||
WHERE user_id = $3 AND artist = $4 AND song_name = $5
|
||||
AND (artist_id IS NULL OR song_id IS NULL)`,
|
||||
artistId, songId, e.UserId, e.Artist, e.SongName)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error updating history entity IDs: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetAlbumStats(userId, albumId int) (int, error) {
|
||||
var count int
|
||||
err := Pool.QueryRow(context.Background(),
|
||||
`SELECT COUNT(*) FROM history h
|
||||
JOIN songs s ON h.song_id = s.id
|
||||
WHERE h.user_id = $1 AND s.album_id = $2`,
|
||||
userId, albumId).Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
func GetHistoryForAlbum(userId, albumId int, limit, offset int) ([]ScrobbleEntry, error) {
|
||||
rows, err := Pool.Query(context.Background(),
|
||||
`SELECT h.timestamp, h.song_name, h.album_name, h.ms_played, h.platform,
|
||||
(SELECT name FROM artists WHERE id = h.artist_id) as artist_name
|
||||
FROM history h
|
||||
JOIN songs s ON h.song_id = s.id
|
||||
WHERE h.user_id = $1 AND s.album_id = $2
|
||||
ORDER BY h.timestamp DESC LIMIT $3 OFFSET $4`,
|
||||
userId, albumId, limit, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var entries []ScrobbleEntry
|
||||
for rows.Next() {
|
||||
var e ScrobbleEntry
|
||||
err := rows.Scan(&e.Timestamp, &e.SongName, &e.AlbumName, &e.MsPlayed, &e.Platform, &e.ArtistName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entries = append(entries, e)
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
Reference in New Issue
Block a user