Skip to content

Commit 1740740

Browse files
committed
Add event comments, followers, and remix contests
1 parent a8261c6 commit 1740740

16 files changed

+1612
-6
lines changed

api/dbv1/full_comments.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func (q *Queries) FullCommentsKeyed(ctx context.Context, arg GetCommentsParams)
105105
SELECT 1
106106
FROM comment_reactions
107107
WHERE comment_id = comments.comment_id
108-
AND user_id = COALESCE(tracks.owner_id, comments.entity_id)
108+
AND user_id = COALESCE(tracks.owner_id, events.user_id, comments.entity_id)
109109
AND is_delete = false
110110
) AS is_artist_reacted,
111111
@@ -128,11 +128,13 @@ func (q *Queries) FullCommentsKeyed(ctx context.Context, arg GetCommentsParams)
128128
129129
FROM comments
130130
LEFT JOIN tracks ON comments.entity_type = 'Track' AND comments.entity_id = tracks.track_id
131+
LEFT JOIN events ON comments.entity_type = 'Event' AND comments.entity_id = events.event_id
131132
LEFT JOIN comment_threads USING (comment_id)
132133
WHERE comments.comment_id = ANY(@ids::int[])
133134
AND (
134135
(comments.entity_type = 'Track' AND (@include_unlisted = true OR COALESCE(tracks.is_unlisted, false) = false))
135136
OR comments.entity_type = 'FanClub'
137+
OR (comments.entity_type = 'Event' AND COALESCE(events.is_deleted, false) = false)
136138
)
137139
ORDER BY comments.created_at DESC
138140
`

api/dbv1/models.go

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/server.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,12 @@ func NewApiServer(config config.Config) *ApiServer {
511511
g.Get("/fan_club/feed", app.v1FanClubFeed)
512512
g.Get("/fan-club/feed", app.v1FanClubFeed)
513513

514+
g.Get("/events/:eventId/comments", app.v1EventComments)
515+
g.Get("/events/:eventId/follow_state", app.v1EventFollowState)
516+
g.Get("/events/:eventId/follow-state", app.v1EventFollowState)
517+
g.Post("/events/:eventId/follow", app.requireAuthMiddleware, app.requireWriteScope, app.postV1EventFollow)
518+
g.Delete("/events/:eventId/follow", app.requireAuthMiddleware, app.requireWriteScope, app.deleteV1EventFollow)
519+
514520
g.Get("/tracks/:trackId/comments", app.v1TrackComments)
515521
g.Get("/tracks/:trackId/comment_count", app.v1TrackCommentCount)
516522
g.Get("/tracks/:trackId/comment-count", app.v1TrackCommentCount)
@@ -608,6 +614,7 @@ func NewApiServer(config config.Config) *ApiServer {
608614
// Events
609615
g.Get("/events/unclaimed_id", app.v1EventsUnclaimedId)
610616
g.Get("/events/unclaimed-id", app.v1EventsUnclaimedId)
617+
g.Get("/events/remix-contests", app.v1EventsRemixContests)
611618
g.Get("/events", app.v1Events)
612619
g.Get("/events/all", app.v1Events)
613620
g.Get("/events/entity", app.v1Events)

api/swagger/swagger-v1.yaml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1194,6 +1194,57 @@ paths:
11941194
"500":
11951195
description: Server error
11961196
content: {}
1197+
/events/remix-contests:
1198+
get:
1199+
tags:
1200+
- events
1201+
summary: Get all remix contests
1202+
description:
1203+
Get remix contest events ordered with currently-active contests first
1204+
(by soonest-ending), followed by ended contests (most-recently-ended
1205+
first). Active contests are those whose end_date is null or in the
1206+
future.
1207+
operationId: Get Remix Contests
1208+
security:
1209+
- {}
1210+
- OAuth2:
1211+
- read
1212+
parameters:
1213+
- name: offset
1214+
in: query
1215+
description:
1216+
The number of items to skip. Useful for pagination (page number
1217+
* limit)
1218+
schema:
1219+
type: integer
1220+
- name: limit
1221+
in: query
1222+
description: The number of items to fetch
1223+
schema:
1224+
type: integer
1225+
- name: status
1226+
in: query
1227+
description: Filter contests by status
1228+
schema:
1229+
type: string
1230+
default: all
1231+
enum:
1232+
- active
1233+
- ended
1234+
- all
1235+
responses:
1236+
"200":
1237+
description: Success
1238+
content:
1239+
application/json:
1240+
schema:
1241+
$ref: "#/components/schemas/events_response"
1242+
"400":
1243+
description: Bad request
1244+
content: {}
1245+
"500":
1246+
description: Server error
1247+
content: {}
11971248
/events/unclaimed_id:
11981249
get:
11991250
tags:

api/v1_comments.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type GetCommentsParams struct {
2323
}
2424

2525
type CreateCommentRequest struct {
26-
EntityType string `json:"entityType" validate:"required,oneof=Track"`
26+
EntityType string `json:"entityType" validate:"required,oneof=Track FanClub Event"`
2727
EntityId int `json:"entityId" validate:"required,min=1"`
2828
Body string `json:"body" validate:"required,max=500"`
2929
CommentId *int `json:"commentId,omitempty" validate:"omitempty,min=1"`
@@ -33,19 +33,19 @@ type CreateCommentRequest struct {
3333
}
3434

3535
type UpdateCommentRequest struct {
36-
EntityType string `json:"entityType" validate:"required,oneof=Track"`
36+
EntityType string `json:"entityType" validate:"required,oneof=Track FanClub Event"`
3737
EntityId int `json:"entityId" validate:"required,min=1"`
3838
Body string `json:"body" validate:"required,max=500"`
3939
Mentions []int `json:"mentions,omitempty" validate:"omitempty,dive,min=1"`
4040
}
4141

4242
type ReactCommentRequest struct {
43-
EntityType string `json:"entityType" validate:"required,oneof=Track"`
43+
EntityType string `json:"entityType" validate:"required,oneof=Track FanClub Event"`
4444
EntityId int `json:"entityId" validate:"required,min=1"`
4545
}
4646

4747
type PinCommentRequest struct {
48-
EntityType string `json:"entityType" validate:"required,oneof=Track"`
48+
EntityType string `json:"entityType" validate:"required,oneof=Track FanClub Event"`
4949
EntityId int `json:"entityId" validate:"required,min=1"`
5050
}
5151

api/v1_event_comments.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package api
2+
3+
import (
4+
"errors"
5+
6+
"api.audius.co/api/dbv1"
7+
"api.audius.co/trashid"
8+
"github.com/gofiber/fiber/v2"
9+
"github.com/jackc/pgx/v5"
10+
)
11+
12+
// v1EventComments returns the top-level comment stream for a remix-contest event.
13+
// Comments are authored by any signed-in user; a comment is considered a "post
14+
// update" when its user_id matches the event's owner user_id (resolved client-side).
15+
// Replies are not returned in this list — they come back nested inside the
16+
// FullComment result just like track comments.
17+
func (app *ApiServer) v1EventComments(c *fiber.Ctx) error {
18+
encodedEventID := c.Params("eventId")
19+
eventID, err := trashid.DecodeHashId(encodedEventID)
20+
if err != nil {
21+
return fiber.NewError(fiber.StatusBadRequest, "invalid event id")
22+
}
23+
24+
var eventRow struct {
25+
UserID int32
26+
IsDeleted bool
27+
}
28+
err = app.pool.QueryRow(c.Context(), `
29+
SELECT user_id, COALESCE(is_deleted, false)
30+
FROM events
31+
WHERE event_id = $1
32+
LIMIT 1
33+
`, eventID).Scan(&eventRow.UserID, &eventRow.IsDeleted)
34+
if err != nil {
35+
if errors.Is(err, pgx.ErrNoRows) {
36+
return fiber.NewError(fiber.StatusNotFound, "event not found")
37+
}
38+
return err
39+
}
40+
if eventRow.IsDeleted {
41+
return fiber.NewError(fiber.StatusNotFound, "event not found")
42+
}
43+
44+
var params GetCommentsParams
45+
if err := app.ParseAndValidateQueryParams(c, &params); err != nil {
46+
return err
47+
}
48+
49+
myID := app.getMyId(c)
50+
51+
// Pull top-level comment ids for this event, sorted and paginated the same
52+
// way track comments are. Threads are materialised below by FullComments.
53+
orderBy := `comments.created_at DESC`
54+
switch params.SortMethod {
55+
case "timestamp":
56+
orderBy = `comments.created_at ASC`
57+
case "top":
58+
orderBy = `(SELECT COUNT(*) FROM comment_reactions cr WHERE cr.comment_id = comments.comment_id) DESC, comments.created_at DESC`
59+
}
60+
61+
sql := `
62+
SELECT comments.comment_id
63+
FROM comments
64+
LEFT JOIN comment_threads ct ON ct.comment_id = comments.comment_id
65+
WHERE comments.entity_type = 'Event'
66+
AND comments.entity_id = @eventId
67+
AND comments.is_delete = false
68+
AND ct.parent_comment_id IS NULL
69+
ORDER BY ` + orderBy + `
70+
LIMIT @limit
71+
OFFSET @offset
72+
`
73+
74+
args := pgx.NamedArgs{
75+
"eventId": eventID,
76+
"limit": params.Limit,
77+
"offset": params.Offset,
78+
}
79+
80+
rows, err := app.pool.Query(c.Context(), sql, args)
81+
if err != nil {
82+
return err
83+
}
84+
commentIDs, err := pgx.CollectRows(rows, pgx.RowTo[int32])
85+
if err != nil {
86+
return err
87+
}
88+
89+
comments, err := app.queries.FullComments(c.Context(), dbv1.GetCommentsParams{
90+
Ids: commentIDs,
91+
MyID: myID,
92+
IncludeUnlisted: true,
93+
})
94+
if err != nil {
95+
return err
96+
}
97+
98+
// Collect related user ids so the UI can render avatars/handles in one shot.
99+
userIDs := []int32{eventRow.UserID}
100+
for _, co := range comments {
101+
userIDs = append(userIDs, int32(co.UserId))
102+
for _, m := range co.Mentions {
103+
userIDs = append(userIDs, int32(m.UserId))
104+
}
105+
for _, r := range co.Replies {
106+
userIDs = append(userIDs, int32(r.UserId))
107+
}
108+
}
109+
110+
related, err := app.queries.Parallel(c.Context(), dbv1.ParallelParams{
111+
UserIds: userIDs,
112+
TrackIds: nil,
113+
MyID: myID,
114+
AuthedWallet: app.tryGetAuthedWallet(c),
115+
})
116+
if err != nil {
117+
return err
118+
}
119+
120+
return c.JSON(fiber.Map{
121+
"data": comments,
122+
"related": fiber.Map{
123+
"users": related.UserList(),
124+
"tracks": related.TrackList(),
125+
},
126+
"event_user_id": trashid.MustEncodeHashID(int(eventRow.UserID)),
127+
})
128+
}

0 commit comments

Comments
 (0)