diff --git a/api/dbv1/full_tracks.go b/api/dbv1/full_tracks.go index e20e5018..39bebe5d 100644 --- a/api/dbv1/full_tracks.go +++ b/api/dbv1/full_tracks.go @@ -2,6 +2,7 @@ package dbv1 import ( "context" + "encoding/json" "fmt" "bridgerton.audius.co/trashid" @@ -17,12 +18,8 @@ type FullTrack struct { UserID string `json:"user_id"` User FullUser `json:"user"` - // hide these GetTrackRow fields - FolloweeFavoriteIds any `json:"followee_favorite_ids,omitempty"` - FolloweeRepostIds any `json:"followee_repost_ids,omitempty"` - - FolloweeFavorites []FullUser `json:"followee_favorites"` - FolloweeReposts []FullUser `json:"followee_reposts"` + FolloweeReposts []*FolloweeRepost `json:"followee_reposts"` + FolloweeFavorites []*FolloweeFavorite `json:"followee_favorites"` } func (q *Queries) FullTracks(ctx context.Context, arg GetTracksParams) ([]FullTrack, error) { @@ -34,12 +31,6 @@ func (q *Queries) FullTracks(ctx context.Context, arg GetTracksParams) ([]FullTr userIds := []int32{} for _, track := range rawTracks { userIds = append(userIds, track.UserID) - for _, id := range track.FolloweeFavoriteIds { - userIds = append(userIds, id) - } - for _, id := range track.FolloweeRepostIds { - userIds = append(userIds, id) - } } users, err := q.FullUsers(ctx, GetUsersParams{ @@ -67,17 +58,20 @@ func (q *Queries) FullTracks(ctx context.Context, arg GetTracksParams) ([]FullTr continue } - followeeFavorites := []FullUser{} - for _, id := range track.FolloweeFavoriteIds { - if user, ok := userMap[id]; ok { - followeeFavorites = append(followeeFavorites, user) + // re-encode ids for followee_favorites + followee_reposts + var followeeReposts []*FolloweeRepost + if err = json.Unmarshal(track.FolloweeReposts, &followeeReposts); err == nil { + for _, r := range followeeReposts { + r.RepostItemId = trashid.StringEncode(r.RepostItemId) + r.UserId = trashid.StringEncode(r.UserId) } } - followeeReposts := []FullUser{} - for _, id := range track.FolloweeFavoriteIds { - if user, ok := userMap[id]; ok { - followeeReposts = append(followeeReposts, user) + var followeeFavorites []*FolloweeFavorite + if err = json.Unmarshal(track.FolloweeFavorites, &followeeFavorites); err == nil { + for _, r := range followeeFavorites { + r.FavoriteItemId = trashid.StringEncode(r.FavoriteItemId) + r.UserId = trashid.StringEncode(r.UserId) } } diff --git a/api/dbv1/get_tracks.sql.go b/api/dbv1/get_tracks.sql.go index 32ce57a3..1fc92595 100644 --- a/api/dbv1/get_tracks.sql.go +++ b/api/dbv1/get_tracks.sql.go @@ -79,33 +79,50 @@ SELECT is_scheduled_release, is_unlisted, - ARRAY( - SELECT user_id - FROM saves - JOIN follows ON followee_user_id = saves.user_id AND follower_user_id = $1 - JOIN aggregate_user USING (user_id) - -- todo: join users, filter out deactivated - WHERE $1 > 0 - AND save_item_id = t.track_id - AND save_type = 'track' - AND saves.is_delete = false - ORDER BY follower_count DESC - LIMIT 10 - )::int[] as followee_favorite_ids, + ( + SELECT json_agg( + json_build_object( + 'user_id', r.user_id::text, + 'repost_item_id', repost_item_id::text, -- this is redundant + 'repost_type', 'RepostType.track', -- some sqlalchemy bs + 'created_at', r.created_at -- this is not actually present in python response? + ) + ) + FROM ( + SELECT user_id, repost_item_id, reposts.created_at + FROM reposts + JOIN follows ON followee_user_id = reposts.user_id AND follower_user_id = $1 + JOIN aggregate_user USING (user_id) + WHERE repost_item_id = t.track_id + AND repost_type = 'track' + AND reposts.is_delete = false + ORDER BY follower_count DESC + LIMIT 3 + ) r + )::jsonb as followee_reposts, + + ( + SELECT json_agg( + json_build_object( + 'user_id', r.user_id::text, + 'favorite_item_id', r.save_item_id::text, -- this is redundant + 'favorite_type', 'SaveType.track', -- some sqlalchemy bs + 'created_at', r.created_at -- this is not actually present in python response? + ) + ) + FROM ( + SELECT user_id, save_item_id, saves.created_at + FROM saves + JOIN follows ON followee_user_id = saves.user_id AND follower_user_id = $1 + JOIN aggregate_user USING (user_id) + WHERE save_item_id = t.track_id + AND save_type = 'track' + AND saves.is_delete = false + ORDER BY follower_count DESC + LIMIT 3 + ) r + )::jsonb as followee_favorites, - ARRAY( - SELECT user_id - FROM reposts - JOIN follows ON followee_user_id = reposts.user_id AND follower_user_id = $1 - JOIN aggregate_user USING (user_id) - -- todo: join users, filter out deactivated - WHERE $1 > 0 - AND repost_item_id = t.track_id - AND repost_type = 'track' - AND reposts.is_delete = false - ORDER BY follower_count DESC - LIMIT 10 - )::int[] as followee_repost_ids, -- followee_favorites, -- route_id, @@ -207,8 +224,8 @@ type GetTracksRow struct { HasCurrentUserSaved bool `json:"has_current_user_saved"` IsScheduledRelease bool `json:"is_scheduled_release"` IsUnlisted bool `json:"is_unlisted"` - FolloweeFavoriteIds []int32 `json:"followee_favorite_ids"` - FolloweeRepostIds []int32 `json:"followee_repost_ids"` + FolloweeReposts json.RawMessage `json:"followee_reposts"` + FolloweeFavorites json.RawMessage `json:"followee_favorites"` StemOf []byte `json:"stem_of"` UpdatedAt pgtype.Timestamp `json:"updated_at"` UserID int32 `json:"user_id"` @@ -292,8 +309,8 @@ func (q *Queries) GetTracks(ctx context.Context, arg GetTracksParams) ([]GetTrac &i.HasCurrentUserSaved, &i.IsScheduledRelease, &i.IsUnlisted, - &i.FolloweeFavoriteIds, - &i.FolloweeRepostIds, + &i.FolloweeReposts, + &i.FolloweeFavorites, &i.StemOf, &i.UpdatedAt, &i.UserID, diff --git a/api/dbv1/jsonb_types.go b/api/dbv1/jsonb_types.go index 6d2faced..6f121f26 100644 --- a/api/dbv1/jsonb_types.go +++ b/api/dbv1/jsonb_types.go @@ -32,3 +32,17 @@ type PlaylistContents struct { MetadataTime int64 `json:"metadata_time"` } `json:"track_ids"` } + +type FolloweeRepost struct { + RepostItemId string `json:"repost_item_id"` + RepostType string `json:"repost_type"` + UserId string `json:"user_id"` + CreatedAt string `json:"created_at"` +} + +type FolloweeFavorite struct { + FavoriteItemId string `json:"favorite_item_id"` + FavoriteType string `json:"favorite_type"` + UserId string `json:"user_id"` + CreatedAt string `json:"created_at"` +} diff --git a/api/dbv1/models.go b/api/dbv1/models.go index bbea1b62..35c17da1 100644 --- a/api/dbv1/models.go +++ b/api/dbv1/models.go @@ -600,7 +600,7 @@ type AggregateUserTip struct { type AlbumPriceHistory struct { PlaylistID int32 `json:"playlist_id"` - Splits []byte `json:"splits"` + Splits json.RawMessage `json:"splits"` TotalPriceCents int64 `json:"total_price_cents"` Blocknumber int32 `json:"blocknumber"` BlockTimestamp pgtype.Timestamp `json:"block_timestamp"` @@ -806,7 +806,7 @@ type Collectible struct { // User ID of the person who owns the collectibles UserID int32 `json:"user_id"` // Data about the collectibles - Data []byte `json:"data"` + Data json.RawMessage `json:"data"` // Blockhash of the most recent block that changed the collectibles data Blockhash string `json:"blockhash"` // Block number of the most recent block that changed the collectibles data @@ -923,7 +923,7 @@ type CoreTxDecoded struct { TxIndex int32 `json:"tx_index"` TxHash string `json:"tx_hash"` TxType string `json:"tx_type"` - TxData []byte `json:"tx_data"` + TxData json.RawMessage `json:"tx_data"` CreatedAt pgtype.Timestamptz `json:"created_at"` } @@ -1243,8 +1243,8 @@ type Repost struct { } type RevertBlock struct { - Blocknumber int32 `json:"blocknumber"` - PrevRecords []byte `json:"prev_records"` + Blocknumber int32 `json:"blocknumber"` + PrevRecords json.RawMessage `json:"prev_records"` } type RewardManagerTx struct { @@ -1299,7 +1299,7 @@ type RpcCursor struct { type RpcError struct { Sig string `json:"sig"` - RpcLogJson []byte `json:"rpc_log_json"` + RpcLogJson json.RawMessage `json:"rpc_log_json"` ErrorText string `json:"error_text"` ErrorCount int32 `json:"error_count"` LastAttempt pgtype.Timestamp `json:"last_attempt"` @@ -1466,7 +1466,7 @@ type Track struct { AudioUploadID pgtype.Text `json:"audio_upload_id"` PreviewStartSeconds pgtype.Float8 `json:"preview_start_seconds"` ReleaseDate pgtype.Timestamp `json:"release_date"` - TrackSegments []byte `json:"track_segments"` + TrackSegments json.RawMessage `json:"track_segments"` IsScheduledRelease bool `json:"is_scheduled_release"` IsDownloadable bool `json:"is_downloadable"` DownloadConditions UsageConditions `json:"download_conditions"` @@ -1484,7 +1484,7 @@ type Track struct { CopyrightLine json.RawMessage `json:"copyright_line"` ProducerCopyrightLine json.RawMessage `json:"producer_copyright_line"` ParentalWarningType pgtype.Text `json:"parental_warning_type"` - PlaylistsPreviouslyContainingTrack []byte `json:"playlists_previously_containing_track"` + PlaylistsPreviouslyContainingTrack json.RawMessage `json:"playlists_previously_containing_track"` AllowedApiKeys []string `json:"allowed_api_keys"` Bpm pgtype.Float8 `json:"bpm"` MusicalKey pgtype.Text `json:"musical_key"` @@ -1526,7 +1526,7 @@ type TrackDownload struct { type TrackPriceHistory struct { TrackID int32 `json:"track_id"` - Splits []byte `json:"splits"` + Splits json.RawMessage `json:"splits"` TotalPriceCents int64 `json:"total_price_cents"` Blocknumber int32 `json:"blocknumber"` BlockTimestamp pgtype.Timestamp `json:"block_timestamp"` @@ -1599,7 +1599,7 @@ type UsdcPurchase struct { Region pgtype.Text `json:"region"` Country pgtype.Text `json:"country"` Vendor pgtype.Text `json:"vendor"` - Splits []byte `json:"splits"` + Splits json.RawMessage `json:"splits"` } type UsdcTransactionsHistory struct { @@ -1731,8 +1731,8 @@ type UserEvent struct { } type UserListeningHistory struct { - UserID int32 `json:"user_id"` - ListeningHistory []byte `json:"listening_history"` + UserID int32 `json:"user_id"` + ListeningHistory json.RawMessage `json:"listening_history"` } type UserPayoutWalletHistory struct { diff --git a/api/dbv1/queries/get_tracks.sql b/api/dbv1/queries/get_tracks.sql index 3ccffb7a..7aa13078 100644 --- a/api/dbv1/queries/get_tracks.sql +++ b/api/dbv1/queries/get_tracks.sql @@ -65,33 +65,50 @@ SELECT is_scheduled_release, is_unlisted, - ARRAY( - SELECT user_id - FROM saves - JOIN follows ON followee_user_id = saves.user_id AND follower_user_id = @my_id - JOIN aggregate_user USING (user_id) - -- todo: join users, filter out deactivated - WHERE @my_id > 0 - AND save_item_id = t.track_id - AND save_type = 'track' - AND saves.is_delete = false - ORDER BY follower_count DESC - LIMIT 10 - )::int[] as followee_favorite_ids, + ( + SELECT json_agg( + json_build_object( + 'user_id', r.user_id::text, + 'repost_item_id', repost_item_id::text, -- this is redundant + 'repost_type', 'RepostType.track', -- some sqlalchemy bs + 'created_at', r.created_at -- this is not actually present in python response? + ) + ) + FROM ( + SELECT user_id, repost_item_id, reposts.created_at + FROM reposts + JOIN follows ON followee_user_id = reposts.user_id AND follower_user_id = @my_id + JOIN aggregate_user USING (user_id) + WHERE repost_item_id = t.track_id + AND repost_type = 'track' + AND reposts.is_delete = false + ORDER BY follower_count DESC + LIMIT 3 + ) r + )::jsonb as followee_reposts, + + ( + SELECT json_agg( + json_build_object( + 'user_id', r.user_id::text, + 'favorite_item_id', r.save_item_id::text, -- this is redundant + 'favorite_type', 'SaveType.track', -- some sqlalchemy bs + 'created_at', r.created_at -- this is not actually present in python response? + ) + ) + FROM ( + SELECT user_id, save_item_id, saves.created_at + FROM saves + JOIN follows ON followee_user_id = saves.user_id AND follower_user_id = @my_id + JOIN aggregate_user USING (user_id) + WHERE save_item_id = t.track_id + AND save_type = 'track' + AND saves.is_delete = false + ORDER BY follower_count DESC + LIMIT 3 + ) r + )::jsonb as followee_favorites, - ARRAY( - SELECT user_id - FROM reposts - JOIN follows ON followee_user_id = reposts.user_id AND follower_user_id = @my_id - JOIN aggregate_user USING (user_id) - -- todo: join users, filter out deactivated - WHERE @my_id > 0 - AND repost_item_id = t.track_id - AND repost_type = 'track' - AND reposts.is_delete = false - ORDER BY follower_count DESC - LIMIT 10 - )::int[] as followee_repost_ids, -- followee_favorites, -- route_id, diff --git a/go.mod b/go.mod index 92d469b4..0250cafb 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/joho/godotenv v1.5.1 github.com/speps/go-hashids/v2 v2.0.1 github.com/stretchr/testify v1.10.0 + github.com/valyala/fasthttp v1.59.0 go.uber.org/zap v1.27.0 golang.org/x/sync v0.12.0 google.golang.org/protobuf v1.36.6 @@ -50,7 +51,6 @@ require ( github.com/sasha-s/go-deadlock v0.3.5 // indirect github.com/supranational/blst v0.3.14 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.59.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect go.opentelemetry.io/otel v1.34.0 // indirect diff --git a/sqlc.yaml b/sqlc.yaml index 07ac7af7..be4e7c70 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -17,6 +17,11 @@ sql: import: "encoding/json" type: "RawMessage" + - db_type: "jsonb" + go_type: + import: "encoding/json" + type: "RawMessage" + - column: "tracks.copyright_line" go_type: import: "encoding/json" diff --git a/trashid/hashid.go b/trashid/hashid.go index 49bf9f38..e5f43fd5 100644 --- a/trashid/hashid.go +++ b/trashid/hashid.go @@ -2,6 +2,7 @@ package trashid import ( "errors" + "strconv" "github.com/speps/go-hashids/v2" ) @@ -30,6 +31,15 @@ func EncodeHashId(id int) (string, error) { return hashIdUtil.Encode([]int{id}) } +func StringEncode(id string) string { + if num, err := strconv.Atoi(id); err == nil { + if result, err := hashIdUtil.Encode([]int{num}); err == nil { + return result + } + } + return id +} + func MustEncodeHashID(id int) string { enc, err := EncodeHashId(id) if err != nil {