package sync import ( "context" "fmt" "github.com/jackc/pgx/v5/pgtype" "git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq" ) // LogChange writes a row to library_changes via the supplied DBTX. dbq.DBTX // is satisfied by both *pgxpool.Pool and pgx.Tx, so callers can opt into // transactional safety where they already hold a tx, or pool-bind for // best-effort logging when no tx is available. // // Callers SHOULD pass a tx that owns the underlying mutation — the change // row and the mutation then commit or roll back together. When pool-bound, // the scanner-style "log after success" pattern keeps the log consistent // with reality at the cost of a tiny race window: if the LogChange call // itself fails after the mutation succeeded, that mutation won't appear in // the change log until the entity is touched again. // // entityID is the string form of the row's primary key. For composite-key // entities (likes, playlist_tracks), use EncodeLikeID / EncodePlaylistTrackID // to produce stable strings. For pgtype.UUID values, use FormatUUID. func LogChange(ctx context.Context, dbtx dbq.DBTX, entityType EntityType, entityID string, op Op) error { q := dbq.New(dbtx) if err := q.InsertLibraryChange(ctx, dbq.InsertLibraryChangeParams{ EntityType: string(entityType), EntityID: entityID, Op: string(op), }); err != nil { return fmt.Errorf("sync.LogChange(%s, %s, %s): %w", entityType, entityID, op, err) } return nil } // FormatUUID renders a pgtype.UUID as the canonical 8-4-4-4-12 hex form. // Returns "" if the UUID is not valid. func FormatUUID(u pgtype.UUID) string { if !u.Valid { return "" } return formatUUIDBytes(u.Bytes) } 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) } // EncodeLikeID joins a user UUID and an entity UUID into the stable // composite identifier used for like_* rows in library_changes. Both // sides are UUID strings so no escaping is needed. func EncodeLikeID(userID, entityID string) string { return userID + ":" + entityID } // EncodePlaylistTrackID joins a playlist UUID and a track UUID into the // stable composite identifier used for playlist_track rows. func EncodePlaylistTrackID(playlistID, trackID string) string { return playlistID + ":" + trackID }