Rewrites the internal/api comment blocks to rule 32: task, milestone and rule ids gone, the stories behind them restated as the constraint they left, stale references corrected (the stream secret is wired from config, the timezone handler already refreshes the scheduler, the Android app is the native client, the design docs cited are not in the repo). Also rewraps comment lines over 80 columns left by the earlier batches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
125 lines
3.8 KiB
Go
125 lines
3.8 KiB
Go
// Small helpers for posting live events to the SSE bus. Producers call
|
|
// these from their handler success paths so the wire shape stays in one
|
|
// place. Each helper is a no-op when h.eventbus is nil (tests that don't
|
|
// construct a bus); production always has one.
|
|
|
|
package api
|
|
|
|
import (
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/eventbus"
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
// publishLikeEvent broadcasts a track/album/artist like or unlike. The
|
|
// event is scoped to the user (so only their own clients invalidate); the
|
|
// payload includes the entity_type and entity_id so the dispatcher can
|
|
// invalidate the right query keys.
|
|
func (h *handlers) publishLikeEvent(userID, entityID pgtype.UUID, entityType string, liked bool) {
|
|
if h.eventbus == nil {
|
|
return
|
|
}
|
|
kind := "track.liked"
|
|
if !liked {
|
|
kind = "track.unliked"
|
|
}
|
|
if entityType == "album" {
|
|
if liked {
|
|
kind = "album.liked"
|
|
} else {
|
|
kind = "album.unliked"
|
|
}
|
|
}
|
|
if entityType == "artist" {
|
|
if liked {
|
|
kind = "artist.liked"
|
|
} else {
|
|
kind = "artist.unliked"
|
|
}
|
|
}
|
|
h.eventbus.Publish(eventbus.Event{
|
|
Kind: kind,
|
|
UserID: uuidToString(userID),
|
|
Data: map[string]any{
|
|
"entity_type": entityType,
|
|
"entity_id": uuidToString(entityID),
|
|
},
|
|
})
|
|
}
|
|
|
|
// publishQuarantineEvent broadcasts a quarantine action. User-side
|
|
// flag/unflag are scoped to the user; admin-side resolve / delete-file
|
|
// are broadcast (empty UserID) because a single admin
|
|
// action can affect every user who'd flagged that track — every client
|
|
// invalidates and the non-affected ones no-op when the query result
|
|
// matches their cache.
|
|
func (h *handlers) publishQuarantineEvent(kind string, userID, trackID pgtype.UUID, broadcast bool) {
|
|
if h.eventbus == nil {
|
|
return
|
|
}
|
|
scope := ""
|
|
if !broadcast {
|
|
scope = uuidToString(userID)
|
|
}
|
|
h.eventbus.Publish(eventbus.Event{
|
|
Kind: kind,
|
|
UserID: scope,
|
|
Data: map[string]any{
|
|
"track_id": uuidToString(trackID),
|
|
},
|
|
})
|
|
}
|
|
|
|
// publishPlaylistEvent broadcasts a playlist mutation to the owner so
|
|
// their other clients invalidate the affected playlist (and playlist list)
|
|
// providers. Public-playlist subscribers are out of scope here — they'd
|
|
// need a separate "playlists.public_updated" broadcast, deferred until
|
|
// the multi-user case is exercised.
|
|
func (h *handlers) publishPlaylistEvent(kind string, ownerID, playlistID pgtype.UUID) {
|
|
if h.eventbus == nil {
|
|
return
|
|
}
|
|
h.eventbus.Publish(eventbus.Event{
|
|
Kind: kind,
|
|
UserID: uuidToString(ownerID),
|
|
Data: map[string]any{
|
|
"playlist_id": uuidToString(playlistID),
|
|
},
|
|
})
|
|
}
|
|
|
|
// publishSystemRebuilt notifies the owner's clients that their system
|
|
// playlists (and You-might-like rows) were regenerated, so they invalidate
|
|
// the home / system-playlist providers and a stale active queue can re-pull.
|
|
// Mirrors the daily scheduler's event; fired here from the manual refresh.
|
|
func (h *handlers) publishSystemRebuilt(userID pgtype.UUID) {
|
|
if h.eventbus == nil {
|
|
return
|
|
}
|
|
h.eventbus.Publish(eventbus.Event{
|
|
Kind: "playlist.system_rebuilt",
|
|
UserID: uuidToString(userID),
|
|
Data: map[string]any{},
|
|
})
|
|
}
|
|
|
|
// publishRequestStatusChanged broadcasts a request status flip to
|
|
// the request's original requester so their /requests page reflects the
|
|
// new state without manual refresh. Admin actors (approve / reject) still
|
|
// route to the requester's user_id, not the admin's — the original user
|
|
// is who needs to see the update.
|
|
func (h *handlers) publishRequestStatusChanged(row dbq.Request) {
|
|
if h.eventbus == nil {
|
|
return
|
|
}
|
|
h.eventbus.Publish(eventbus.Event{
|
|
Kind: "request.status_changed",
|
|
UserID: uuidToString(row.UserID),
|
|
Data: map[string]any{
|
|
"request_id": uuidToString(row.ID),
|
|
"status": string(row.Status),
|
|
"kind": string(row.Kind),
|
|
},
|
|
})
|
|
}
|