diff --git a/db/entities.go b/db/entities.go index 6829863..0d271e8 100644 --- a/db/entities.go +++ b/db/entities.go @@ -794,6 +794,7 @@ func GetHistoryForSong(userId, songId int, limit, offset int) ([]ScrobbleEntry, } type ScrobbleEntry struct { + Id int Timestamp time.Time SongName string ArtistName string @@ -958,7 +959,7 @@ func GetHistoryForSongs(userId int, songIds []int, limit, offset int) ([]Scrobbl return []ScrobbleEntry{}, nil } rows, err := Pool.Query(context.Background(), - `SELECT h.timestamp, h.song_name, h.album_name, h.ms_played, h.platform, + `SELECT h.id, 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, h.artist_ids FROM history h WHERE h.user_id = $1 AND h.song_id = ANY($2) @@ -972,7 +973,7 @@ func GetHistoryForSongs(userId int, songIds []int, limit, offset int) ([]Scrobbl var entries []ScrobbleEntry for rows.Next() { var e ScrobbleEntry - err := rows.Scan(&e.Timestamp, &e.SongName, &e.AlbumName, &e.MsPlayed, &e.Platform, &e.ArtistName, &e.ArtistIds) + err := rows.Scan(&e.Id, &e.Timestamp, &e.SongName, &e.AlbumName, &e.MsPlayed, &e.Platform, &e.ArtistName, &e.ArtistIds) if err != nil { return nil, err } @@ -980,3 +981,17 @@ func GetHistoryForSongs(userId int, songIds []int, limit, offset int) ([]Scrobbl } return entries, nil } + +func DeleteHistoryByIds(userId int, ids []int) error { + if len(ids) == 0 { + return nil + } + _, err := Pool.Exec(context.Background(), + `DELETE FROM history WHERE user_id = $1 AND id = ANY($2)`, + userId, ids) + if err != nil { + fmt.Fprintf(os.Stderr, "Error deleting history: %v\n", err) + return err + } + return nil +} diff --git a/templates/song.gohtml b/templates/song.gohtml index af9c483..d0340cc 100644 --- a/templates/song.gohtml +++ b/templates/song.gohtml @@ -5,6 +5,7 @@ {{.Song.Title}} {{if eq .LoggedInUsername .Username}} + {{end}} {{if .ArtistNames}} @@ -23,9 +24,16 @@
-

Scrobbles

+
+

Scrobbles

+ +
+ @@ -34,6 +42,9 @@ {{$username := .Username}} {{range .Times}} +
Artist Title Album
{{- $artistNames := getArtistNames .ArtistIds}} {{- range $i, $name := $artistNames}}{{if $i}}, {{end}}{{$name}}{{end}} @@ -68,4 +79,67 @@ {{end}} + + {{end}} diff --git a/web/entity.go b/web/entity.go index e24a17c..8798665 100644 --- a/web/entity.go +++ b/web/entity.go @@ -1002,3 +1002,36 @@ func imageUploadHandler() http.HandlerFunc { }) } } + +func deleteScrobbleHandler() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + username := getLoggedInUsername(r) + if username == "" { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + userId, err := getUserIdByUsername(r.Context(), username) + if err != nil { + http.Error(w, "User not found", http.StatusNotFound) + return + } + + var ids []int + if err := json.NewDecoder(r.Body).Decode(&ids); err != nil { + fmt.Fprintf(os.Stderr, "Error decoding request: %v\n", err) + http.Error(w, "Invalid request", http.StatusBadRequest) + return + } + + err = db.DeleteHistoryByIds(userId, ids) + if err != nil { + fmt.Fprintf(os.Stderr, "Error deleting scrobbles: %v\n", err) + http.Error(w, "Error deleting scrobbles", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{"status": "ok"}) + } +} diff --git a/web/web.go b/web/web.go index 583badd..6871d61 100644 --- a/web/web.go +++ b/web/web.go @@ -120,6 +120,7 @@ func Start() { r.Patch("/api/artist/{id}/batch", artistBatchEditHandler()) r.Patch("/api/song/{id}/batch", songBatchEditHandler()) r.Patch("/api/album/{id}/batch", albumBatchEditHandler()) + r.Post("/api/scrobble/delete", deleteScrobbleHandler()) r.Post("/api/upload/image", imageUploadHandler()) r.Get("/search", searchHandler()) r.Get("/import", importPageHandler())