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
54 changes: 54 additions & 0 deletions api/dbv1/collaborator_invites.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package dbv1

import (
"context"
"time"

"api.audius.co/trashid"
)

// FullTrackCollaboratorInvite is a collaborator credit from the invited user's
// perspective: which track, who invited them, and the current status.
type FullTrackCollaboratorInvite struct {
TrackID string `json:"track_id"`
Status string `json:"status"`
InvitedBy User `json:"invited_by"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

// FullTrackCollaboratorInvites resolves a user's collaborator invites, embedding
// the inviter as a full user object (mirrors FullManagers).
func (q *Queries) FullTrackCollaboratorInvites(ctx context.Context, params GetTrackCollaboratorInvitesForUserParams) ([]FullTrackCollaboratorInvite, error) {
rows, err := q.GetTrackCollaboratorInvitesForUser(ctx, params)
if err != nil {
return nil, err
}

inviterIds := make([]int32, len(rows))
for i, row := range rows {
inviterIds[i] = row.InvitedBy
}

users, err := q.UsersKeyed(ctx, GetUsersParams{
Ids: inviterIds,
MyID: params.UserID,
})
if err != nil {
return nil, err
}

invites := make([]FullTrackCollaboratorInvite, len(rows))
for i, row := range rows {
trackID, _ := trashid.EncodeHashId(int(row.TrackID))
invites[i] = FullTrackCollaboratorInvite{
TrackID: trackID,
Status: row.Status,
InvitedBy: users[row.InvitedBy],
CreatedAt: row.CreatedAt,
UpdatedAt: row.UpdatedAt,
}
}

return invites, nil
}
24 changes: 21 additions & 3 deletions api/dbv1/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions api/dbv1/queries/track_collaborators.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- Accepted collaborators for a set of tracks, used to embed a `collaborators`
-- array on track responses. Returns one row per (track, collaborator); the Go
-- layer bulk-resolves the user objects. Backed by the track_collaborators
-- primary key (track_id leads), so the ANY(...) lookup is index-served.
-- name: GetTrackCollaborators :many
SELECT track_id, collaborator_user_id
FROM track_collaborators
WHERE track_id = ANY(@track_ids::int[])
AND status = 'accepted'
ORDER BY track_id, created_at;

-- A user's collaborator invites/credits, optionally filtered by status
-- (pending/accepted/rejected). Backed by the covering
-- (collaborator_user_id, status, track_id) index.
-- name: GetTrackCollaboratorInvitesForUser :many
SELECT track_id, collaborator_user_id, invited_by, status, created_at, updated_at
FROM track_collaborators
WHERE collaborator_user_id = @user_id::int
AND (sqlc.narg('status')::text IS NULL OR status = sqlc.narg('status'))
ORDER BY created_at DESC;
102 changes: 102 additions & 0 deletions api/dbv1/track_collaborators.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 33 additions & 9 deletions api/dbv1/tracks.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ const IncludeID3TagsCtxKey = "includeID3Tags"
type Track struct {
GetTracksRow

Permalink string `json:"permalink"`
IsStreamable bool `json:"is_streamable"`
Artwork *SquareImage `json:"artwork"`
Stream *MediaLink `json:"stream"`
Download *MediaLink `json:"download"`
Preview *MediaLink `json:"preview"`
UserID trashid.HashId `json:"user_id"`
User User `json:"user"`
Access Access `json:"access"`
Permalink string `json:"permalink"`
IsStreamable bool `json:"is_streamable"`
Artwork *SquareImage `json:"artwork"`
Stream *MediaLink `json:"stream"`
Download *MediaLink `json:"download"`
Preview *MediaLink `json:"preview"`
UserID trashid.HashId `json:"user_id"`
User User `json:"user"`
Collaborators []User `json:"collaborators"`
Access Access `json:"access"`

FolloweeReposts []*FolloweeRepost `json:"followee_reposts"`
FolloweeFavorites []*FolloweeFavorite `json:"followee_favorites"`
Expand All @@ -45,6 +46,7 @@ func (q *Queries) TracksKeyed(ctx context.Context, arg TracksParams) (map[int32]
}

userIds := []int32{}
trackIds := make([]int32, 0, len(rawTracks))
collectSplitUserIds := func(usage *AccessGate) {
if usage == nil || usage.UsdcPurchase == nil {
return
Expand All @@ -56,6 +58,7 @@ func (q *Queries) TracksKeyed(ctx context.Context, arg TracksParams) (map[int32]

for _, rawTrack := range rawTracks {
userIds = append(userIds, rawTrack.UserID)
trackIds = append(trackIds, rawTrack.TrackID)

var remixOf RemixOf
json.Unmarshal(rawTrack.RemixOf, &remixOf)
Expand All @@ -67,6 +70,18 @@ func (q *Queries) TracksKeyed(ctx context.Context, arg TracksParams) (map[int32]
collectSplitUserIds(rawTrack.DownloadConditions)
}

// Fetch accepted collaborators for these tracks in one query, and fold
// their user IDs into the bulk user fetch below so each is fully resolved.
collaboratorRows, err := q.GetTrackCollaborators(ctx, trackIds)
if err != nil {
return nil, err
}
collaboratorsByTrack := map[int32][]int32{}
for _, cr := range collaboratorRows {
collaboratorsByTrack[cr.TrackID] = append(collaboratorsByTrack[cr.TrackID], cr.CollaboratorUserID)
userIds = append(userIds, cr.CollaboratorUserID)
}

userMap, err := q.UsersKeyed(ctx, GetUsersParams{
MyID: arg.MyID.(int32),
Ids: userIds,
Expand Down Expand Up @@ -130,6 +145,14 @@ func (q *Queries) TracksKeyed(ctx context.Context, arg TracksParams) (map[int32]
}
}

// Resolve accepted collaborators (order preserved from the query).
collaborators := []User{}
for _, cid := range collaboratorsByTrack[rawTrack.TrackID] {
if cu, ok := userMap[cid]; ok {
collaborators = append(collaborators, cu)
}
}

// Get access from the bulk access map
access := accessMap[rawTrack.TrackID]

Expand Down Expand Up @@ -179,6 +202,7 @@ func (q *Queries) TracksKeyed(ctx context.Context, arg TracksParams) (map[int32]
Preview: preview,
User: user,
UserID: user.ID,
Collaborators: collaborators,
FolloweeFavorites: fullFolloweeFavorites(rawTrack.FolloweeFavorites),
FolloweeReposts: fullFolloweeReposts(rawTrack.FolloweeReposts),
RemixOf: fullRemixOf,
Expand Down
1 change: 1 addition & 0 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ func NewApiServer(config config.Config) *ApiServer {
g.Get("/users/:userId/balance/history", app.v1UsersBalanceHistory)
g.Get("/users/:userId/managers", app.v1UsersManagers)
g.Get("/users/:userId/managed_users", app.v1UsersManagedUsers)
g.Get("/users/:userId/collaboration_invites", app.v1UsersCollaborationInvites)
g.Get("/grantees/:address/users", app.v1GranteeUsers)
g.Post("/users/:userId/grants", app.requireAuthMiddleware, app.requireWriteScope, app.postV1UsersGrant)
g.Delete("/users/:userId/grants/:address", app.requireAuthMiddleware, app.requireWriteScope, app.deleteV1UsersGrant)
Expand Down
4 changes: 3 additions & 1 deletion api/v1_notifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const notificationRelatedActorsPerGroup = 1
type GetNotificationsQueryParams struct {
// Note that when limit is 0, we return 20 items to calculate unread count
Limit int `query:"limit" default:"20" validate:"min=0,max=100"`
Types []string `query:"types" validate:"dive,oneof=announcement follow repost save remix cosign create tip_receive tip_send challenge_reward repost_of_repost save_of_repost tastemaker reaction supporter_dethroned supporter_rank_up supporting_rank_up milestone track_added_to_playlist tier_change trending trending_playlist trending_underground usdc_purchase_buyer usdc_purchase_seller track_added_to_purchased_album request_manager approve_manager_request claimable_reward comment comment_thread comment_mention comment_reaction listen_streak_reminder fan_remix_contest_started fan_remix_contest_ended fan_remix_contest_ending_soon fan_remix_contest_winners_selected fan_remix_contest_submission artist_remix_contest_ended artist_remix_contest_ending_soon artist_remix_contest_submissions fan_club_text_post remix_contest_update"`
Types []string `query:"types" validate:"dive,oneof=announcement follow repost save remix cosign create tip_receive tip_send challenge_reward repost_of_repost save_of_repost tastemaker reaction supporter_dethroned supporter_rank_up supporting_rank_up milestone track_added_to_playlist tier_change trending trending_playlist trending_underground usdc_purchase_buyer usdc_purchase_seller track_added_to_purchased_album request_manager approve_manager_request track_collaborator_invite track_collaborator_accept claimable_reward comment comment_thread comment_mention comment_reaction listen_streak_reminder fan_remix_contest_started fan_remix_contest_ended fan_remix_contest_ending_soon fan_remix_contest_winners_selected fan_remix_contest_submission artist_remix_contest_ended artist_remix_contest_ending_soon artist_remix_contest_submissions fan_club_text_post remix_contest_update"`
GroupID string `query:"group_id" validate:"omitempty"`
Timestamp float64 `query:"timestamp" validate:"omitempty,min=0"`
}
Expand Down Expand Up @@ -378,6 +378,8 @@ func collectNotificationRelatedIds(action json.RawMessage, userIds, trackIds, pl
"data.receiver_user_id",
"data.dethroned_user_id",
"data.grantee_user_id",
"data.inviter_user_id",
"data.collaborator_user_id",
"data.tastemaker_user_id",
"data.tastemaker_item_owner_id",
"data.track_owner_id",
Expand Down
Loading
Loading