feat(server): like/unlike handlers write library_changes
All 6 like/unlike sites (track/album/artist × like/unlike) now emit a library_changes row via the shared logLikeChange helper. Best-effort: LogChange failures Warn but don't fail the HTTP response — a missed log row is recovered at the next mutation on the same entity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,8 +10,21 @@ import (
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/apierror"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
||||
"git.fabledsword.com/bvandeusen/minstrel/internal/playevents"
|
||||
syncpkg "git.fabledsword.com/bvandeusen/minstrel/internal/sync"
|
||||
)
|
||||
|
||||
// logLikeChange writes a library_changes row for a like / unlike. Best-
|
||||
// effort: a failure here means the next sync may miss the row, but the
|
||||
// HTTP response still succeeds. Cheap consequence — operator's other
|
||||
// device will see the like once it manually pulls or once a future
|
||||
// like/unlike on the same entity emits a fresh log row.
|
||||
func (h *handlers) logLikeChange(r *http.Request, et syncpkg.EntityType, userID, entityID pgtype.UUID, op syncpkg.Op) {
|
||||
if err := syncpkg.LogChange(r.Context(), h.pool, et,
|
||||
syncpkg.EncodeLikeID(syncpkg.FormatUUID(userID), syncpkg.FormatUUID(entityID)), op); err != nil {
|
||||
h.logger.Warn("api: LogChange like", "kind", et, "user", userID, "entity", entityID, "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
type likedIDsResponse struct {
|
||||
TrackIDs []string `json:"track_ids"`
|
||||
AlbumIDs []string `json:"album_ids"`
|
||||
@@ -46,6 +59,7 @@ func (h *handlers) handleLikeTrack(w http.ResponseWriter, r *http.Request) {
|
||||
if rows == 1 {
|
||||
_ = playevents.CaptureContextualLikeIfPlaying(r.Context(), q, user.ID, id, h.logger)
|
||||
}
|
||||
h.logLikeChange(r, syncpkg.EntityLikeTrack, user.ID, id, syncpkg.OpUpsert)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
@@ -68,6 +82,7 @@ func (h *handlers) handleUnlikeTrack(w http.ResponseWriter, r *http.Request) {
|
||||
h.logger.Error("api: soft-delete contextual_likes", "err", err)
|
||||
// Don't fail the response — soft-delete is best-effort.
|
||||
}
|
||||
h.logLikeChange(r, syncpkg.EntityLikeTrack, user.ID, id, syncpkg.OpDelete)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
@@ -95,6 +110,7 @@ func (h *handlers) handleLikeAlbum(w http.ResponseWriter, r *http.Request) {
|
||||
writeErr(w, apierror.InternalMsg("insert failed", err))
|
||||
return
|
||||
}
|
||||
h.logLikeChange(r, syncpkg.EntityLikeAlbum, user.ID, id, syncpkg.OpUpsert)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
@@ -112,6 +128,7 @@ func (h *handlers) handleUnlikeAlbum(w http.ResponseWriter, r *http.Request) {
|
||||
writeErr(w, apierror.InternalMsg("delete failed", err))
|
||||
return
|
||||
}
|
||||
h.logLikeChange(r, syncpkg.EntityLikeAlbum, user.ID, id, syncpkg.OpDelete)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
@@ -139,6 +156,7 @@ func (h *handlers) handleLikeArtist(w http.ResponseWriter, r *http.Request) {
|
||||
writeErr(w, apierror.InternalMsg("insert failed", err))
|
||||
return
|
||||
}
|
||||
h.logLikeChange(r, syncpkg.EntityLikeArtist, user.ID, id, syncpkg.OpUpsert)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
@@ -156,6 +174,7 @@ func (h *handlers) handleUnlikeArtist(w http.ResponseWriter, r *http.Request) {
|
||||
writeErr(w, apierror.InternalMsg("delete failed", err))
|
||||
return
|
||||
}
|
||||
h.logLikeChange(r, syncpkg.EntityLikeArtist, user.ID, id, syncpkg.OpDelete)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user