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
18 changes: 18 additions & 0 deletions .changeset/purple-suns-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@audius/sdk': major
---

Rewrite write endpoints

Write endpoints have gone an entire overhaul, and now will call the API conditionally to handle writes on behalf of apps if the EntityManager service is not initialized. In order to support this, and moving towards having this be the default path moving forward, the signatures for the writes have all changed to align with the autogenerated code from the API schema of the API write endpoints.

Non-exhaustive list of changes (see updated dev docs post release for complete breakdown of methods):

- Creating albums and playlists no longer lets you pass in track IDs separately. They must be part of the `playlistContents`
- No more `advancedOptions` in order to match the required schema for the write endpoints in API. For now there's no replacement. Reach out if you desire these abilities again.
- Most write method parameters now have top level `userId` and (if applicable) entity id (eg. `playlistId` for `updatePlaylist`) and a `metadata` field, to mirror the autogenerated code which separates the request path/query params and body. CommentsAPI for example has been affected greatly by this.
- `updateUserProfile` is now `updateUser`
- `addTrackToPlaylist`, `removeTrackToPlaylist` etc should _not_ be used going forward.
- `updateCoinRequest` is now `metadata` in `updateCoin` method parameters.
- USDC access gates for stream/download conditions now have splits formatted as a list with elements `user_id` and `percentage`
- Authentication can now be done via a Bearer token when using the API routes
36 changes: 27 additions & 9 deletions packages/common/src/adapters/accessConditionsFromSDK.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,46 @@
import type { full } from '@audius/sdk'
import type { AccessGate, full } from '@audius/sdk'
import {
instanceOfExtendedPurchaseGate,
instanceOfFollowGate,
instanceOfPurchaseGate,
instanceOfTokenGate,
instanceOfNftGate
} from '@audius/sdk/src/sdk/api/generated/full'
instanceOfTokenGate
} from '@audius/sdk'

import { AccessConditions } from '~/models'

/** Accepts default API AccessGate or full API AccessGate (e.g. from playlists). */
export const accessConditionsFromSDK = (
input: full.AccessGate
input: AccessGate | full.AccessGate
): AccessConditions | null => {
if (instanceOfFollowGate(input)) {
return { follow_user_id: input.followUserId }
} else if (instanceOfPurchaseGate(input)) {
return { usdc_purchase: input.usdcPurchase }
} else if (instanceOfExtendedPurchaseGate(input)) {
const purchase = input.usdcPurchase
const splits = Array.isArray(purchase.splits)
? purchase.splits.map((s) => ({
user_id: s.userId,
percentage: s.percentage,
payout_wallet: s.payoutWallet,
amount: s.amount,
...(s.ethWallet != null && { eth_wallet: s.ethWallet })
}))
: []
const albumTrackPrice = (purchase as { albumTrackPrice?: number })
.albumTrackPrice
return {
usdc_purchase: {
price: purchase.price,
...(albumTrackPrice != null && { albumTrackPrice }),
splits
}
}
} else if (instanceOfTokenGate(input)) {
return {
token_gate: {
token_mint: input.tokenGate.tokenMint,
token_amount: input.tokenGate.tokenAmount
}
}
} else if (instanceOfNftGate(input)) {
} else if ('nftCollection' in input && input.nftCollection != null) {
return null
} else {
throw new Error(`Unsupported access gate type: ${JSON.stringify(input)}`)
Expand Down
42 changes: 37 additions & 5 deletions packages/common/src/adapters/accessConditionsToSDK.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,46 @@
import type { TrackMetadata } from '@audius/sdk'
import type { CreateAlbumMetadata, TrackMetadata } from '@audius/sdk'

import {
AccessConditions,
isContentFollowGated,
isContentTokenGated,
isContentUSDCPurchaseGated
isContentUSDCPurchaseGated,
type PaymentSplit,
type USDCPurchaseConditions
} from '~/models'

/** Maps common USDC conditions to SDK format. Use for albums (stream conditions are USDC-only). */
export const usdcPurchaseConditionsToSDK = (
input: USDCPurchaseConditions
): NonNullable<CreateAlbumMetadata['streamConditions']> => {
const splits = input.usdc_purchase.splits ?? []
return {
usdcPurchase: {
price: input.usdc_purchase.price,
...(input.usdc_purchase.albumTrackPrice != null && {
albumTrackPrice: input.usdc_purchase.albumTrackPrice
}),
splits: splits.map(
(
s: PaymentSplit & {
userId?: number
payoutWallet?: string
ethWallet?: string
}
) => ({
userId: s.user_id ?? s.userId,
percentage: s.percentage,
payoutWallet: s.payout_wallet ?? s.payoutWallet ?? '',
amount: s.amount,
...((s.eth_wallet ?? s.ethWallet) != null && {
ethWallet: s.eth_wallet ?? s.ethWallet
})
})
)
}
}
}

export const accessConditionsToSDK = (
input: AccessConditions
): TrackMetadata['downloadConditions'] => {
Expand All @@ -15,9 +49,7 @@ export const accessConditionsToSDK = (
followUserId: input.follow_user_id
}
} else if (isContentUSDCPurchaseGated(input)) {
return {
usdcPurchase: input.usdc_purchase
}
return usdcPurchaseConditionsToSDK(input)
} else if (isContentTokenGated(input)) {
return {
tokenGate: {
Expand Down
73 changes: 44 additions & 29 deletions packages/common/src/adapters/collection.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import {
CreateAlbumMetadata,
CreatePlaylistMetadata,
type CreateAlbumRequestBody,
type CreatePlaylistRequestBody,
full,
Id,
OptionalHashId,
type Playlist,
type PlaylistAddedTimestamp,
UpdateAlbumRequest,
UpdatePlaylistRequest
type UpdateAlbumRequestBody,
type UpdatePlaylistRequestBody
} from '@audius/sdk'
import dayjs from 'dayjs'
import { omit } from 'lodash'
Expand All @@ -18,10 +21,11 @@ import {
UserCollectionMetadata,
Variant
} from '~/models/Collection'
import { Copyright } from '~/models/Track'
import { Copyright, isContentUSDCPurchaseGated } from '~/models/Track'
import type { AlbumValues, PlaylistValues } from '~/schemas'

import { accessConditionsFromSDK } from './accessConditionsFromSDK'
import { usdcPurchaseConditionsToSDK } from './accessConditionsToSDK'
import { resourceContributorFromSDK } from './attribution'
import { favoriteFromSDK } from './favorite'
import { coverArtSizesCIDsFromSDK } from './imageSize'
Expand All @@ -34,13 +38,13 @@ const addedTimestampToPlaylistTrackId = ({
timestamp,
trackId,
metadataTimestamp
}: full.PlaylistAddedTimestamp): PlaylistTrackId | null => {
}: PlaylistAddedTimestamp): PlaylistTrackId | null => {
const decoded = OptionalHashId.parse(trackId)
if (decoded) {
return {
track: decoded,
time: timestamp,
metadata_time: metadataTimestamp
metadata_time: metadataTimestamp ?? 0
}
}
return null
Expand All @@ -51,11 +55,14 @@ export const userCollectionMetadataFromSDK = (
| full.PlaylistFullWithoutTracks
| full.SearchPlaylistFull
| full.PlaylistFull
| Playlist
): UserCollectionMetadata | undefined => {
try {
const decodedPlaylistId = OptionalHashId.parse(input.id)
const decodedOwnerId = OptionalHashId.parse(input.userId ?? input.user.id)
const user = userMetadataFromSDK(input.user)
const decodedOwnerId = OptionalHashId.parse(
'userId' in input && input.userId != null ? input.userId : input.user.id
)
const user = userMetadataFromSDK(input.user as unknown as full.UserFull)
if (!decodedPlaylistId || !decodedOwnerId || !user) {
return undefined
}
Expand All @@ -74,7 +81,7 @@ export const userCollectionMetadataFromSDK = (
'150x150': input.artwork._150x150,
'480x480': input.artwork._480x480,
'1000x1000': input.artwork._1000x1000,
mirrors: input.artwork.mirrors
mirrors: (input.artwork as { mirrors?: string[] }).mirrors ?? []
}
: {},
variant: Variant.USER_GENERATED,
Expand All @@ -98,11 +105,11 @@ export const userCollectionMetadataFromSDK = (
? coverArtSizesCIDsFromSDK(input.coverArtCids)
: null,
followee_reposts: transformAndCleanList(
input.followeeReposts,
'followeeReposts' in input ? (input.followeeReposts ?? []) : [],
repostFromSDK
),
followee_saves: transformAndCleanList(
input.followeeFavorites,
'followeeFavorites' in input ? (input.followeeFavorites ?? []) : [],
favoriteFromSDK
),
playlist_contents: {
Expand All @@ -111,21 +118,30 @@ export const userCollectionMetadataFromSDK = (
addedTimestampToPlaylistTrackId
)
},
producer_copyright_line: input.producerCopyrightLine
? (snakecaseKeys(input.producerCopyrightLine) as Copyright)
: null,
stream_conditions: input.streamConditions
? accessConditionsFromSDK(input.streamConditions)
: null,
tracks: transformAndCleanList(input.tracks, userTrackMetadataFromSDK),
producer_copyright_line:
'producerCopyrightLine' in input && input.producerCopyrightLine
? (snakecaseKeys(input.producerCopyrightLine) as Copyright)
: null,
stream_conditions:
'streamConditions' in input && input.streamConditions
? accessConditionsFromSDK(input.streamConditions)
: null,
tracks: transformAndCleanList(
('tracks' in input ? (input.tracks ?? []) : []) as unknown as (
| full.TrackFull
| full.SearchTrackFull
)[],
userTrackMetadataFromSDK
),
user,

// Retypes / Renames
save_count: input.favoriteCount,

// Nullable fields
cover_art: input.coverArt ?? null,
cover_art_sizes: input.coverArtSizes ?? null,
cover_art: 'coverArt' in input ? (input.coverArt ?? null) : null,
cover_art_sizes:
'coverArtSizes' in input ? (input.coverArtSizes ?? null) : null,
description: input.description ?? null
}

Expand Down Expand Up @@ -159,7 +175,7 @@ export const accountCollectionFromSDK = (

export const playlistMetadataForCreateWithSDK = (
input: Collection | PlaylistValues
): CreatePlaylistMetadata => {
): CreatePlaylistRequestBody => {
return {
playlistName: input.playlist_name ?? '',
description: input.description ?? '',
Expand All @@ -171,7 +187,7 @@ export const playlistMetadataForCreateWithSDK = (
artists: input.artists ?? null,
copyrightLine: input.copyright_line ?? null,
producerCopyrightLine: input.producer_copyright_line ?? null,
parentalWarningType: input.parental_warning_type ?? null,
parentalWarningType: input.parental_warning_type ?? undefined,
...('cover_art_sizes' in input
? {
coverArtCid: input.cover_art_sizes ?? '',
Expand All @@ -183,7 +199,7 @@ export const playlistMetadataForCreateWithSDK = (

export const playlistMetadataForUpdateWithSDK = (
input: Collection
): UpdatePlaylistRequest['metadata'] => {
): UpdatePlaylistRequestBody => {
return {
...playlistMetadataForCreateWithSDK(input),
playlistContents: input.playlist_contents
Expand All @@ -202,13 +218,12 @@ export const playlistMetadataForUpdateWithSDK = (

export const albumMetadataForCreateWithSDK = (
input: Collection | AlbumValues
): CreateAlbumMetadata => {
): CreateAlbumRequestBody => {
return {
streamConditions:
input.stream_conditions && 'usdc_purchase' in input.stream_conditions
? {
usdcPurchase: input.stream_conditions.usdc_purchase
}
input.stream_conditions != null &&
isContentUSDCPurchaseGated(input.stream_conditions)
? usdcPurchaseConditionsToSDK(input.stream_conditions)
: null,
isStreamGated: input.is_stream_gated ?? false,
isScheduledRelease: input.is_scheduled_release ?? false,
Expand All @@ -229,7 +244,7 @@ export const albumMetadataForCreateWithSDK = (

export const albumMetadataForUpdateWithSDK = (
input: Collection
): UpdateAlbumRequest['metadata'] => {
): UpdateAlbumRequestBody => {
return {
...albumMetadataForCreateWithSDK(input),
playlistContents: input.playlist_contents
Expand Down
Loading
Loading