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
19 changes: 19 additions & 0 deletions TIPS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Quickly scan some sql into a map and respond:

```go
rows, err := app.pool.Query(c.Context(), sql, pgx.NamedArgs{
"user_id": c.Locals("userId"),
})
if err != nil {
return err
}

stuff, err := pgx.CollectRows(rows, pgx.RowToMap)
if err != nil {
return err
}

return c.JSON(fiber.Map{
"data": stuff,
})
```
198 changes: 198 additions & 0 deletions api/dbv1/full_comments.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package dbv1

import (
"context"
"time"

"bridgerton.audius.co/trashid"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
)

type GetCommentsParams struct {
MyID interface{} `json:"my_id"`
Ids []int32 `json:"ids"`
}

type FullComment struct {
Id trashid.HashId `json:"id"`
EntityType string `json:"entity_type"`
EntityId trashid.HashId `json:"entity_id"`
UserId trashid.HashId `json:"user_id"`
Message string `json:"message"`
Mentions []struct {
UserId int `json:"user_id"`
Handle string `json:"handle"`
} `json:"mentions"`
TrackTimestampS pgtype.Int4 `json:"track_timestamp_s"`
IsMuted bool `json:"is_muted"`
IsEdited bool `json:"is_edited"`
IsCurrentUserReacted bool `json:"is_current_user_reacted"`
IsArtistReacted bool `json:"is_artist_reacted"`
IsDelete bool `json:"-"`
IsTombstone bool `json:"is_tombstone"`
ReactCount int `json:"react_count"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`

ReplyCount int `json:"reply_count"`
Replies []FullComment `json:"replies"`

// this should be omitted
ReplyIds []int32 `db:"reply_ids" json:"-"`
ParentCommentId pgtype.Int4 `json:"-"`
}

func (q *Queries) FullCommentsKeyed(ctx context.Context, arg GetCommentsParams) (map[int32]FullComment, error) {
if len(arg.Ids) == 0 {
return nil, nil
}

sql := `
SELECT
comment_id as id,
parent_comment_id,
entity_type,
entity_id,
user_id,
text as message,

(
SELECT json_agg(
json_build_object(
'user_id', m.user_id,
'handle', handle
)
)
FROM (
SELECT user_id, handle FROM comment_mentions
JOIN users USING (user_id)
WHERE comment_id = comments.comment_id
) m
)::jsonb as mentions,

track_timestamp_s,

(
SELECT count(*)
FROM comment_reactions
WHERE comment_id = comments.comment_id
AND is_delete = false
) as react_count,


(
SELECT array_agg(comment_id)
FROM comment_threads
JOIN comments cc USING (comment_id)
WHERE parent_comment_id = comments.comment_id
AND cc.is_delete = false
) as reply_ids,

is_edited,

EXISTS (
SELECT 1
FROM comment_reactions
WHERE comment_id = comments.comment_id
AND user_id = @my_id
AND is_delete = false
) AS is_current_user_reacted,

EXISTS (
SELECT 1
FROM comment_reactions
WHERE comment_id = comments.comment_id
AND user_id = tracks.owner_id
AND is_delete = false
) AS is_artist_reacted,

comments.is_delete,

coalesce((
SELECT is_muted
FROM comment_notification_settings mutes
WHERE @my_id > 0
AND mutes.user_id = @my_id
AND mutes.entity_type = entity_type
AND mutes.entity_id = entity_id
LIMIT 1
), false) as is_muted,

comments.created_at,
comments.updated_at

FROM comments
JOIN tracks ON entity_id = track_id
LEFT JOIN comment_threads USING (comment_id)
WHERE comment_id = ANY(@ids::int[])
ORDER BY comments.created_at DESC
`

rows, err := q.db.Query(ctx, sql, pgx.NamedArgs{
"ids": arg.Ids,
"my_id": arg.MyID,
})
if err != nil {
return nil, err
}

comments, err := pgx.CollectRows(rows, pgx.RowToStructByNameLax[FullComment])
if err != nil {
return nil, err
}

commentMap := map[int32]FullComment{}
for _, comment := range comments {
commentMap[int32(comment.Id)] = comment
}

// fetch replies
replyIds := []int32{}
for _, comment := range comments {
replyIds = append(replyIds, comment.ReplyIds...)
}
replyMap, err := q.FullCommentsKeyed(ctx, GetCommentsParams{
MyID: arg.MyID,
Ids: replyIds,
})
if err != nil {
return nil, err
}

for id, comment := range commentMap {
for _, replyId := range comment.ReplyIds {
if reply, ok := replyMap[replyId]; ok {
comment.Replies = append(comment.Replies, reply)
}
}
// todo: sort replies?
comment.ReplyCount = len(comment.Replies)

if comment.IsDelete {
comment.Message = "[Removed]"
if comment.ReplyCount > 0 {
comment.IsTombstone = true
}
}
commentMap[id] = comment
}

return commentMap, nil

}

func (q *Queries) FullComments(ctx context.Context, arg GetCommentsParams) ([]FullComment, error) {
commentMap, err := q.FullCommentsKeyed(ctx, arg)
if err != nil {
return nil, err
}

comments := make([]FullComment, 0, len(arg.Ids))
for _, id := range arg.Ids {
if c, ok := commentMap[id]; ok {
comments = append(comments, c)
}
}
return comments, nil
}
24 changes: 24 additions & 0 deletions api/dbv1/parallel.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,27 @@ func (q *Queries) Parallel(ctx context.Context, arg ParallelParams) (*ParallelRe

return result, nil
}

func (r *ParallelResult) UserList() []FullUser {
userList := make([]FullUser, 0, len(r.UserMap))
for _, u := range r.UserMap {
userList = append(userList, u)
}
return userList
}

func (r *ParallelResult) TrackList() []FullTrack {
trackList := make([]FullTrack, 0, len(r.TrackMap))
for _, t := range r.TrackMap {
trackList = append(trackList, t)
}
return trackList
}

func (r *ParallelResult) PlaylistList() []FullPlaylist {
playlistList := make([]FullPlaylist, 0, len(r.PlaylistMap))
for _, p := range r.PlaylistMap {
playlistList = append(playlistList, p)
}
return playlistList
}
8 changes: 8 additions & 0 deletions api/fixture_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,14 @@ var (
"updated_at": time.Now(),
"txhash": "tx123",
}

commentBaseRow = map[string]any{
"entity_type": "Track",
"created_at": time.Now(),
"updated_at": time.Now(),
"txhash": "0x1",
"blockhash": "0x2",
}
)

func insertFixtures(table string, baseRow map[string]any, csvFile string) {
Expand Down
20 changes: 14 additions & 6 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,15 @@ func NewApiServer(config config.Config) *ApiServer {
logger.Error("db connect failed", zap.Error(err))
}

connConfig.ConnConfig.Tracer = &tracelog.TraceLog{
Logger: pgxzap.NewLogger(logger),
LogLevel: tracelog.LogLevelInfo,
// disable sql logging in ENV "test"
if config.Env != "test" {
connConfig.ConnConfig.Tracer = &tracelog.TraceLog{
Logger: pgxzap.NewLogger(logger),
LogLevel: tracelog.LogLevelInfo,
}
}

pool, err := pgxpool.NewWithConfig(context.Background(), connConfig)
// To turn off pgx logging, use this:
// pool, err := pgxpool.New(context.Background(), config.DbUrl)

if err != nil {
logger.Fatal("db connect failed", zap.Error(err))
Expand Down Expand Up @@ -194,7 +195,7 @@ func NewApiServer(config config.Config) *ApiServer {
for _, g := range []fiber.Router{v1, v1Full} {
// Users
g.Get("/users", app.v1Users)

g.Get("/users/unclaimed_id", app.v1UsersUnclaimedId)
g.Get("/users/account/:wallet", app.requireAuthMiddleware, app.v1UsersAccount)

g.Use("/users/handle/:handle", app.requireHandleMiddleware)
Expand All @@ -204,6 +205,7 @@ func NewApiServer(config config.Config) *ApiServer {

g.Use("/users/:userId", app.requireUserIdMiddleware)
g.Get("/users/:userId", app.v1User)
g.Get("/users/:userId/comments", app.v1UsersComments)
g.Get("/users/:userId/followers", app.v1UsersFollowers)
g.Get("/users/:userId/following", app.v1UsersFollowing)
g.Get("/users/:userId/library/tracks", app.v1UsersLibraryTracks)
Expand All @@ -220,6 +222,7 @@ func NewApiServer(config config.Config) *ApiServer {

// Tracks
g.Get("/tracks", app.v1Tracks)
g.Get("/tracks/unclaimed_id", app.v1TracksUnclaimedId)

g.Get("/tracks/trending", app.v1TracksTrending)
g.Get("/tracks/trending/ids", app.v1TracksTrendingIds)
Expand All @@ -229,9 +232,11 @@ func NewApiServer(config config.Config) *ApiServer {
g.Get("/tracks/:trackId", app.v1Track)
g.Get("/tracks/:trackId/reposts", app.v1TracksReposts)
g.Get("/tracks/:trackId/favorites", app.v1TracksFavorites)
g.Get("/tracks/:trackId/comments", app.v1TracksComments)

// Playlists
g.Get("/playlists", app.v1playlists)
g.Get("/playlists/unclaimed_id", app.v1PlaylistsUnclaimedId)

g.Use("/playlists/:playlistId", app.requirePlaylistIdMiddleware)
g.Get("/playlists/:playlistId", app.v1Playlist)
Expand All @@ -243,6 +248,9 @@ func NewApiServer(config config.Config) *ApiServer {

// Rewards
g.Get("/rewards/claim", app.v1ClaimRewards)

// Comments
g.Get("/comments/unclaimed_id", app.v1CommentsUnclaimedId)
}

app.Static("/", "./static")
Expand Down
9 changes: 9 additions & 0 deletions api/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func TestMain(m *testing.M) {
}

app = NewApiServer(config.Config{
Env: "test",
DbUrl: "postgres://postgres:example@localhost:21300/test",
DelegatePrivateKey: "0633fddb74e32b3cbc64382e405146319c11a1a52dc96598e557c5dbe2f31468",
})
Expand Down Expand Up @@ -79,6 +80,8 @@ func TestMain(m *testing.M) {
insertFixtures("usdc_purchases", usdcPurchaseBaseRow, "testdata/usdc_purchases_fixtures.csv")
insertFixtures("track_routes", map[string]any{}, "testdata/track_routes_fixtures.csv")
insertFixtures("grants", grantBaseRow, "testdata/grants_fixtures.csv")
insertFixtures("comments", commentBaseRow, "testdata/comment_fixtures.csv")
insertFixtures("comment_threads", map[string]any{}, "testdata/comment_thread_fixtures.csv")

// index to es / os

Expand Down Expand Up @@ -135,6 +138,12 @@ func Test200UnAuthed(t *testing.T) {
"/v1/full/playlists?id=7eP5n",
"/v1/full/playlists/7eP5n/reposts",
"/v1/full/playlists/7eP5n/favorites",

// unclaimed ids
"/v1/users/unclaimed_id",
"/v1/tracks/unclaimed_id",
"/v1/playlists/unclaimed_id",
"/v1/comments/unclaimed_id",
}

for _, u := range urls {
Expand Down
3 changes: 3 additions & 0 deletions api/testdata/comment_fixtures.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
comment_id,user_id,entity_id,text
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

1,1,201,flame emoji
2,2,201,thanks for the emoji
2 changes: 2 additions & 0 deletions api/testdata/comment_thread_fixtures.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
comment_id,parent_comment_id
2,1
Loading
Loading