-
Notifications
You must be signed in to change notification settings - Fork 111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[PAY-2533, PAY-2534, PAY-2545, PAY-2547, PAY-2532] Index album purchases #7735
Merged
Merged
Changes from 13 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
1e8eea8
add album purchase type
amendelsohn 72353fd
create album_price_history_table
amendelsohn b9cdd86
index_purchase changes
amendelsohn 5257a8f
update price history table on playlist change
amendelsohn eeca61e
Merge branch 'main' into am-purchaseable-album-type
amendelsohn b1ce711
update test script for album purchase
amendelsohn 7bcece7
fix import
amendelsohn aec3439
pipe stream_conditions through to db; add cmd for premium album
amendelsohn a877407
working purchase script
amendelsohn 774e23f
add purchase album test
amendelsohn 115830f
add payment_router album purchase test
amendelsohn b08c9c7
remove debug logs
amendelsohn abd358c
type for purchase content type
amendelsohn 89f4e39
Merge branch 'main' into am-purchaseable-album-type
amendelsohn 9fe83d1
Merge branch 'main' into am-purchaseable-album-type
amendelsohn 4e893fa
PR feedback
amendelsohn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,49 +1,101 @@ | ||
import { randomBytes, randomInt } from "crypto"; | ||
import chalk from "chalk"; | ||
import { program } from "commander"; | ||
import { randomBytes, randomInt } from 'crypto' | ||
import chalk from 'chalk' | ||
import { program } from 'commander' | ||
|
||
import { initializeAudiusLibs } from "./utils.mjs"; | ||
import { initializeAudiusLibs } from './utils.mjs' | ||
|
||
program.command("create-playlist") | ||
.description("Create playlist") | ||
.argument("<trackIds...>", "Tracks to include in playlist") | ||
.option("-n, --name <name>", "Name of playlist (chosen randomly if not specified)") | ||
.option("-a, --album", "Make playlist an album", false) | ||
.option("-d, --description <description>", "Description of playlist (chosen randomly if not specified)") | ||
.option("-p, --private", "Make playlist private", false) | ||
.option("-f, --from <from>", "The account to create playlist from") | ||
.action(async (trackIds, { name, album, description, private: isPrivate, from }) => { | ||
const audiusLibs = await initializeAudiusLibs(from); | ||
const rand = randomBytes(2).toString("hex").padStart(4, "0").toUpperCase(); | ||
program | ||
.command('create-playlist') | ||
.description('Create playlist') | ||
.argument('<trackIds...>', 'Tracks to include in playlist') | ||
.option( | ||
'-n, --name <name>', | ||
'Name of playlist (chosen randomly if not specified)' | ||
) | ||
.option('-a, --album', 'Make playlist an album', false) | ||
.option( | ||
'-d, --description <description>', | ||
'Description of playlist (chosen randomly if not specified)' | ||
) | ||
.option('-p, --private', 'Make playlist private', false) | ||
.option( | ||
'-u, --price <price>', | ||
'The price for the album. Cannot be used without --album option' | ||
) | ||
.option('-f, --from <from>', 'The account to create playlist from') | ||
.action( | ||
async ( | ||
trackIds, | ||
{ name, album, description, price, private: isPrivate, from } | ||
) => { | ||
const audiusLibs = await initializeAudiusLibs(from) | ||
const rand = randomBytes(2).toString('hex').padStart(4, '0').toUpperCase() | ||
|
||
try { | ||
const playlistId = randomInt(400_001, 40_000_000); | ||
const playlistName = name || `playlist ${rand}` | ||
const metadata = { | ||
playlist_id: playlistId, | ||
playlist_name: playlistName, | ||
description: description || `playlist generated by audius-cmd ${rand}`, | ||
is_album: album, | ||
is_private: isPrivate, | ||
playlist_contents: { | ||
track_ids: trackIds.map(trackId => ({ | ||
track: Number(trackId), | ||
metadata_time: Date.now() / 1000, | ||
})) | ||
try { | ||
const playlistId = randomInt(400_001, 40_000_000) | ||
const playlistName = name || `playlist ${rand}` | ||
const metadata = { | ||
playlist_id: playlistId, | ||
playlist_name: playlistName, | ||
description: | ||
description || `playlist generated by audius-cmd ${rand}`, | ||
is_album: album, | ||
is_private: isPrivate, | ||
playlist_contents: { | ||
track_ids: trackIds.map((trackId) => ({ | ||
track: Number(trackId), | ||
metadata_time: Date.now() / 1000 | ||
})) | ||
} | ||
} | ||
console.log(chalk.yellow('Playlist Metadata: '), metadata) | ||
if (price) { | ||
if (!album) { | ||
program.error(chalk.red('Price can only be set for albums')) | ||
} | ||
metadata.is_stream_gated = true | ||
metadata.stream_conditions = await getStreamConditions({ | ||
price, | ||
audiusLibs | ||
}) | ||
console.log( | ||
chalk.yellow('Stream Conditions: '), | ||
metadata.stream_conditions | ||
) | ||
} | ||
const response = await audiusLibs.EntityManager.createPlaylist(metadata) | ||
|
||
if (response.error) { | ||
program.error(chalk.red(response.error)) | ||
} | ||
} | ||
const response = await audiusLibs.EntityManager.createPlaylist(metadata) | ||
|
||
if (response.error) { | ||
program.error(chalk.red(response.error)); | ||
console.log(chalk.green('Successfully created playlist')) | ||
console.log(chalk.yellow('Playlist Name: '), playlistName) | ||
console.log(chalk.yellow('Playlist ID: '), playlistId) | ||
} catch (err) { | ||
program.error(err.message) | ||
} | ||
|
||
console.log(chalk.green("Successfully created playlist")); | ||
console.log(chalk.yellow("Playlist Name: "), playlistName) | ||
console.log(chalk.yellow("Playlist ID: "), playlistId) | ||
} catch (err) { | ||
program.error(err.message); | ||
process.exit(0) | ||
} | ||
) | ||
|
||
process.exit(0); | ||
}); | ||
const getStreamConditions = async ({ price: priceString, audiusLibs }) => { | ||
if (priceString) { | ||
const price = Number.parseInt(priceString) | ||
if (!Number.isFinite(price) || price <= 0) { | ||
throw new Error(`Invalid price "${priceString}"`) | ||
} | ||
const { userbank } = | ||
await audiusLibs.solanaWeb3Manager.createUserBankIfNeeded({ | ||
mint: 'usdc' | ||
}) | ||
return { | ||
usdc_purchase: { | ||
price, | ||
splits: { [userbank.toString()]: price * 10 ** 4 } | ||
} | ||
} | ||
} | ||
return null | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import chalk from 'chalk' | ||
import { program } from 'commander' | ||
|
||
import { initializeAudiusLibs } from './utils.mjs' | ||
|
||
program | ||
.command('purchase-content') | ||
.description('Purchases a track or album using USDC') | ||
.argument('<id>', 'The track_id or playlist_id to purchase') | ||
.option('-f, --from [from]', 'The account purchasing the content (handle)') | ||
.option('-t, --type [type]', 'The content type to purchase (album or track)') | ||
.option( | ||
'-e, --extra-amount [amount]', | ||
'Extra amount to pay in addition to the price (in cents)' | ||
) | ||
.action(async (contentId, { from, type, extraAmount: extraAmountCents }) => { | ||
type = type || 'track' | ||
const audiusLibs = await initializeAudiusLibs(from) | ||
const user = audiusLibs.userStateManager.getCurrentUser() | ||
|
||
let blocknumber | ||
let streamConditions | ||
if (type === 'track') { | ||
const track = (await audiusLibs.Track.getTracks(100, 0, [contentId]))[0] | ||
if (!track.stream_conditions || !track.is_stream_gated) { | ||
program.error('Track is not stream gated') | ||
} | ||
if (!track.stream_conditions?.usdc_purchase?.splits) { | ||
program.error('Track is not purchaseable') | ||
} | ||
blocknumber = track.blocknumber | ||
streamConditions = track.stream_conditions | ||
} else if (type === 'album') { | ||
const album = ( | ||
await audiusLibs.Playlist.getPlaylists(100, 0, [contentId]) | ||
)[0] | ||
if (!album.is_album) { | ||
program.error('Playlist is not an album') | ||
} | ||
if (!album.stream_conditions || !album.is_stream_gated) { | ||
program.error('Album is not stream gated') | ||
} | ||
if (!album.stream_conditions?.usdc_purchase?.splits) { | ||
program.error('Album is not purchaseable') | ||
} | ||
blocknumber = album.blocknumber | ||
streamConditions = album.stream_conditions | ||
} else { | ||
program.error('Invalid type') | ||
} | ||
|
||
let extraAmount | ||
if (extraAmountCents) { | ||
const parsedExtraAmount = Number.parseInt(extraAmountCents) | ||
if (!Number.isFinite(parsedExtraAmount) || parsedExtraAmount <= 0) { | ||
program.error(`Invalid extra amount: ${extraAmountCents}`) | ||
} | ||
extraAmount = parsedExtraAmount * 10 ** 4 | ||
} | ||
|
||
try { | ||
const response = await audiusLibs.solanaWeb3Manager.purchaseContent({ | ||
id: contentId, | ||
extraAmount, | ||
type, | ||
blocknumber, | ||
splits: streamConditions.usdc_purchase.splits, | ||
purchaserUserId: user.user_id, | ||
purchaseAccess: 'stream' | ||
}) | ||
if (response.error) { | ||
program.error(chalk.red(response.error)) | ||
} | ||
console.log(chalk.green(`Successfully purchased ${type}`)) | ||
console.log(chalk.yellow('Transaction Signature:'), response.res) | ||
} catch (err) { | ||
program.error(err.message) | ||
} | ||
|
||
process.exit(0) | ||
}) |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
packages/discovery-provider/ddl/migrations/0058_album_price_history_table.sql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
CREATE TABLE IF NOT EXISTS album_price_history ( | ||
playlist_id integer NOT NULL, | ||
splits JSONB NOT NULL, -- Represents amounts per each Solana account | ||
total_price_cents bigint NOT NULL, | ||
blocknumber integer NOT NULL, | ||
block_timestamp timestamp WITHOUT TIME ZONE NOT NULL, | ||
created_at timestamp WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, | ||
PRIMARY KEY (playlist_id, block_timestamp), | ||
CONSTRAINT blocknumber_fkey FOREIGN KEY (blocknumber) REFERENCES blocks("number") | ||
); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Usually like to attach linear IDs/URLs to these so they don't get lost.