feat(server): scanner + DeleteTrackFile write library_changes
Wires sync.LogChange into the library mutation sites so /api/library/sync reflects upserts and deletes. Architectural pivot: LogChange's signature is now (ctx, dbq.DBTX, ...) so it works with both *pgxpool.Pool and pgx.Tx. The scanner doesn't run mutations in explicit transactions, so it pool-binds; delete.go matches. Tx-bound callers (likes/playlists in subsequent commits) keep atomicity. Also: sync.FormatUUID centralizes the pgtype.UUID → canonical string conversion that both the scanner and the sync handler need; library_sync.go now uses it instead of a local copy. Best-effort logging on scanner failures (Warn, don't fail the scan): a LogChange error after a successful upsert is rare and self-healing — the next scan that touches the entity re-emits the change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -168,7 +168,7 @@ func (h *handlers) hydrateUpserts(
|
||||
}
|
||||
}
|
||||
|
||||
userIDStr := uuidToString(userID)
|
||||
userIDStr := syncpkg.FormatUUID(userID)
|
||||
|
||||
if rows := ids[syncpkg.EntityLikeTrack]; len(rows) > 0 {
|
||||
out["like_track"] = scopedLikeRows(rows, userIDStr, "track_id")
|
||||
@@ -188,7 +188,7 @@ func (h *handlers) hydrateUpserts(
|
||||
}
|
||||
for _, p := range playlists {
|
||||
// Filter: only return playlists owned by this user OR public ones
|
||||
if uuidToString(p.UserID) != userIDStr && !p.IsPublic {
|
||||
if syncpkg.FormatUUID(p.UserID) != userIDStr && !p.IsPublic {
|
||||
continue
|
||||
}
|
||||
b, _ := json.Marshal(p)
|
||||
@@ -249,32 +249,6 @@ func stringsToUUIDs(strs []string) []pgtype.UUID {
|
||||
return out
|
||||
}
|
||||
|
||||
// uuidToString renders a pgtype.UUID as the canonical 8-4-4-4-12 hex
|
||||
// form. Returns "" if the UUID is not valid.
|
||||
func uuidToString(u pgtype.UUID) string {
|
||||
if !u.Valid {
|
||||
return ""
|
||||
}
|
||||
b := u.Bytes
|
||||
return formatUUIDBytes(b)
|
||||
}
|
||||
|
||||
func formatUUIDBytes(b [16]byte) string {
|
||||
const hex = "0123456789abcdef"
|
||||
out := make([]byte, 36)
|
||||
pos := 0
|
||||
for i := 0; i < 16; i++ {
|
||||
if i == 4 || i == 6 || i == 8 || i == 10 {
|
||||
out[pos] = '-'
|
||||
pos++
|
||||
}
|
||||
out[pos] = hex[b[i]>>4]
|
||||
out[pos+1] = hex[b[i]&0x0f]
|
||||
pos += 2
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
|
||||
// splitOnce returns [before, after] split at the first occurrence of sep,
|
||||
// or [s] if sep doesn't appear. Used for composite-key parsing.
|
||||
func splitOnce(s, sep string) []string {
|
||||
|
||||
Reference in New Issue
Block a user