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
64 changes: 43 additions & 21 deletions api/dbv1/get_users.sql.go

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

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.

62 changes: 41 additions & 21 deletions api/dbv1/queries/get_users.sql
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ SELECT

has_collectibles,
allow_ai_attribution,
preferred_coin_flair_mint,

(
SELECT JSON_BUILD_OBJECT(
Expand All @@ -145,28 +146,47 @@ SELECT
'ticker', ticker
)::jsonb
FROM artist_coins
WHERE artist_coins.mint = COALESCE(
-- Owned first
(
SELECT artist_coins.mint
FROM artist_coins
WHERE artist_coins.user_id = u.user_id
LIMIT 1
),
-- Then most held
(
SELECT sol_user_balances.mint
FROM sol_user_balances
JOIN artist_coins ON artist_coins.mint = sol_user_balances.mint -- ensure mapped in artist_coins
WHERE sol_user_balances.user_id = u.user_id
AND sol_user_balances.balance > 0
AND sol_user_balances.mint != '9LzCMqDgTKYz9Drzqnpgee3SGa89up3a247ypMj2xrqM' -- ignore prod wAUDIO
AND sol_user_balances.mint != 'BELGiMZQ34SDE6x2FUaML2UHDAgBLS64xvhXjX5tBBZo' -- ignore stage wAUDIO
AND sol_user_balances.mint != 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZy4z6cQ' -- ignore USDC
ORDER BY sol_user_balances.balance DESC
LIMIT 1
WHERE artist_coins.mint =
-- User explicitly disabled flair
CASE WHEN (u.preferred_coin_flair_mint = '') THEN NULL
ELSE COALESCE(
-- Use preferred flair if valid artist coin and user has a balance
CASE WHEN (u.preferred_coin_flair_mint IS NOT NULL) THEN
(
SELECT sol_user_balances.mint
FROM sol_user_balances
JOIN artist_coins ON artist_coins.mint = sol_user_balances.mint
WHERE sol_user_balances.user_id = u.user_id
AND sol_user_balances.balance > 0
AND sol_user_balances.mint = u.preferred_coin_flair_mint
LIMIT 1
)
ELSE NULL
END,
-- Owned first
(
SELECT artist_coins.mint
FROM artist_coins
WHERE artist_coins.user_id = u.user_id
LIMIT 1
),
-- Then most held
(
SELECT sol_user_balances.mint
FROM sol_user_balances
JOIN artist_coins ON artist_coins.mint = sol_user_balances.mint -- ensure mapped in artist_coins
WHERE sol_user_balances.user_id = u.user_id
AND sol_user_balances.balance > 0
AND sol_user_balances.mint NOT IN (
'9LzCMqDgTKYz9Drzqnpgee3SGa89up3a247ypMj2xrqM', -- ignore prod wAUDIO
'BELGiMZQ34SDE6x2FUaML2UHDAgBLS64xvhXjX5tBBZo', -- ignore stage wAUDIO
'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZy4z6cQ' -- ignore USDC
)
ORDER BY sol_user_balances.balance DESC
LIMIT 1
)
)
)
END
) AS artist_coin_badge

FROM users u
Expand Down
4 changes: 3 additions & 1 deletion api/swagger/swagger-v1-full.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5495,6 +5495,8 @@ components:
ticker:
type: string
description: The coin symbol/ticker
preferred_coin_flair_mint:
type: string
bio:
type: string
cover_photo:
Expand Down Expand Up @@ -6035,7 +6037,7 @@ components:
type: array
items:
$ref: '#/components/schemas/coin'
coin_members_count_response:
coin_members_count_response:
type: object
required:
- data
Expand Down
132 changes: 131 additions & 1 deletion api/v1_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,26 @@ func TestGetUserCoinBadges(t *testing.T) {
"user_id": 4,
"handle": "stereosteve",
},
{
"user_id": 5,
"handle": "user5",
"preferred_coin_flair_mint": "test_mint_address_124", // Prefers STEVE
},
{
"user_id": 6,
"handle": "user6",
"preferred_coin_flair_mint": "test_mint_address_123", // Prefers TESTCOIN but has zero balance
},
{
"user_id": 7,
"handle": "user7",
"preferred_coin_flair_mint": "", // Empty string - should show no badge
},
{
"user_id": 8,
"handle": "user8",
"preferred_coin_flair_mint": "test_mint_address_124", // Prefers STEVE over their own coin
},
},
"artist_coins": {
{
Expand All @@ -87,6 +107,14 @@ func TestGetUserCoinBadges(t *testing.T) {
"logo_uri": "https://example.com/audio-logo.png",
"created_at": "2024-01-01 00:00:00",
},
{
"ticker": "USER8COIN",
"decimals": 8,
"user_id": 8,
"mint": "test_mint_address_125",
"logo_uri": "https://example.com/user8-logo.png",
"created_at": "2024-01-01 00:00:00",
},
},
"sol_user_balances": {
// User 1 has more AUDIO than TESTCOIN, but more TESTCOIN than $TEVE
Expand Down Expand Up @@ -138,6 +166,55 @@ func TestGetUserCoinBadges(t *testing.T) {
"mint": "test_mint_address_123",
"balance": 300,
},
// User 5 prefers STEVE and has balance in it (should show STEVE even if they have more of other coins)
{
"user_id": 5,
"mint": "test_mint_address_124",
"balance": 50,
},
{
"user_id": 5,
"mint": "test_mint_address_123",
"balance": 1000, // Much higher balance but should be ignored due to preference
},
{
"user_id": 5,
"mint": "9LzCMqDgTKYz9Drzqnpgee3SGa89up3a247ypMj2xrqM",
"balance": 500,
},
// User 6 prefers TESTCOIN but has zero balance (should fall back to existing logic)
{
"user_id": 6,
"mint": "test_mint_address_123",
"balance": 0,
},
{
"user_id": 6,
"mint": "test_mint_address_124",
"balance": 200,
},
// User 7 has empty string preference (should show no badge)
{
"user_id": 7,
"mint": "test_mint_address_123",
"balance": 100,
},
{
"user_id": 7,
"mint": "test_mint_address_124",
"balance": 200,
},
// User 8 prefers STEVE over their own coin (should show STEVE despite having their own coin)
{
"user_id": 8,
"mint": "test_mint_address_124", // STEVE - preferred coin
"balance": 100,
},
{
"user_id": 8,
"mint": "test_mint_address_125", // Their own coin
"balance": 500, // Higher balance but should be ignored due to preference
},
},
}

Expand All @@ -149,6 +226,7 @@ func TestGetUserCoinBadges(t *testing.T) {
assert.Equal(t, 200, status)

jsonAssert(t, body, map[string]any{
"data.0.preferred_coin_flair_mint": nil,
"data.0.artist_coin_badge.mint": "test_mint_address_123",
"data.0.artist_coin_badge.ticker": "TESTCOIN",
"data.0.artist_coin_badge.logo_uri": "https://example.com/test-logo.png",
Expand All @@ -161,7 +239,8 @@ func TestGetUserCoinBadges(t *testing.T) {
assert.Equal(t, 200, status)

jsonAssert(t, body, map[string]any{
"data.0.artist_coin_badge": nil,
"data.0.preferred_coin_flair_mint": nil,
"data.0.artist_coin_badge": nil,
})
}

Expand All @@ -171,6 +250,7 @@ func TestGetUserCoinBadges(t *testing.T) {
assert.Equal(t, 200, status)

jsonAssert(t, body, map[string]any{
"data.0.preferred_coin_flair_mint": nil,
"data.0.artist_coin_badge.mint": "test_mint_address_123",
"data.0.artist_coin_badge.ticker": "TESTCOIN",
"data.0.artist_coin_badge.logo_uri": "https://example.com/test-logo.png",
Expand All @@ -182,6 +262,56 @@ func TestGetUserCoinBadges(t *testing.T) {
status, body := testGet(t, app, "/v1/full/users/"+trashid.MustEncodeHashID(4))
assert.Equal(t, 200, status)

jsonAssert(t, body, map[string]any{
"data.0.preferred_coin_flair_mint": nil,
"data.0.artist_coin_badge.mint": "test_mint_address_124",
"data.0.artist_coin_badge.ticker": "STEVE",
"data.0.artist_coin_badge.logo_uri": "https://example.com/steve-logo.png",
})
}

// Preferred flair with non-zero balance takes priority over higher balance coins
{
status, body := testGet(t, app, "/v1/full/users/"+trashid.MustEncodeHashID(5))
assert.Equal(t, 200, status)

jsonAssert(t, body, map[string]any{
"data.0.preferred_coin_flair_mint": "test_mint_address_124",
"data.0.artist_coin_badge.mint": "test_mint_address_124",
"data.0.artist_coin_badge.ticker": "STEVE",
"data.0.artist_coin_badge.logo_uri": "https://example.com/steve-logo.png",
})
}

// Preferred flair with zero balance falls back to 'auto' logic (artist's own coin/highest balance)
{
status, body := testGet(t, app, "/v1/full/users/"+trashid.MustEncodeHashID(6))
assert.Equal(t, 200, status)

jsonAssert(t, body, map[string]any{
"data.0.preferred_coin_flair_mint": "test_mint_address_123",
"data.0.artist_coin_badge.mint": "test_mint_address_124",
"data.0.artist_coin_badge.ticker": "STEVE",
"data.0.artist_coin_badge.logo_uri": "https://example.com/steve-logo.png",
})
}

// Empty string preferred flair should return no badge even if user has balances
{
status, body := testGet(t, app, "/v1/full/users/"+trashid.MustEncodeHashID(7))
assert.Equal(t, 200, status)

jsonAssert(t, body, map[string]any{
"data.0.preferred_coin_flair_mint": "",
"data.0.artist_coin_badge": nil,
})
}

// Preferred flair takes priority over user's own artist coin
{
status, body := testGet(t, app, "/v1/full/users/"+trashid.MustEncodeHashID(8))
assert.Equal(t, 200, status)

jsonAssert(t, body, map[string]any{
"data.0.artist_coin_badge.mint": "test_mint_address_124",
"data.0.artist_coin_badge.ticker": "STEVE",
Expand Down
6 changes: 6 additions & 0 deletions ddl/migrations/0175_add_user_coin_badge_preference.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
BEGIN;

ALTER TABLE users ADD COLUMN IF NOT EXISTS preferred_coin_flair_mint TEXT DEFAULT NULL;
COMMENT ON COLUMN users.preferred_coin_flair_mint IS 'The mint of the coin which the user has selected as their preferred flair. NULL for auto, empty string for none.';

COMMIT;
Loading
Loading