Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/genesis-writer/entities_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ func (w *Writer) writeCommentPass(
EntityID: c.EntityID,
EntityType: c.EntityType,
TrackTimestampS: c.TrackTimestampS,
CreatedAt: c.CreatedAt.UTC().Format(time.RFC3339),
CreatedAt: c.CreatedAt.UTC().Format(time.RFC3339Nano),
IsMembersOnly: c.IsMembersOnly,
VideoURL: deref(c.VideoURL),
IsEdited: c.IsEdited,
Expand Down Expand Up @@ -194,7 +194,7 @@ func (w *Writer) writeCommentReactions(ctx context.Context) error {
return cr, err
},
func(ctx context.Context, cr commentReaction) error {
metaJSON, err := json.Marshal(createdAtMeta{CreatedAt: cr.createdAt.UTC().Format(time.RFC3339)})
metaJSON, err := json.Marshal(createdAtMeta{CreatedAt: cr.createdAt.UTC().Format(time.RFC3339Nano)})
if err != nil {
return fmt.Errorf("marshal comment reaction metadata: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/genesis-writer/entities_comment_pin.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func (w *Writer) writeCommentPins(ctx context.Context) error {
func(ctx context.Context, p sourceCommentPin) error {
metaJSON, err := json.Marshal(commentPinMetadata{
EntityID: p.TrackID,
CreatedAt: p.PinnedAt.UTC().Format(time.RFC3339),
CreatedAt: p.PinnedAt.UTC().Format(time.RFC3339Nano),
})
if err != nil {
return fmt.Errorf("marshal comment pin metadata for track %d: %w", p.TrackID, err)
Expand Down
2 changes: 1 addition & 1 deletion cmd/genesis-writer/entities_dashboard_wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (w *Writer) writeDashboardWalletUsers(ctx context.Context) error {
func(ctx context.Context, d sourceDashboardWalletUser) error {
metaJSON, err := json.Marshal(dashboardWalletMetadata{
Wallet: d.Wallet,
CreatedAt: d.CreatedAt.UTC().Format(time.RFC3339),
CreatedAt: d.CreatedAt.UTC().Format(time.RFC3339Nano),
IsDelete: d.IsDelete,
})
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions cmd/genesis-writer/entities_developer_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func (w *Writer) writeDeveloperApps(ctx context.Context) error {
Description: deref(d.Description),
ImageURL: deref(d.ImageURL),
IsPersonalAccess: d.IsPersonalAccess,
CreatedAt: d.CreatedAt.UTC().Format(time.RFC3339),
CreatedAt: d.CreatedAt.UTC().Format(time.RFC3339Nano),
}
metaJSON, err := json.Marshal(meta)
if err != nil {
Expand Down Expand Up @@ -148,7 +148,7 @@ func (w *Writer) writeGrants(ctx context.Context) error {
IsRevoked: g.IsRevoked,
IsApproved: g.IsApproved,
GranteeAddress: g.GranteeAddress,
CreatedAt: g.CreatedAt.UTC().Format(time.RFC3339),
CreatedAt: g.CreatedAt.UTC().Format(time.RFC3339Nano),
})
if err != nil {
return fmt.Errorf("marshal grant metadata: %w", err)
Expand Down
4 changes: 2 additions & 2 deletions cmd/genesis-writer/entities_email.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func (w *Writer) writeEncryptedEmails(ctx context.Context) error {
EmailOwnerUserID: e.EmailOwnerUserID,
EncryptedEmail: e.EncryptedEmail,
AccessGrants: grants,
CreatedAt: e.CreatedAt.UTC().Format(time.RFC3339),
CreatedAt: e.CreatedAt.UTC().Format(time.RFC3339Nano),
}
metaJSON, err := json.Marshal(meta)
if err != nil {
Expand Down Expand Up @@ -165,7 +165,7 @@ func (w *Writer) writeEmailAccess(ctx context.Context) error {
GrantorUserID: ea.grantorUserID,
EncryptedKey: ea.encryptedKey,
}},
CreatedAt: ea.createdAt.UTC().Format(time.RFC3339),
CreatedAt: ea.createdAt.UTC().Format(time.RFC3339Nano),
})
if err != nil {
return fmt.Errorf("marshal email access metadata: %w", err)
Expand Down
4 changes: 2 additions & 2 deletions cmd/genesis-writer/entities_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ func (w *Writer) writeEvents(ctx context.Context) error {
meta := eventMetadata{
EventType: e.EventType,
EntityID: e.EntityID,
CreatedAt: e.CreatedAt.UTC().Format(time.RFC3339),
CreatedAt: e.CreatedAt.UTC().Format(time.RFC3339Nano),
}
if e.EntityType != nil {
meta.EntityType = *e.EntityType
}
if e.EndDate != nil {
meta.EndDate = e.EndDate.UTC().Format(time.RFC3339)
meta.EndDate = e.EndDate.UTC().Format(time.RFC3339Nano)
}
meta.EventData = unmarshalJSONB(e.EventData)

Expand Down
6 changes: 3 additions & 3 deletions cmd/genesis-writer/entities_playlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ func preloadRemovedPlaylistTracks(ctx context.Context, db *pgxpool.Pool) (map[in
if err := rows.Scan(&playlistID, &r.TrackID, &createdAt, &updatedAt); err != nil {
return nil, err
}
r.CreatedAt = createdAt.UTC().Format(time.RFC3339)
r.UpdatedAt = updatedAt.UTC().Format(time.RFC3339)
r.CreatedAt = createdAt.UTC().Format(time.RFC3339Nano)
r.UpdatedAt = updatedAt.UTC().Format(time.RFC3339Nano)
m[playlistID] = append(m[playlistID], r)
}
return m, rows.Err()
Expand Down Expand Up @@ -180,7 +180,7 @@ func (w *Writer) writePlaylists(ctx context.Context) error {
},
func(ctx context.Context, p sourcePlaylist) error {
inner := playlistMetadataInner{
CreatedAt: p.CreatedAt.UTC().Format(time.RFC3339),
CreatedAt: p.CreatedAt.UTC().Format(time.RFC3339Nano),
PlaylistName: deref(p.PlaylistName),
Description: deref(p.Description),
IsAlbum: p.IsAlbum,
Expand Down
2 changes: 1 addition & 1 deletion cmd/genesis-writer/entities_social.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type repostMeta struct {
}

func fmtCreatedAt(t time.Time) string {
return t.UTC().Format(time.RFC3339)
return t.UTC().Format(time.RFC3339Nano)
}

// --- Follows ---
Expand Down
9 changes: 6 additions & 3 deletions cmd/genesis-writer/entities_track.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ type sourceTrack struct {
// a column the writer forgets to carry is invisible in the output otherwise.
func buildTrackMetadata(t sourceTrack, collaborators []int64) trackMetadataInner {
inner := trackMetadataInner{
CreatedAt: t.CreatedAt.UTC().Format(time.RFC3339),
CreatedAt: t.CreatedAt.UTC().Format(time.RFC3339Nano),
OwnerID: t.OwnerID,
Title: deref(t.Title),
Description: deref(t.Description),
Expand Down Expand Up @@ -223,7 +223,10 @@ func fmtReleaseDate(t *time.Time) string {
if t == nil {
return ""
}
return t.UTC().Format(time.RFC3339)
// RFC3339Nano, not RFC3339: the plain layout has no fractional-second
// component, which silently rounded 26,129 tracks whose release_date
// carries microseconds down to the whole second.
return t.UTC().Format(time.RFC3339Nano)
}

func (w *Writer) writeTracks(ctx context.Context) error {
Expand Down Expand Up @@ -367,7 +370,7 @@ func (w *Writer) writeTrackDownloads(ctx context.Context) error {
City: deref(d.City),
Region: deref(d.Region),
Country: deref(d.Country),
CreatedAt: d.CreatedAt.UTC().Format(time.RFC3339),
CreatedAt: d.CreatedAt.UTC().Format(time.RFC3339Nano),
}
metaJSON, err := json.Marshal(meta)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/genesis-writer/entities_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func (w *Writer) writeUsers(ctx context.Context) error {
},
func(ctx context.Context, u sourceUser) error {
meta := userMetadata{
CreatedAt: u.CreatedAt.UTC().Format(time.RFC3339),
CreatedAt: u.CreatedAt.UTC().Format(time.RFC3339Nano),
Name: deref(u.Name),
Handle: deref(u.Handle),
Bio: deref(u.Bio),
Expand Down
63 changes: 63 additions & 0 deletions cmd/genesis-writer/release_date_format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,66 @@ func TestReleaseDateIsEmittedInAnAcceptedLayout(t *testing.T) {
t.Errorf("nil release_date = %q, want empty so omitempty drops it", fmtReleaseDate(nil))
}
}

// RFC3339 has no fractional-second component, so formatting with it rounded
// every sub-second release_date down to the whole second. The indexer accepts
// RFC3339Nano, and a value with no fraction still formats identically, so
// nothing about the layout above changes.
func TestReleaseDateKeepsSubSecondPrecision(t *testing.T) {
rd := time.Date(2026, 2, 2, 15, 53, 12, 50585000, time.UTC)
got := fmtReleaseDate(&rd)

if got != "2026-02-02T15:53:12.050585Z" {
t.Errorf("fmtReleaseDate = %q, want %q -- microseconds were dropped", got, "2026-02-02T15:53:12.050585Z")
}

parsed, err := time.Parse(time.RFC3339Nano, got)
if err != nil {
t.Fatalf("indexer cannot parse %q: %v", got, err)
}
if !parsed.Equal(rd) {
t.Errorf("round-tripped to %v, want %v", parsed, rd)
}

// A whole-second value must still come out in the plain layout, so this
// change is invisible to the 99% of rows that carry no fraction.
whole := time.Date(2026, 9, 6, 22, 6, 0, 0, time.UTC)
if got := fmtReleaseDate(&whole); got != "2026-09-06T22:06:00Z" {
t.Errorf("whole-second release_date = %q, want %q", got, "2026-09-06T22:06:00Z")
}
}

// created_at went out through the same non-fractional layout, and it is not a
// cosmetic field: the indexer replays each migrated row as of its created_at
// (migrationBlockTime), and parity keys track_downloads on it. A full-snapshot
// run truncated every one of the 78,032 track_downloads rows, plus ~2.9M
// social rows, to the whole second.
//
// parseMigrationTimestamp tries RFC3339Nano before RFC3339, and Go's parser
// accepts a fractional second against a layout that lacks one, so emitting the
// longer form is safe for every reader.
func TestEmittedCreatedAtKeepsSubSecondPrecision(t *testing.T) {
src := sourceTrack{
TrackID: 1,
OwnerID: 2,
CreatedAt: time.Date(2024, 12, 1, 12, 30, 13, 244308000, time.UTC),
}

got := buildTrackMetadata(src, nil).CreatedAt
if got != "2024-12-01T12:30:13.244308Z" {
t.Errorf("created_at = %q, want %q -- microseconds were dropped", got, "2024-12-01T12:30:13.244308Z")
}

parsed, err := time.Parse(time.RFC3339Nano, got)
if err != nil {
t.Fatalf("indexer cannot parse %q: %v", got, err)
}
if !parsed.Equal(src.CreatedAt) {
t.Errorf("round-tripped to %v, want %v", parsed, src.CreatedAt)
}

// The stricter RFC3339 layout used by event_create.go must still accept it.
if _, err := time.Parse(time.RFC3339, got); err != nil {
t.Errorf("plain RFC3339 parse rejected %q: %v", got, err)
}
}
7 changes: 5 additions & 2 deletions cmd/genesis-writer/timestamps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ import (
// worth guarding is a *new* emission site added later without .UTC(), and only
// scanning the package catches that.
func TestAllEmittedTimestampsAreUTC(t *testing.T) {
// A .Format(time.RFC3339) whose receiver does not end in .UTC().
unnormalized := regexp.MustCompile(`(\.UTC\(\))?\.Format\(time\.RFC3339\)`)
// A .Format(time.RFC3339Nano) whose receiver does not end in .UTC().
// RFC3339 is matched too so that reintroducing the non-fractional layout
// -- which silently rounded ~3M created_at values to the whole second --
// is still covered by the .UTC() half of this guard.
unnormalized := regexp.MustCompile(`(\.UTC\(\))?\.Format\(time\.RFC3339(Nano)?\)`)

entries, err := os.ReadDir(".")
if err != nil {
Expand Down