Skip to content
Open
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: 3 additions & 1 deletion api/dbv1/full_comments.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func (q *Queries) FullCommentsKeyed(ctx context.Context, arg GetCommentsParams)
SELECT 1
FROM comment_reactions
WHERE comment_id = comments.comment_id
AND user_id = COALESCE(tracks.owner_id, comments.entity_id)
AND user_id = COALESCE(tracks.owner_id, events.user_id, comments.entity_id)
AND is_delete = false
) AS is_artist_reacted,
Expand All @@ -128,11 +128,13 @@ func (q *Queries) FullCommentsKeyed(ctx context.Context, arg GetCommentsParams)
FROM comments
LEFT JOIN tracks ON comments.entity_type = 'Track' AND comments.entity_id = tracks.track_id
LEFT JOIN events ON comments.entity_type = 'Event' AND comments.entity_id = events.event_id
LEFT JOIN comment_threads USING (comment_id)
WHERE comments.comment_id = ANY(@ids::int[])
AND (
(comments.entity_type = 'Track' AND (@include_unlisted = true OR COALESCE(tracks.is_unlisted, false) = false))
OR comments.entity_type = 'FanClub'
OR (comments.entity_type = 'Event' AND COALESCE(events.is_deleted, false) = false)
)
ORDER BY comments.created_at DESC
`
Expand Down
2 changes: 2 additions & 0 deletions api/dbv1/models.go

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

7 changes: 7 additions & 0 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,12 @@ func NewApiServer(config config.Config) *ApiServer {
g.Get("/fan_club/feed", app.v1FanClubFeed)
g.Get("/fan-club/feed", app.v1FanClubFeed)

g.Get("/events/:eventId/comments", app.v1EventComments)
g.Get("/events/:eventId/follow_state", app.v1EventFollowState)
g.Get("/events/:eventId/follow-state", app.v1EventFollowState)
g.Post("/events/:eventId/follow", app.requireAuthMiddleware, app.requireWriteScope, app.postV1EventFollow)
g.Delete("/events/:eventId/follow", app.requireAuthMiddleware, app.requireWriteScope, app.deleteV1EventFollow)

g.Get("/tracks/:trackId/comments", app.v1TrackComments)
g.Get("/tracks/:trackId/comment_count", app.v1TrackCommentCount)
g.Get("/tracks/:trackId/comment-count", app.v1TrackCommentCount)
Expand Down Expand Up @@ -608,6 +614,7 @@ func NewApiServer(config config.Config) *ApiServer {
// Events
g.Get("/events/unclaimed_id", app.v1EventsUnclaimedId)
g.Get("/events/unclaimed-id", app.v1EventsUnclaimedId)
g.Get("/events/remix-contests", app.v1EventsRemixContests)
g.Get("/events", app.v1Events)
g.Get("/events/all", app.v1Events)
g.Get("/events/entity", app.v1Events)
Expand Down
51 changes: 51 additions & 0 deletions api/swagger/swagger-v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,57 @@ paths:
"500":
description: Server error
content: {}
/events/remix-contests:
get:
tags:
- events
summary: Get all remix contests
description:
Get remix contest events ordered with currently-active contests first
(by soonest-ending), followed by ended contests (most-recently-ended
first). Active contests are those whose end_date is null or in the
future.
operationId: Get Remix Contests
security:
- {}
- OAuth2:
- read
parameters:
- name: offset
in: query
description:
The number of items to skip. Useful for pagination (page number
* limit)
schema:
type: integer
- name: limit
in: query
description: The number of items to fetch
schema:
type: integer
- name: status
in: query
description: Filter contests by status
schema:
type: string
default: all
enum:
- active
- ended
- all
responses:
"200":
description: Success
content:
application/json:
schema:
$ref: "#/components/schemas/events_response"
"400":
description: Bad request
content: {}
"500":
description: Server error
content: {}
/events/unclaimed_id:
get:
tags:
Expand Down
8 changes: 4 additions & 4 deletions api/v1_comments.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type GetCommentsParams struct {
}

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

type UpdateCommentRequest struct {
EntityType string `json:"entityType" validate:"required,oneof=Track"`
EntityType string `json:"entityType" validate:"required,oneof=Track FanClub Event"`
EntityId int `json:"entityId" validate:"required,min=1"`
Body string `json:"body" validate:"required,max=500"`
Mentions []int `json:"mentions,omitempty" validate:"omitempty,dive,min=1"`
}

type ReactCommentRequest struct {
EntityType string `json:"entityType" validate:"required,oneof=Track"`
EntityType string `json:"entityType" validate:"required,oneof=Track FanClub Event"`
EntityId int `json:"entityId" validate:"required,min=1"`
}

type PinCommentRequest struct {
EntityType string `json:"entityType" validate:"required,oneof=Track"`
EntityType string `json:"entityType" validate:"required,oneof=Track FanClub Event"`
EntityId int `json:"entityId" validate:"required,min=1"`
}

Expand Down
128 changes: 128 additions & 0 deletions api/v1_event_comments.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package api

import (
"errors"

"api.audius.co/api/dbv1"
"api.audius.co/trashid"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5"
)

// v1EventComments returns the top-level comment stream for a remix-contest event.
// Comments are authored by any signed-in user; a comment is considered a "post
// update" when its user_id matches the event's owner user_id (resolved client-side).
// Replies are not returned in this list — they come back nested inside the
// FullComment result just like track comments.
func (app *ApiServer) v1EventComments(c *fiber.Ctx) error {
encodedEventID := c.Params("eventId")
eventID, err := trashid.DecodeHashId(encodedEventID)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, "invalid event id")
}

var eventRow struct {
UserID int32
IsDeleted bool
}
err = app.pool.QueryRow(c.Context(), `
SELECT user_id, COALESCE(is_deleted, false)
FROM events
WHERE event_id = $1
LIMIT 1
`, eventID).Scan(&eventRow.UserID, &eventRow.IsDeleted)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return fiber.NewError(fiber.StatusNotFound, "event not found")
}
return err
}
if eventRow.IsDeleted {
return fiber.NewError(fiber.StatusNotFound, "event not found")
}

var params GetCommentsParams
if err := app.ParseAndValidateQueryParams(c, &params); err != nil {
return err
}

myID := app.getMyId(c)

// Pull top-level comment ids for this event, sorted and paginated the same
// way track comments are. Threads are materialised below by FullComments.
orderBy := `comments.created_at DESC`
switch params.SortMethod {
case "timestamp":
orderBy = `comments.created_at ASC`
case "top":
orderBy = `(SELECT COUNT(*) FROM comment_reactions cr WHERE cr.comment_id = comments.comment_id) DESC, comments.created_at DESC`
}

sql := `
SELECT comments.comment_id
FROM comments
LEFT JOIN comment_threads ct ON ct.comment_id = comments.comment_id
WHERE comments.entity_type = 'Event'
AND comments.entity_id = @eventId
AND comments.is_delete = false
AND ct.parent_comment_id IS NULL
ORDER BY ` + orderBy + `
LIMIT @limit
OFFSET @offset
`

args := pgx.NamedArgs{
"eventId": eventID,
"limit": params.Limit,
"offset": params.Offset,
}

rows, err := app.pool.Query(c.Context(), sql, args)
if err != nil {
return err
}
commentIDs, err := pgx.CollectRows(rows, pgx.RowTo[int32])
if err != nil {
return err
}

comments, err := app.queries.FullComments(c.Context(), dbv1.GetCommentsParams{
Ids: commentIDs,
MyID: myID,
IncludeUnlisted: true,
})
if err != nil {
return err
}

// Collect related user ids so the UI can render avatars/handles in one shot.
userIDs := []int32{eventRow.UserID}
for _, co := range comments {
userIDs = append(userIDs, int32(co.UserId))
for _, m := range co.Mentions {
userIDs = append(userIDs, int32(m.UserId))
}
for _, r := range co.Replies {
userIDs = append(userIDs, int32(r.UserId))
}
}

related, err := app.queries.Parallel(c.Context(), dbv1.ParallelParams{
UserIds: userIDs,
TrackIds: nil,
MyID: myID,
AuthedWallet: app.tryGetAuthedWallet(c),
})
if err != nil {
return err
}

return c.JSON(fiber.Map{
"data": comments,
"related": fiber.Map{
"users": related.UserList(),
"tracks": related.TrackList(),
},
"event_user_id": trashid.MustEncodeHashID(int(eventRow.UserID)),
})
}
Loading
Loading