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
3 changes: 3 additions & 0 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,9 @@ func NewApiServer(config config.Config) *ApiServer {
g.Post("/prizes/claim", app.v1PrizesClaim)
g.Get("/wallet/:wallet/prizes", app.v1WalletPrizes)

// Genres
g.Get("/genres/popular", app.v1GenresPopular)

// Resolve
g.Get("/resolve", app.v1Resolve)

Expand Down
78 changes: 78 additions & 0 deletions api/swagger/swagger-v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ tags:
description: Events related operations
- name: explore
description: Explore related operations
- name: genres
description: Genre discovery operations
- name: rewards
description: Rewards related operations
- name: prizes
Expand Down Expand Up @@ -1591,6 +1593,57 @@ paths:
"500":
description: Server error
content: {}
/genres/popular:
get:
tags:
- genres
description: Get popular genres from recently created tracks.
operationId: Get Popular Genres
parameters:
- name: start_time
in: query
description: Unix timestamp. Only tracks created after this time are counted.
schema:
type: integer
default: 0
- name: limit
in: query
description: The number of genres to fetch
schema:
type: integer
minimum: 1
maximum: 100
default: 100
- name: offset
in: query
description:
The number of items to skip. Useful for pagination (page number
* limit)
schema:
type: integer
minimum: 0
default: 0
- name: min_count
in: query
description: Minimum number of tracks required for a genre to be returned
schema:
type: integer
minimum: 1
maximum: 1000000
default: 1
responses:
"200":
description: Success
content:
application/json:
schema:
$ref: "#/components/schemas/popular_genres_response"
"400":
description: Bad request
content: {}
"500":
description: Server error
content: {}
/playlists:
get:
tags:
Expand Down Expand Up @@ -10594,6 +10647,28 @@ paths:
content: {}
components:
schemas:
popular_genres_response:
required:
- data
type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/popular_genre"
popular_genre:
required:
- name
- count
type: object
properties:
name:
type: string
description: Genre name
count:
type: integer
format: int64
description: Number of tracks in this genre for the requested time window
create_access_key_response:
type: object
required:
Expand Down Expand Up @@ -13564,6 +13639,9 @@ components:
event_data:
type: object
properties: {}
permalink:
type: string
description: Canonical contest permalink derived from event_routes.
create_user_request_body:
type: object
required:
Expand Down
54 changes: 54 additions & 0 deletions api/v1_genres_popular.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package api

import (
"time"

"api.audius.co/api/dbv1"
"github.com/gofiber/fiber/v2"
)

type GetPopularGenresParams struct {
StartTime int `query:"start_time" default:"0"`
Limit int `query:"limit" default:"100" validate:"min=1,max=100"`
Offset int `query:"offset" default:"0" validate:"min=0"`
MinCount int `query:"min_count" default:"1" validate:"min=1,max=1000000"`
}

type PopularGenre struct {
Name string `json:"name"`
Count int64 `json:"count"`
}

func (app *ApiServer) v1GenresPopular(c *fiber.Ctx) error {
params := GetPopularGenresParams{}
if err := app.ParseAndValidateQueryParams(c, &params); err != nil {
return err
}

startTime := time.Unix(int64(params.StartTime), 0)

genres, err := app.queries.GetGenres(c.Context(), dbv1.GetGenresParams{
LimitVal: int32(params.Limit),
OffsetVal: int32(params.Offset),
StartTime: startTime,
})
if err != nil {
return err
}

result := make([]PopularGenre, 0, len(genres))
for _, genre := range genres {
if genre.Count < int64(params.MinCount) {
continue
}

result = append(result, PopularGenre{
Name: genre.Genre.String,
Count: genre.Count,
})
}

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

import (
"context"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestGenresPopular(t *testing.T) {
app := testAppWithFixtures(t)

var response struct {
Data []struct {
Name string `json:"name"`
Count int64 `json:"count"`
}
}

status, _ := testGet(t, app, "/v1/genres/popular?start_time=0", &response)
require.Equal(t, 200, status)
require.NotEmpty(t, response.Data)

var foundElectronic bool
for i, genre := range response.Data {
if genre.Name == "Electronic" {
foundElectronic = true
}
if i > 0 {
assert.GreaterOrEqual(t, response.Data[i-1].Count, genre.Count)
}
}
assert.True(t, foundElectronic, "expected fixture genres in response")
}

func TestGenresPopularMinCount(t *testing.T) {
app := testAppWithFixtures(t)

var response struct {
Data []struct {
Name string `json:"name"`
Count int64 `json:"count"`
}
}

status, _ := testGet(t, app, "/v1/genres/popular?start_time=0&min_count=2", &response)
require.Equal(t, 200, status)
require.NotEmpty(t, response.Data)

for _, genre := range response.Data {
assert.GreaterOrEqual(t, genre.Count, int64(2))
}
}

func TestGenresPopularExcludesAccessAuthoritiesTracks(t *testing.T) {
app := testAppWithFixtures(t)
ctx := context.Background()
require.NotNil(t, app.writePool, "test requires write pool")

url := fmt.Sprintf("/v1/genres/popular?start_time=%d", 0)
var before struct {
Data []struct {
Name string `json:"name"`
Count int64 `json:"count"`
}
}
status, _ := testGet(t, app, url, &before)
require.Equal(t, 200, status)

var electronicCountBefore int64
for _, g := range before.Data {
if g.Name == "Electronic" {
electronicCountBefore = g.Count
break
}
}
require.Greater(t, electronicCountBefore, int64(0), "fixtures should have Electronic tracks")

_, err := app.writePool.Exec(ctx, `UPDATE tracks SET access_authorities = ARRAY['0xgate']::text[] WHERE track_id = 100 AND is_current = true`)
require.NoError(t, err)

var after struct {
Data []struct {
Name string `json:"name"`
Count int64 `json:"count"`
}
}
status, _ = testGet(t, app, url, &after)
require.Equal(t, 200, status)

var electronicCountAfter int64
for _, g := range after.Data {
if g.Name == "Electronic" {
electronicCountAfter = g.Count
break
}
}
assert.Equal(t, electronicCountBefore-1, electronicCountAfter, "genre count must exclude access_authorities tracks")
}
Loading