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
11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ COPY . .
ARG TARGETARCH
RUN CGO_ENABLED=0 GOOS=linux GOARCH=$TARGETARCH go build -o bridge main.go

FROM node:20-alpine AS plans-builder

WORKDIR /app

COPY static/plans/package.json static/plans/package-lock.json ./static/plans/
RUN cd static/plans && npm ci

COPY static/plans/ ./static/plans/
RUN cd static/plans && npm run build

FROM alpine:latest

RUN apk add --no-cache bash postgresql-client
Expand All @@ -19,6 +29,7 @@ WORKDIR /app

COPY --from=builder /app/bridge /bin/bridge
COPY --from=builder /app/ddl ./ddl
COPY --from=plans-builder /app/static/plans/dist ./static/plans/dist

EXPOSE 1323

Expand Down
9 changes: 9 additions & 0 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,10 @@ func NewApiServer(config config.Config) *ApiServer {
app.Get("/content/verbose", app.contentNodes)
app.Get("/content-nodes/verbose", app.contentNodes)

// Plans React app - serve static assets first, then SPA routing
app.Static("/plans/assets", "./static/plans/dist/assets")
app.Get("/plans/*", app.servePlans)

app.Static("/", "./static")

// Disable swagger in test environments, because it will slow things down a lot
Expand Down Expand Up @@ -705,6 +709,11 @@ func (app *ApiServer) home(c *fiber.Ctx) error {
})
}

func (app *ApiServer) servePlans(c *fiber.Ctx) error {
// Serve index.html for SPA routing (all /plans/* routes that aren't assets)
return c.SendFile("./static/plans/dist/index.html")
}

func decodeIdList(c *fiber.Ctx) []int32 {
var ids []int32
for _, b := range c.Request().URI().QueryArgs().PeekMulti("id") {
Expand Down
50 changes: 50 additions & 0 deletions api/v1_users_developer_apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,23 @@ type DeveloperApp struct {
ImageUrl *string `json:"image_url" db:"image_url"`
}

type DeveloperAppWithMetrics struct {
Address string `json:"address" db:"address"`
UserId trashid.HashId `json:"user_id" db:"user_id"`
Name string `json:"name" db:"name"`
Description *string `json:"description" db:"description"`
ImageUrl *string `json:"image_url" db:"image_url"`
RequestCount int64 `json:"request_count" db:"request_count"`
RequestCountAllTime int64 `json:"request_count_all_time" db:"request_count_all_time"`
}

func (app *ApiServer) v1UsersDeveloperApps(c *fiber.Ctx) error {
userId := app.getUserId(c)
includeMetrics := c.Query("include") == "metrics"

if includeMetrics {
return app.v1UsersDeveloperAppsWithMetrics(c, userId)
}

sql := `
SELECT address, user_id, name, description, image_url
Expand All @@ -41,3 +56,38 @@ func (app *ApiServer) v1UsersDeveloperApps(c *fiber.Ctx) error {
"data": apps,
})
}

func (app *ApiServer) v1UsersDeveloperAppsWithMetrics(c *fiber.Ctx, userId int32) error {
sql := `
SELECT
da.address,
da.user_id,
da.name,
da.description,
da.image_url,
COALESCE(SUM(ama.request_count) FILTER (WHERE ama.date >= DATE_TRUNC('month', CURRENT_DATE)::date AND ama.date <= CURRENT_DATE), 0)::bigint AS request_count,
COALESCE(SUM(ama.request_count), 0)::bigint AS request_count_all_time
FROM developer_apps da
LEFT JOIN api_metrics_apps ama ON ama.api_key = da.address
WHERE da.user_id = @userId
AND da.is_current = true
AND da.is_delete = false
GROUP BY da.address, da.user_id, da.name, da.description, da.image_url
`

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

apps, err := pgx.CollectRows(rows, pgx.RowToStructByName[DeveloperAppWithMetrics])
if err != nil {
return err
}

return c.JSON(fiber.Map{
"data": apps,
})
}
36 changes: 36 additions & 0 deletions ddl/migrations/0189_api_keys_tables.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
BEGIN;

-- api_keys: stores API key credentials with rate limit settings
CREATE TABLE IF NOT EXISTS api_keys (
api_key VARCHAR(255) NOT NULL PRIMARY KEY,
api_secret VARCHAR(255),
rps INTEGER NOT NULL DEFAULT 10,
rpm INTEGER NOT NULL DEFAULT 500000,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);

-- api_access_keys: stores access keys associated with API keys
CREATE TABLE IF NOT EXISTS api_access_keys (
api_key VARCHAR(255) NOT NULL,
api_access_key VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
is_active BOOLEAN NOT NULL DEFAULT true,
PRIMARY KEY (api_key, api_access_key)
);

CREATE INDEX IF NOT EXISTS idx_api_access_keys_api_access_key ON api_access_keys(api_access_key);
CREATE INDEX IF NOT EXISTS idx_api_access_keys_is_active ON api_access_keys(api_key, is_active) WHERE is_active = true;

-- Migrate existing developer apps into api_keys
INSERT INTO api_keys (api_key, api_secret, created_at)
SELECT DISTINCT ON (da.address)
da.address,
NULL,
da.created_at
FROM developer_apps da
WHERE da.is_current = true
AND da.is_delete = false
ORDER BY da.address, da.created_at DESC
ON CONFLICT (api_key) DO NOTHING;

COMMIT;
4 changes: 4 additions & 0 deletions static/plans/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
dist
.DS_Store
*.log
44 changes: 44 additions & 0 deletions static/plans/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Audius API Plans

React application for displaying Audius API plans at `api.audius.co/plans`.

## Setup

1. Install dependencies:
```bash
npm install
```

2. Set environment variable:
```bash
export VITE_AUDIUS_API_KEY=your_api_key_here
```

Or create a `.env` file:
```
VITE_AUDIUS_API_KEY=your_api_key_here
```

## Development

Run the development server:
```bash
npm run dev
```

## Building

Build for production:
```bash
npm run build
```

This will create a `dist/` directory with the built files that will be served by the Go API server.

## Deployment

After building, the Go server will serve the built files from `./static/plans/dist/` at the `/plans` route.

The app is configured to:
- Serve static assets from `/plans/assets/`
- Serve the SPA from `/plans/*` routes
17 changes: 17 additions & 0 deletions static/plans/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Audius API Plans</title>
<script type="module">
import { Buffer } from "buffer";
window.Buffer = Buffer;
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading