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
40 changes: 40 additions & 0 deletions api/swagger/swagger-v1-full.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6394,6 +6394,26 @@ components:
type: string
description: The description of the coin
example: "A majestic bear token for wildlife conservation"
link_1:
type: string
description: Generic link URL for the coin
example: "https://x.com/bear_token"
format: uri
link_2:
type: string
description: Generic link URL for the coin
example: "https://instagram.com/bear_token"
format: uri
link_3:
type: string
description: Generic link URL for the coin
example: "https://tiktok.com/@bear_token"
format: uri
link_4:
type: string
description: Generic link URL for the coin
example: "https://bear-token.com"
format: uri
create_coin_response:
type: object
properties:
Expand Down Expand Up @@ -6431,6 +6451,26 @@ components:
type: string
description: The URI for the coin's logo image
example: "https://example.com/logo.png"
description:
type: string
description: The description of the coin
example: "A majestic bear token for wildlife conservation"
link_1:
type: string
description: Generic link URL for the coin
example: "https://x.com/bear_token"
link_2:
type: string
description: Generic link URL for the coin
example: "https://instagram.com/bear_token"
link_3:
type: string
description: Generic link URL for the coin
example: "https://tiktok.com/@bear_token"
link_4:
type: string
description: Generic link URL for the coin
example: "https://bear-token.com"
created_at:
type: string
format: date-time
Expand Down
40 changes: 40 additions & 0 deletions api/swagger/swagger-v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6036,6 +6036,26 @@ components:
type: string
description: The description of the coin
example: "A majestic bear token for wildlife conservation"
link_1:
type: string
description: Generic link URL for the coin
example: "https://x.com/bear_token"
format: uri
link_2:
type: string
description: Generic link URL for the coin
example: "https://instagram.com/bear_token"
format: uri
link_3:
type: string
description: Generic link URL for the coin
example: "https://tiktok.com/@bear_token"
format: uri
link_4:
type: string
description: Generic link URL for the coin
example: "https://bear-token.com"
format: uri
create_coin_response:
type: object
properties:
Expand Down Expand Up @@ -6073,6 +6093,26 @@ components:
type: string
description: The URI for the coin's logo image
example: "https://example.com/logo.png"
description:
type: string
description: The description of the coin
example: "A majestic bear token for wildlife conservation"
link_1:
type: string
description: Generic link URL for the coin
example: "https://x.com/bear_token"
link_2:
type: string
description: Generic link URL for the coin
example: "https://instagram.com/bear_token"
link_3:
type: string
description: Generic link URL for the coin
example: "https://tiktok.com/@bear_token"
link_4:
type: string
description: Generic link URL for the coin
example: "https://bear-token.com"
created_at:
type: string
format: date-time
Expand Down
70 changes: 58 additions & 12 deletions api/v1_create_coin.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package api

import (
"errors"
"fmt"
"time"

"api.audius.co/config"
Expand All @@ -13,12 +14,16 @@ import (
)

type CreateCoinBody struct {
Mint string `json:"mint" validate:"required,solana_address"`
Ticker string `json:"ticker" validate:"required,min=2,max=10,coin_ticker"`
Decimals int32 `json:"decimals" validate:"required,min=0,max=18"`
Name string `json:"name" validate:"required,min=1,max=32"`
LogoUri string `json:"logo_uri" validate:"omitempty,url"`
Description string `json:"description" validate:"max=2500"`
Mint string `json:"mint" validate:"required,solana_address"`
Ticker string `json:"ticker" validate:"required,min=2,max=10,coin_ticker"`
Decimals int32 `json:"decimals" validate:"required,min=0,max=18"`
Name string `json:"name" validate:"required,min=1,max=32"`
LogoUri string `json:"logo_uri" validate:"omitempty,url"`
Description string `json:"description" validate:"max=2500"`
Link1 *string `json:"link_1,omitempty"`
Link2 *string `json:"link_2,omitempty"`
Link3 *string `json:"link_3,omitempty"`
Link4 *string `json:"link_4,omitempty"`
}

func (app *ApiServer) v1CreateCoin(c *fiber.Ctx) error {
Expand Down Expand Up @@ -61,21 +66,58 @@ func (app *ApiServer) v1CreateCoin(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, "User has already created a coin")
}

linkValues := map[string]interface{}{
"link_1": nil,
"link_2": nil,
"link_3": nil,
"link_4": nil,
}

linkInputs := []struct {
field string
value *string
}{
{"link_1", body.Link1},
{"link_2", body.Link2},
{"link_3", body.Link3},
{"link_4", body.Link4},
}

for _, link := range linkInputs {
if link.value == nil {
continue
}
if *link.value != "" {
if err := validateURL(*link.value); err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Invalid %s URL format", link.field))
}
linkValues[link.field] = *link.value
} else {
linkValues[link.field] = nil
}
}

sql := `
INSERT INTO artist_coins (mint, ticker, user_id, decimals, name, logo_uri, description)
VALUES (@mint, @ticker, @user_id, @decimals, @name, @logo_uri, @description)
RETURNING mint, ticker, user_id, decimals, name, logo_uri, description, created_at
INSERT INTO artist_coins (mint, ticker, user_id, decimals, name, logo_uri, description, link_1, link_2, link_3, link_4)
VALUES (@mint, @ticker, @user_id, @decimals, @name, @logo_uri, @description, @link_1, @link_2, @link_3, @link_4)
RETURNING mint, ticker, user_id, decimals, name, logo_uri, description, link_1, link_2, link_3, link_4, created_at
`

row := app.writePool.QueryRow(c.Context(), sql, pgx.NamedArgs{
args := pgx.NamedArgs{
"mint": body.Mint,
"ticker": body.Ticker,
"user_id": userID,
"decimals": body.Decimals,
"name": body.Name,
"logo_uri": body.LogoUri,
"description": body.Description,
})
"link_1": linkValues["link_1"],
"link_2": linkValues["link_2"],
"link_3": linkValues["link_3"],
"link_4": linkValues["link_4"],
}

row := app.writePool.QueryRow(c.Context(), sql, args)

var result struct {
Mint string `json:"mint"`
Expand All @@ -85,10 +127,14 @@ func (app *ApiServer) v1CreateCoin(c *fiber.Ctx) error {
Name string `json:"name"`
LogoUri string `json:"logo_uri"`
Description string `json:"description"`
Link1 *string `json:"link_1,omitempty"`
Link2 *string `json:"link_2,omitempty"`
Link3 *string `json:"link_3,omitempty"`
Link4 *string `json:"link_4,omitempty"`
CreatedAt time.Time `json:"created_at"`
}

if err := row.Scan(&result.Mint, &result.Ticker, &result.UserID, &result.Decimals, &result.Name, &result.LogoUri, &result.Description, &result.CreatedAt); err != nil {
if err := row.Scan(&result.Mint, &result.Ticker, &result.UserID, &result.Decimals, &result.Name, &result.LogoUri, &result.Description, &result.Link1, &result.Link2, &result.Link3, &result.Link4, &result.CreatedAt); err != nil {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && pgErr.Code == pgerrcode.UniqueViolation {
if pgErr.ConstraintName == "artist_coins_pkey" {
Expand Down
56 changes: 56 additions & 0 deletions api/v1_create_coin_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package api

import (
"context"
"database/sql"
"encoding/json"
"testing"
"time"
Expand Down Expand Up @@ -58,6 +60,60 @@ func TestV1CreateCoin(t *testing.T) {

}

func TestV1CreateCoin_StoresLinks(t *testing.T) {
app := emptyTestApp(t)
database.Seed(app.pool.Replicas[0], database.FixtureMap{
"users": {
{
"user_id": 2,
"wallet": "0xc3d1d41e6872ffbd15c473d14fc3a9250be5b5e0",
"is_verified": true,
},
},
})

link1Val := "https://example.com/alpha"
link2Val := "https://example.com/beta"
link3Val := ""

requestBody := CreateCoinBody{
Mint: "lionR26zyyB3fNQm5wWv1ZfN8MPQDUMwaAuoG79b1Yj",
Ticker: "LION",
Decimals: 9,
Name: "LION",
LogoUri: "https://example.com/lion-logo.png",
Description: "Roaring with automatic social links",
Link1: &link1Val,
Link2: &link2Val,
Link3: &link3Val,
}
requestBodyBytes, err := json.Marshal(requestBody)
assert.NoError(t, err)

status, _ := testPostWithWallet(t, app, "/v1/coins?user_id="+trashid.MustEncodeHashID(2), "0xc3d1d41e6872ffbd15c473d14fc3a9250be5b5e0", requestBodyBytes, map[string]string{
"Content-Type": "application/json",
})

assert.Equal(t, 201, status)

var dbLink1, dbLink2, dbLink3, dbLink4 sql.NullString
err = app.pool.QueryRow(context.Background(), `
SELECT link_1, link_2, link_3, link_4
FROM artist_coins
WHERE mint = $1
`, requestBody.Mint).Scan(&dbLink1, &dbLink2, &dbLink3, &dbLink4)
assert.NoError(t, err)

assert.True(t, dbLink1.Valid)
assert.Equal(t, "https://example.com/alpha", dbLink1.String)

assert.True(t, dbLink2.Valid)
assert.Equal(t, "https://example.com/beta", dbLink2.String)

assert.False(t, dbLink3.Valid)
assert.False(t, dbLink4.Valid)
}

func TestV1CreateCoin_DuplicateMint(t *testing.T) {
app := emptyTestApp(t)
database.Seed(app.pool.Replicas[0], database.FixtureMap{
Expand Down
1 change: 0 additions & 1 deletion api/v1_update_coin.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ func (app *ApiServer) v1UpdateCoin(c *fiber.Ctx) error {
setParts := []string{"updated_at = NOW()"}
args := pgx.NamedArgs{"mint": mint}
hasUpdates := false

if body.Description != "" {
setParts = append(setParts, "description = @description")
args["description"] = body.Description
Expand Down
Loading