diff --git a/packages/common/src/api/tan-query/users/useUpdateProfile.ts b/packages/common/src/api/tan-query/users/useUpdateProfile.ts index d4939f16441..d37d044f804 100644 --- a/packages/common/src/api/tan-query/users/useUpdateProfile.ts +++ b/packages/common/src/api/tan-query/users/useUpdateProfile.ts @@ -44,8 +44,9 @@ export const useUpdateProfile = () => { } } - const { blockHash, blockNumber } = await sdk.users.updateProfile({ + const { blockHash, blockNumber } = await sdk.users.updateUser({ userId: Id.parse(currentUserId), + id: Id.parse(currentUserId), profilePictureFile: metadata.updatedProfilePicture?.file, coverArtFile: metadata.updatedCoverPhoto?.file, metadata: userMetadataToSdk(metadata) diff --git a/packages/sdk/src/sdk/api/albums/types.ts b/packages/sdk/src/sdk/api/albums/types.ts index ed25ecf70ec..eddc1849b85 100644 --- a/packages/sdk/src/sdk/api/albums/types.ts +++ b/packages/sdk/src/sdk/api/albums/types.ts @@ -8,13 +8,11 @@ import { HashId } from '../../types/HashId' import { Mood, Genre } from '../generated/default' import type { CreatePlaylistRequestBody, + CreateTrackRequestBody, UpdatePlaylistRequestBody } from '../generated/default' import type { UploadPlaylistProgressHandler } from '../playlists/types' -import { - UploadTrackMetadataSchema, - USDCPurchaseConditions -} from '../tracks/types' +import { USDCPurchaseConditions } from '../tracks/types' // Album request body types that wrap playlist types but use album field names export type CreateAlbumRequestBody = Omit< @@ -135,16 +133,6 @@ export const UploadAlbumMetadataSchema = CreateAlbumMetadataSchema.extend({ export type AlbumMetadata = z.input -const AlbumTrackMetadataSchema = UploadTrackMetadataSchema.partial({ - genre: true, - mood: true, - tags: true, - isStreamGated: true, - streamConditions: true, - isDownloadable: true, - downloadConditions: true -}) - export const UpdateAlbumMetadataSchema = UploadAlbumMetadataSchema.partial() .merge( z.object({ @@ -161,21 +149,17 @@ export const UpdateAlbumMetadataSchema = UploadAlbumMetadataSchema.partial() ) .strict() -export const UploadAlbumSchema = z - .object({ - userId: HashId, - imageFile: ImageFile, - metadata: UploadAlbumMetadataSchema, - onProgress: z.optional(z.function()), - /** - * Track metadata is populated from the album if fields are missing - */ - trackMetadatas: z.array(AlbumTrackMetadataSchema), - audioFiles: z.array(AudioFile) - }) - .strict() +export const UploadAlbumSchema = z.object({ + userId: HashId, + imageFile: ImageFile, + onProgress: z.optional(z.function()), + audioFiles: z.array(AudioFile) +}) -export type UploadAlbumRequest = z.input +export type UploadAlbumRequest = z.input & { + metadata: CreateAlbumRequestBody + trackMetadatas: CreateTrackRequestBody[] +} export const UpdateAlbumSchema = z .object({ diff --git a/packages/sdk/src/sdk/api/comments/CommentsAPI.ts b/packages/sdk/src/sdk/api/comments/CommentsAPI.ts index 3c58fba3d1d..2b7dc9fbaf9 100644 --- a/packages/sdk/src/sdk/api/comments/CommentsAPI.ts +++ b/packages/sdk/src/sdk/api/comments/CommentsAPI.ts @@ -1,13 +1,15 @@ import snakecaseKeys from 'snakecase-keys' +import { OverrideProperties } from 'type-fest' import { LoggerService } from '../../services' import { Action, EntityManagerService, - EntityType + EntityType, + type ManageEntityOptions } from '../../services/EntityManager/types' +import { HashId } from '../../types/HashId' import { decodeHashId, encodeHashId } from '../../utils/hashId' -import { parseParams } from '../../utils/parseParams' import { Configuration, CommentsApi as GeneratedCommentsApi, @@ -21,21 +23,30 @@ import { type ReportCommentRequest } from '../generated/default' -import { - CreateCommentSchema, - UpdateCommentSchema, - DeleteCommentSchema, - PinCommentSchema, - ReactCommentSchema, - ReportCommentSchema, - EntityManagerCreateCommentRequest, - EntityManagerUpdateCommentRequest, - EntityManagerDeleteCommentRequest, - EntityManagerPinCommentRequest, - EntityManagerReactCommentRequest, - EntityManagerReportCommentRequest -} from './types' +import type { CommentMetadata } from './types' + +type EditCommentMetadata = CommentMetadata & { + trackId: number +} + +type PinCommentMetadata = { + userId: number + entityId: number + trackId: number + isPin: boolean +} +type ReactCommentMetadata = { + userId: number + commentId: number + isLiked: boolean + trackId: number +} + +type CommentNotificationOptions = OverrideProperties< + Omit, + { action: Action.MUTE | Action.UNMUTE } +> export class CommentsApi extends GeneratedCommentsApi { constructor( configuration: Configuration, @@ -57,13 +68,7 @@ export class CommentsApi extends GeneratedCommentsApi { /** @hidden * Create a comment using entity manager */ - async createCommentWithEntityManager( - params: EntityManagerCreateCommentRequest - ) { - const metadata = await parseParams( - 'createComment', - CreateCommentSchema - )(params) + async createCommentWithEntityManager(metadata: CommentMetadata) { const { userId, entityType = EntityType.TRACK, commentId } = metadata const newCommentId = commentId ?? (await this.generateCommentId()) await this.entityManager.manageEntity({ @@ -87,8 +92,8 @@ export class CommentsApi extends GeneratedCommentsApi { if (this.entityManager) { const { metadata, userId } = params const commentId = await this.createCommentWithEntityManager({ - userId, - entityId: encodeHashId(metadata.entityId) ?? '', + userId: HashId.parse(userId), + entityId: metadata.entityId, entityType: metadata.entityType, body: metadata.body, commentId: metadata.commentId, @@ -107,14 +112,8 @@ export class CommentsApi extends GeneratedCommentsApi { /** @hidden * Update a comment using entity manager */ - async updateCommentWithEntityManager( - params: EntityManagerUpdateCommentRequest - ) { - const metadata = await parseParams( - 'updateComment', - UpdateCommentSchema - )(params) - const { userId, entityId, trackId, body } = metadata + async updateCommentWithEntityManager(metadata: EditCommentMetadata) { + const { userId, entityId, trackId } = metadata const response = await this.entityManager.manageEntity({ userId, entityType: EntityType.COMMENT, @@ -122,7 +121,7 @@ export class CommentsApi extends GeneratedCommentsApi { action: Action.UPDATE, metadata: JSON.stringify({ cid: '', - data: snakecaseKeys({ body, entityId: trackId }) + data: snakecaseKeys({ ...metadata, entityId: trackId }) }) }) return response @@ -134,15 +133,12 @@ export class CommentsApi extends GeneratedCommentsApi { ) { if (this.entityManager) { const { metadata, userId, commentId } = params - await this.updateCommentWithEntityManager({ - userId, - entityId: commentId, - trackId: encodeHashId(metadata.entityId) ?? '', + return await this.updateCommentWithEntityManager({ + userId: HashId.parse(userId), + entityId: HashId.parse(commentId), + trackId: HashId.parse(commentId), body: metadata.body }) - return { - success: true - } } return super.updateComment(params, requestInit) } @@ -150,13 +146,7 @@ export class CommentsApi extends GeneratedCommentsApi { /** @hidden * Delete a comment using entity manager */ - async deleteCommentWithEntityManager( - params: EntityManagerDeleteCommentRequest - ) { - const metadata = await parseParams( - 'deleteComment', - DeleteCommentSchema - )(params) + async deleteCommentWithEntityManager(metadata: CommentMetadata) { const { userId, entityId } = metadata const response = await this.entityManager.manageEntity({ userId, @@ -173,14 +163,10 @@ export class CommentsApi extends GeneratedCommentsApi { requestInit?: RequestInit ) { if (this.entityManager) { - const metadata: EntityManagerDeleteCommentRequest = { - userId: params.userId, - entityId: params.commentId - } - await this.deleteCommentWithEntityManager(metadata) - return { - success: true - } + return await this.deleteCommentWithEntityManager({ + userId: HashId.parse(params.userId), + entityId: HashId.parse(params.commentId) + }) } return super.deleteComment(params, requestInit) } @@ -188,13 +174,7 @@ export class CommentsApi extends GeneratedCommentsApi { /** @hidden * React to a comment using entity manager */ - async reactToCommentWithEntityManager( - params: EntityManagerReactCommentRequest - ) { - const metadata = await parseParams( - 'reactComment', - ReactCommentSchema - )(params) + async reactToCommentWithEntityManager(metadata: ReactCommentMetadata) { const { userId, commentId, isLiked, trackId } = metadata const response = await this.entityManager.manageEntity({ userId, @@ -214,16 +194,12 @@ export class CommentsApi extends GeneratedCommentsApi { requestInit?: RequestInit ) { if (this.entityManager) { - const metadata: EntityManagerReactCommentRequest = { - userId: params.userId, - commentId: params.commentId, + return await this.reactToCommentWithEntityManager({ + userId: HashId.parse(params.userId), + commentId: HashId.parse(params.commentId), isLiked: true, - trackId: params.commentId // trackId represents the entity being commented on - } - await this.reactToCommentWithEntityManager(metadata) - return { - success: true - } + trackId: HashId.parse(params.commentId) + }) } return super.reactToComment(params, requestInit) } @@ -233,16 +209,12 @@ export class CommentsApi extends GeneratedCommentsApi { requestInit?: RequestInit ) { if (this.entityManager) { - const metadata: EntityManagerReactCommentRequest = { - userId: params.userId, - commentId: params.commentId, - isLiked: false, - trackId: params.commentId - } - await this.reactToCommentWithEntityManager(metadata) - return { - success: true - } + return await this.reactToCommentWithEntityManager({ + userId: HashId.parse(params.userId), + commentId: HashId.parse(params.commentId), + isLiked: true, + trackId: HashId.parse(params.commentId) + }) } return super.unreactToComment(params, requestInit) } @@ -250,8 +222,7 @@ export class CommentsApi extends GeneratedCommentsApi { /** @hidden * Pin a comment using entity manager */ - async pinCommentWithEntityManager(params: EntityManagerPinCommentRequest) { - const metadata = await parseParams('pinComment', PinCommentSchema)(params) + async pinCommentWithEntityManager(metadata: PinCommentMetadata) { const { userId, entityId, trackId, isPin } = metadata const response = await this.entityManager.manageEntity({ userId, @@ -271,16 +242,12 @@ export class CommentsApi extends GeneratedCommentsApi { requestInit?: RequestInit ) { if (this.entityManager) { - const metadata: EntityManagerPinCommentRequest = { - userId: params.userId, - entityId: params.commentId, - trackId: params.commentId, // trackId represents the entity being commented on + return await this.pinCommentWithEntityManager({ + userId: HashId.parse(params.userId), + entityId: HashId.parse(params.commentId), + trackId: HashId.parse(params.commentId), isPin: true - } - await this.pinCommentWithEntityManager(metadata) - return { - success: true - } + }) } return super.pinComment(params, requestInit) } @@ -290,16 +257,12 @@ export class CommentsApi extends GeneratedCommentsApi { requestInit?: RequestInit ) { if (this.entityManager) { - const metadata: EntityManagerPinCommentRequest = { - userId: params.userId, - entityId: params.commentId, - trackId: params.commentId, + return await this.pinCommentWithEntityManager({ + userId: HashId.parse(params.userId), + entityId: HashId.parse(params.commentId), + trackId: HashId.parse(params.commentId), isPin: false - } - await this.pinCommentWithEntityManager(metadata) - return { - success: true - } + }) } return super.unpinComment(params, requestInit) } @@ -307,14 +270,7 @@ export class CommentsApi extends GeneratedCommentsApi { /** @hidden * Report a comment using entity manager */ - async reportCommentWithEntityManager( - params: EntityManagerReportCommentRequest - ) { - const metadata = await parseParams( - 'reportComment', - ReportCommentSchema - )(params) - const { userId, entityId } = metadata + async reportCommentWithEntityManager(userId: number, entityId: number) { const response = await this.entityManager.manageEntity({ userId, entityType: EntityType.COMMENT, @@ -330,14 +286,10 @@ export class CommentsApi extends GeneratedCommentsApi { requestInit?: RequestInit ) { if (this.entityManager) { - const metadata: EntityManagerReportCommentRequest = { - userId: params.userId, - entityId: params.commentId - } - await this.reportCommentWithEntityManager(metadata) - return { - success: true - } + return await this.reportCommentWithEntityManager( + HashId.parse(params.userId), + HashId.parse(params.commentId) + ) } return super.reportComment(params, requestInit) } @@ -359,12 +311,7 @@ export class CommentsApi extends GeneratedCommentsApi { /** @hidden * Update comment notification settings (entity manager only) */ - async updateCommentNotificationSetting(config: { - userId: number - entityType: EntityType - entityId: number - action: Action.MUTE | Action.UNMUTE - }) { + async updateCommentNotificationSetting(config: CommentNotificationOptions) { const response = await this.entityManager.manageEntity({ ...config, metadata: '' diff --git a/packages/sdk/src/sdk/api/comments/types.ts b/packages/sdk/src/sdk/api/comments/types.ts index 680e24e5e1f..3a7d287d57e 100644 --- a/packages/sdk/src/sdk/api/comments/types.ts +++ b/packages/sdk/src/sdk/api/comments/types.ts @@ -1,6 +1,3 @@ -import { z } from 'zod' - -import { HashId } from '../../types/HashId' import type { CommentEntityType } from '../generated/default' export type CommentMetadata = { @@ -13,80 +10,3 @@ export type CommentMetadata = { trackTimestampS?: number mentions?: number[] } - -// Zod schemas for dual-auth support -export const CreateCommentSchema = z - .object({ - userId: HashId, - entityId: HashId, - entityType: z.optional(z.string()), - body: z.optional(z.string()), - commentId: z.optional(z.number()), - parentCommentId: z.optional(z.number()), - trackTimestampS: z.optional(z.number()), - mentions: z.optional(z.array(z.number())) - }) - .strict() - -export type EntityManagerCreateCommentRequest = z.input< - typeof CreateCommentSchema -> - -export const UpdateCommentSchema = z - .object({ - userId: HashId, - entityId: HashId, - trackId: HashId, - body: z.string() - }) - .strict() - -export type EntityManagerUpdateCommentRequest = z.input< - typeof UpdateCommentSchema -> - -export const DeleteCommentSchema = z - .object({ - userId: HashId, - entityId: HashId - }) - .strict() - -export type EntityManagerDeleteCommentRequest = z.input< - typeof DeleteCommentSchema -> - -export const PinCommentSchema = z - .object({ - userId: HashId, - entityId: HashId, - trackId: HashId, - isPin: z.boolean() - }) - .strict() - -export type EntityManagerPinCommentRequest = z.input - -export const ReactCommentSchema = z - .object({ - userId: HashId, - commentId: HashId, - isLiked: z.boolean(), - trackId: HashId - }) - .strict() - -export type EntityManagerReactCommentRequest = z.input< - typeof ReactCommentSchema -> - -export const ReportCommentSchema = z - .object({ - userId: HashId, - entityId: HashId - }) - .strict() - -export type EntityManagerReportCommentRequest = z.input< - typeof ReportCommentSchema -> diff --git a/packages/sdk/src/sdk/api/generated/default/.openapi-generator/FILES b/packages/sdk/src/sdk/api/generated/default/.openapi-generator/FILES index 17978a7a286..e763e312d54 100644 --- a/packages/sdk/src/sdk/api/generated/default/.openapi-generator/FILES +++ b/packages/sdk/src/sdk/api/generated/default/.openapi-generator/FILES @@ -80,8 +80,6 @@ models/CreateUserDeveloperAppRequestBody.ts models/CreateUserDeveloperAppResponse.ts models/CreateUserRequestBody.ts models/CreateUserRequestBodyEvents.ts -models/CreateUserRequestBodyPlaylistLibrary.ts -models/CreateUserRequestBodyPlaylistLibraryContentsInner.ts models/CreateUserResponse.ts models/DashboardWalletUser.ts models/DashboardWalletUsersResponse.ts @@ -198,7 +196,6 @@ models/UpdateDeveloperAppRequestBody.ts models/UpdatePlaylistRequestBody.ts models/UpdateTrackRequestBody.ts models/UpdateUserRequestBody.ts -models/UpdateUserRequestBodyPlaylistLibrary.ts models/User.ts models/UserCoin.ts models/UserCoinAccount.ts @@ -208,6 +205,8 @@ models/UserCoinsResponse.ts models/UserCommentsResponse.ts models/UserIdAddress.ts models/UserIdsAddressesResponse.ts +models/UserPlaylistLibrary.ts +models/UserPlaylistLibraryContentsInner.ts models/UserResponse.ts models/UserSearch.ts models/UserTrackListenCountsResponse.ts diff --git a/packages/sdk/src/sdk/api/generated/default/models/CreateCommentRequestBody.ts b/packages/sdk/src/sdk/api/generated/default/models/CreateCommentRequestBody.ts index 381ab926df6..604fa946cca 100644 --- a/packages/sdk/src/sdk/api/generated/default/models/CreateCommentRequestBody.ts +++ b/packages/sdk/src/sdk/api/generated/default/models/CreateCommentRequestBody.ts @@ -46,7 +46,7 @@ export interface CreateCommentRequestBody { */ body: string; /** - * Optional comment ID (will be generated if not provided) + * Optional ID for the comment (will be generated if not provided) * @type {number} * @memberof CreateCommentRequestBody */ diff --git a/packages/sdk/src/sdk/api/generated/default/models/CreatePlaylistRequestBody.ts b/packages/sdk/src/sdk/api/generated/default/models/CreatePlaylistRequestBody.ts index 7d785fbb972..93e137da69f 100644 --- a/packages/sdk/src/sdk/api/generated/default/models/CreatePlaylistRequestBody.ts +++ b/packages/sdk/src/sdk/api/generated/default/models/CreatePlaylistRequestBody.ts @@ -41,10 +41,10 @@ import { export interface CreatePlaylistRequestBody { /** * Optional playlist ID (will be generated if not provided) - * @type {number} + * @type {string} * @memberof CreatePlaylistRequestBody */ - playlistId?: number; + playlistId?: string; /** * Playlist or album name * @type {string} diff --git a/packages/sdk/src/sdk/api/generated/default/models/CreateTrackRequestBody.ts b/packages/sdk/src/sdk/api/generated/default/models/CreateTrackRequestBody.ts index 56464ce5f51..13a078f174a 100644 --- a/packages/sdk/src/sdk/api/generated/default/models/CreateTrackRequestBody.ts +++ b/packages/sdk/src/sdk/api/generated/default/models/CreateTrackRequestBody.ts @@ -59,10 +59,10 @@ import { export interface CreateTrackRequestBody { /** * Optional track ID (will be generated if not provided) - * @type {number} + * @type {string} * @memberof CreateTrackRequestBody */ - trackId?: number; + trackId?: string; /** * Track title * @type {string} @@ -182,7 +182,7 @@ export interface CreateTrackRequestBody { * @type {boolean} * @memberof CreateTrackRequestBody */ - downloadable?: boolean; + isDownloadable?: boolean; /** * Whether the track is unlisted * @type {boolean} @@ -281,7 +281,7 @@ export function CreateTrackRequestBodyFromJSONTyped(json: any, ignoreDiscriminat 'previewCid': !exists(json, 'preview_cid') ? undefined : json['preview_cid'], 'previewStartSeconds': !exists(json, 'preview_start_seconds') ? undefined : json['preview_start_seconds'], 'duration': !exists(json, 'duration') ? undefined : json['duration'], - 'downloadable': !exists(json, 'downloadable') ? undefined : json['downloadable'], + 'isDownloadable': !exists(json, 'is_downloadable') ? undefined : json['is_downloadable'], 'isUnlisted': !exists(json, 'is_unlisted') ? undefined : json['is_unlisted'], 'streamConditions': !exists(json, 'stream_conditions') ? undefined : AccessGateFromJSON(json['stream_conditions']), 'downloadConditions': !exists(json, 'download_conditions') ? undefined : AccessGateFromJSON(json['download_conditions']), @@ -323,7 +323,7 @@ export function CreateTrackRequestBodyToJSON(value?: CreateTrackRequestBody | nu 'preview_cid': value.previewCid, 'preview_start_seconds': value.previewStartSeconds, 'duration': value.duration, - 'downloadable': value.downloadable, + 'is_downloadable': value.isDownloadable, 'is_unlisted': value.isUnlisted, 'stream_conditions': AccessGateToJSON(value.streamConditions), 'download_conditions': AccessGateToJSON(value.downloadConditions), diff --git a/packages/sdk/src/sdk/api/generated/default/models/CreateUserRequestBody.ts b/packages/sdk/src/sdk/api/generated/default/models/CreateUserRequestBody.ts index 8081128a150..33a1febb2bb 100644 --- a/packages/sdk/src/sdk/api/generated/default/models/CreateUserRequestBody.ts +++ b/packages/sdk/src/sdk/api/generated/default/models/CreateUserRequestBody.ts @@ -20,12 +20,12 @@ import { CreateUserRequestBodyEventsFromJSONTyped, CreateUserRequestBodyEventsToJSON, } from './CreateUserRequestBodyEvents'; -import type { CreateUserRequestBodyPlaylistLibrary } from './CreateUserRequestBodyPlaylistLibrary'; +import type { UserPlaylistLibrary } from './UserPlaylistLibrary'; import { - CreateUserRequestBodyPlaylistLibraryFromJSON, - CreateUserRequestBodyPlaylistLibraryFromJSONTyped, - CreateUserRequestBodyPlaylistLibraryToJSON, -} from './CreateUserRequestBodyPlaylistLibrary'; + UserPlaylistLibraryFromJSON, + UserPlaylistLibraryFromJSONTyped, + UserPlaylistLibraryToJSON, +} from './UserPlaylistLibrary'; /** * @@ -34,11 +34,11 @@ import { */ export interface CreateUserRequestBody { /** - * Optional user ID (will be generated if not provided) - * @type {number} + * Optional user hash ID (will be generated if not provided) + * @type {string} * @memberof CreateUserRequestBody */ - userId?: number; + userId?: string; /** * User handle (unique username) * @type {string} @@ -143,10 +143,10 @@ export interface CreateUserRequestBody { splUsdcPayoutWallet?: string; /** * - * @type {CreateUserRequestBodyPlaylistLibrary} + * @type {UserPlaylistLibrary} * @memberof CreateUserRequestBody */ - playlistLibrary?: CreateUserRequestBodyPlaylistLibrary; + playlistLibrary?: UserPlaylistLibrary; /** * * @type {CreateUserRequestBodyEvents} @@ -204,7 +204,7 @@ export function CreateUserRequestBodyFromJSONTyped(json: any, ignoreDiscriminato 'profileType': !exists(json, 'profile_type') ? undefined : json['profile_type'], 'allowAiAttribution': !exists(json, 'allow_ai_attribution') ? undefined : json['allow_ai_attribution'], 'splUsdcPayoutWallet': !exists(json, 'spl_usdc_payout_wallet') ? undefined : json['spl_usdc_payout_wallet'], - 'playlistLibrary': !exists(json, 'playlist_library') ? undefined : CreateUserRequestBodyPlaylistLibraryFromJSON(json['playlist_library']), + 'playlistLibrary': !exists(json, 'playlist_library') ? undefined : UserPlaylistLibraryFromJSON(json['playlist_library']), 'events': !exists(json, 'events') ? undefined : CreateUserRequestBodyEventsFromJSON(json['events']), }; } @@ -236,7 +236,7 @@ export function CreateUserRequestBodyToJSON(value?: CreateUserRequestBody | null 'profile_type': value.profileType, 'allow_ai_attribution': value.allowAiAttribution, 'spl_usdc_payout_wallet': value.splUsdcPayoutWallet, - 'playlist_library': CreateUserRequestBodyPlaylistLibraryToJSON(value.playlistLibrary), + 'playlist_library': UserPlaylistLibraryToJSON(value.playlistLibrary), 'events': CreateUserRequestBodyEventsToJSON(value.events), }; } diff --git a/packages/sdk/src/sdk/api/generated/default/models/CreateUserRequestBodyEvents.ts b/packages/sdk/src/sdk/api/generated/default/models/CreateUserRequestBodyEvents.ts index 8a5ddfd732a..88a8c3a5a64 100644 --- a/packages/sdk/src/sdk/api/generated/default/models/CreateUserRequestBodyEvents.ts +++ b/packages/sdk/src/sdk/api/generated/default/models/CreateUserRequestBodyEvents.ts @@ -21,11 +21,11 @@ import { exists, mapValues } from '../runtime'; */ export interface CreateUserRequestBodyEvents { /** - * User ID of the referrer - * @type {number} + * Hash ID of the user who referred this user + * @type {string} * @memberof CreateUserRequestBodyEvents */ - referrer?: number; + referrer?: string; /** * Whether the user is on mobile * @type {boolean} diff --git a/packages/sdk/src/sdk/api/generated/default/models/CreateUserRequestBodyPlaylistLibrary.ts b/packages/sdk/src/sdk/api/generated/default/models/CreateUserRequestBodyPlaylistLibrary.ts deleted file mode 100644 index 3c9c650152d..00000000000 --- a/packages/sdk/src/sdk/api/generated/default/models/CreateUserRequestBodyPlaylistLibrary.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// @ts-nocheck -/** - * API - * Audius V1 API - * - * The version of the OpenAPI document: 1.0 - * - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -import type { CreateUserRequestBodyPlaylistLibraryContentsInner } from './CreateUserRequestBodyPlaylistLibraryContentsInner'; -import { - CreateUserRequestBodyPlaylistLibraryContentsInnerFromJSON, - CreateUserRequestBodyPlaylistLibraryContentsInnerFromJSONTyped, - CreateUserRequestBodyPlaylistLibraryContentsInnerToJSON, -} from './CreateUserRequestBodyPlaylistLibraryContentsInner'; - -/** - * User's playlist library with support for folders and playlists - * @export - * @interface CreateUserRequestBodyPlaylistLibrary - */ -export interface CreateUserRequestBodyPlaylistLibrary { - /** - * Array of folders and playlist identifiers - * @type {Array} - * @memberof CreateUserRequestBodyPlaylistLibrary - */ - contents: Array; -} - -/** - * Check if a given object implements the CreateUserRequestBodyPlaylistLibrary interface. - */ -export function instanceOfCreateUserRequestBodyPlaylistLibrary(value: object): value is CreateUserRequestBodyPlaylistLibrary { - let isInstance = true; - isInstance = isInstance && "contents" in value && value["contents"] !== undefined; - - return isInstance; -} - -export function CreateUserRequestBodyPlaylistLibraryFromJSON(json: any): CreateUserRequestBodyPlaylistLibrary { - return CreateUserRequestBodyPlaylistLibraryFromJSONTyped(json, false); -} - -export function CreateUserRequestBodyPlaylistLibraryFromJSONTyped(json: any, ignoreDiscriminator: boolean): CreateUserRequestBodyPlaylistLibrary { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'contents': ((json['contents'] as Array).map(CreateUserRequestBodyPlaylistLibraryContentsInnerFromJSON)), - }; -} - -export function CreateUserRequestBodyPlaylistLibraryToJSON(value?: CreateUserRequestBodyPlaylistLibrary | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'contents': ((value.contents as Array).map(CreateUserRequestBodyPlaylistLibraryContentsInnerToJSON)), - }; -} - diff --git a/packages/sdk/src/sdk/api/generated/default/models/PinCommentRequestBody.ts b/packages/sdk/src/sdk/api/generated/default/models/PinCommentRequestBody.ts index 27171bf5c0b..ef43a072a64 100644 --- a/packages/sdk/src/sdk/api/generated/default/models/PinCommentRequestBody.ts +++ b/packages/sdk/src/sdk/api/generated/default/models/PinCommentRequestBody.ts @@ -34,7 +34,7 @@ export interface PinCommentRequestBody { */ entityType: CommentEntityType; /** - * ID of the entity (track or playlist) the comment is on + * ID of the entity (track) the comment is on * @type {number} * @memberof PinCommentRequestBody */ diff --git a/packages/sdk/src/sdk/api/generated/default/models/PlaylistLibraryFolder.ts b/packages/sdk/src/sdk/api/generated/default/models/PlaylistLibraryFolder.ts index 78280c871ab..82176824092 100644 --- a/packages/sdk/src/sdk/api/generated/default/models/PlaylistLibraryFolder.ts +++ b/packages/sdk/src/sdk/api/generated/default/models/PlaylistLibraryFolder.ts @@ -14,12 +14,12 @@ */ import { exists, mapValues } from '../runtime'; -import type { CreateUserRequestBodyPlaylistLibraryContentsInner } from './CreateUserRequestBodyPlaylistLibraryContentsInner'; +import type { UserPlaylistLibraryContentsInner } from './UserPlaylistLibraryContentsInner'; import { - CreateUserRequestBodyPlaylistLibraryContentsInnerFromJSON, - CreateUserRequestBodyPlaylistLibraryContentsInnerFromJSONTyped, - CreateUserRequestBodyPlaylistLibraryContentsInnerToJSON, -} from './CreateUserRequestBodyPlaylistLibraryContentsInner'; + UserPlaylistLibraryContentsInnerFromJSON, + UserPlaylistLibraryContentsInnerFromJSONTyped, + UserPlaylistLibraryContentsInnerToJSON, +} from './UserPlaylistLibraryContentsInner'; /** * Folder containing nested playlists and folders @@ -47,10 +47,10 @@ export interface PlaylistLibraryFolder { name: string; /** * Nested folders and playlist identifiers - * @type {Array} + * @type {Array} * @memberof PlaylistLibraryFolder */ - contents: Array; + contents: Array; } @@ -89,7 +89,7 @@ export function PlaylistLibraryFolderFromJSONTyped(json: any, ignoreDiscriminato 'id': json['id'], 'type': json['type'], 'name': json['name'], - 'contents': ((json['contents'] as Array).map(CreateUserRequestBodyPlaylistLibraryContentsInnerFromJSON)), + 'contents': ((json['contents'] as Array).map(UserPlaylistLibraryContentsInnerFromJSON)), }; } @@ -105,7 +105,7 @@ export function PlaylistLibraryFolderToJSON(value?: PlaylistLibraryFolder | null 'id': value.id, 'type': value.type, 'name': value.name, - 'contents': ((value.contents as Array).map(CreateUserRequestBodyPlaylistLibraryContentsInnerToJSON)), + 'contents': ((value.contents as Array).map(UserPlaylistLibraryContentsInnerToJSON)), }; } diff --git a/packages/sdk/src/sdk/api/generated/default/models/ReactCommentRequestBody.ts b/packages/sdk/src/sdk/api/generated/default/models/ReactCommentRequestBody.ts index f206127caa7..21400696514 100644 --- a/packages/sdk/src/sdk/api/generated/default/models/ReactCommentRequestBody.ts +++ b/packages/sdk/src/sdk/api/generated/default/models/ReactCommentRequestBody.ts @@ -34,7 +34,7 @@ export interface ReactCommentRequestBody { */ entityType: CommentEntityType; /** - * ID of the entity (track or playlist) being commented on + * ID of the entity (track) being commented on * @type {number} * @memberof ReactCommentRequestBody */ diff --git a/packages/sdk/src/sdk/api/generated/default/models/StemParent.ts b/packages/sdk/src/sdk/api/generated/default/models/StemParent.ts index 4d844213e42..7a27fca93f6 100644 --- a/packages/sdk/src/sdk/api/generated/default/models/StemParent.ts +++ b/packages/sdk/src/sdk/api/generated/default/models/StemParent.ts @@ -35,10 +35,10 @@ export interface StemParent { category: StemCategory; /** * - * @type {number} + * @type {string} * @memberof StemParent */ - parentTrackId: number; + parentTrackId: string; } /** diff --git a/packages/sdk/src/sdk/api/generated/default/models/Track.ts b/packages/sdk/src/sdk/api/generated/default/models/Track.ts index c95aa313041..55094602de8 100644 --- a/packages/sdk/src/sdk/api/generated/default/models/Track.ts +++ b/packages/sdk/src/sdk/api/generated/default/models/Track.ts @@ -239,10 +239,10 @@ export interface Track { access?: TrackAccessInfo; /** * - * @type {number} + * @type {string} * @memberof Track */ - aiAttributionUserId?: number; + aiAttributionUserId?: string; /** * * @type {Array} diff --git a/packages/sdk/src/sdk/api/generated/default/models/TrackElementWrite.ts b/packages/sdk/src/sdk/api/generated/default/models/TrackElementWrite.ts index 1becc276367..d7d2d8b9c21 100644 --- a/packages/sdk/src/sdk/api/generated/default/models/TrackElementWrite.ts +++ b/packages/sdk/src/sdk/api/generated/default/models/TrackElementWrite.ts @@ -22,10 +22,10 @@ import { exists, mapValues } from '../runtime'; export interface TrackElementWrite { /** * - * @type {number} + * @type {string} * @memberof TrackElementWrite */ - parentTrackId: number; + parentTrackId: string; } /** diff --git a/packages/sdk/src/sdk/api/generated/default/models/UpdateTrackRequestBody.ts b/packages/sdk/src/sdk/api/generated/default/models/UpdateTrackRequestBody.ts index 310537dee35..0e3deb068c3 100644 --- a/packages/sdk/src/sdk/api/generated/default/models/UpdateTrackRequestBody.ts +++ b/packages/sdk/src/sdk/api/generated/default/models/UpdateTrackRequestBody.ts @@ -176,7 +176,7 @@ export interface UpdateTrackRequestBody { * @type {boolean} * @memberof UpdateTrackRequestBody */ - downloadable?: boolean; + isDownloadable?: boolean; /** * Whether the track is unlisted * @type {boolean} @@ -271,7 +271,7 @@ export function UpdateTrackRequestBodyFromJSONTyped(json: any, ignoreDiscriminat 'previewCid': !exists(json, 'preview_cid') ? undefined : json['preview_cid'], 'previewStartSeconds': !exists(json, 'preview_start_seconds') ? undefined : json['preview_start_seconds'], 'duration': !exists(json, 'duration') ? undefined : json['duration'], - 'downloadable': !exists(json, 'downloadable') ? undefined : json['downloadable'], + 'isDownloadable': !exists(json, 'is_downloadable') ? undefined : json['is_downloadable'], 'isUnlisted': !exists(json, 'is_unlisted') ? undefined : json['is_unlisted'], 'streamConditions': !exists(json, 'stream_conditions') ? undefined : AccessGateFromJSON(json['stream_conditions']), 'downloadConditions': !exists(json, 'download_conditions') ? undefined : AccessGateFromJSON(json['download_conditions']), @@ -312,7 +312,7 @@ export function UpdateTrackRequestBodyToJSON(value?: UpdateTrackRequestBody | nu 'preview_cid': value.previewCid, 'preview_start_seconds': value.previewStartSeconds, 'duration': value.duration, - 'downloadable': value.downloadable, + 'is_downloadable': value.isDownloadable, 'is_unlisted': value.isUnlisted, 'stream_conditions': AccessGateToJSON(value.streamConditions), 'download_conditions': AccessGateToJSON(value.downloadConditions), diff --git a/packages/sdk/src/sdk/api/generated/default/models/UpdateUserRequestBody.ts b/packages/sdk/src/sdk/api/generated/default/models/UpdateUserRequestBody.ts index 524e27e6489..e087b36a53c 100644 --- a/packages/sdk/src/sdk/api/generated/default/models/UpdateUserRequestBody.ts +++ b/packages/sdk/src/sdk/api/generated/default/models/UpdateUserRequestBody.ts @@ -20,12 +20,12 @@ import { CreateUserRequestBodyEventsFromJSONTyped, CreateUserRequestBodyEventsToJSON, } from './CreateUserRequestBodyEvents'; -import type { UpdateUserRequestBodyPlaylistLibrary } from './UpdateUserRequestBodyPlaylistLibrary'; +import type { UserPlaylistLibrary } from './UserPlaylistLibrary'; import { - UpdateUserRequestBodyPlaylistLibraryFromJSON, - UpdateUserRequestBodyPlaylistLibraryFromJSONTyped, - UpdateUserRequestBodyPlaylistLibraryToJSON, -} from './UpdateUserRequestBodyPlaylistLibrary'; + UserPlaylistLibraryFromJSON, + UserPlaylistLibraryFromJSONTyped, + UserPlaylistLibraryToJSON, +} from './UserPlaylistLibrary'; /** * Request body for updating user profile. All fields are optional. @@ -118,11 +118,11 @@ export interface UpdateUserRequestBody { */ isDeactivated?: boolean; /** - * Track ID to feature as artist pick - * @type {number} + * Track hash ID to feature as artist pick + * @type {string} * @memberof UpdateUserRequestBody */ - artistPickTrackId?: number; + artistPickTrackId?: string; /** * Whether to allow AI attribution * @type {boolean} @@ -143,10 +143,10 @@ export interface UpdateUserRequestBody { coinFlairMint?: string; /** * - * @type {UpdateUserRequestBodyPlaylistLibrary} + * @type {UserPlaylistLibrary} * @memberof UpdateUserRequestBody */ - playlistLibrary?: UpdateUserRequestBodyPlaylistLibrary; + playlistLibrary?: UserPlaylistLibrary; /** * * @type {CreateUserRequestBodyEvents} @@ -202,7 +202,7 @@ export function UpdateUserRequestBodyFromJSONTyped(json: any, ignoreDiscriminato 'allowAiAttribution': !exists(json, 'allow_ai_attribution') ? undefined : json['allow_ai_attribution'], 'splUsdcPayoutWallet': !exists(json, 'spl_usdc_payout_wallet') ? undefined : json['spl_usdc_payout_wallet'], 'coinFlairMint': !exists(json, 'coin_flair_mint') ? undefined : json['coin_flair_mint'], - 'playlistLibrary': !exists(json, 'playlist_library') ? undefined : UpdateUserRequestBodyPlaylistLibraryFromJSON(json['playlist_library']), + 'playlistLibrary': !exists(json, 'playlist_library') ? undefined : UserPlaylistLibraryFromJSON(json['playlist_library']), 'events': !exists(json, 'events') ? undefined : CreateUserRequestBodyEventsFromJSON(json['events']), }; } @@ -234,7 +234,7 @@ export function UpdateUserRequestBodyToJSON(value?: UpdateUserRequestBody | null 'allow_ai_attribution': value.allowAiAttribution, 'spl_usdc_payout_wallet': value.splUsdcPayoutWallet, 'coin_flair_mint': value.coinFlairMint, - 'playlist_library': UpdateUserRequestBodyPlaylistLibraryToJSON(value.playlistLibrary), + 'playlist_library': UserPlaylistLibraryToJSON(value.playlistLibrary), 'events': CreateUserRequestBodyEventsToJSON(value.events), }; } diff --git a/packages/sdk/src/sdk/api/generated/default/models/UpdateUserRequestBodyPlaylistLibrary.ts b/packages/sdk/src/sdk/api/generated/default/models/UpdateUserRequestBodyPlaylistLibrary.ts deleted file mode 100644 index 4cbcdb64750..00000000000 --- a/packages/sdk/src/sdk/api/generated/default/models/UpdateUserRequestBodyPlaylistLibrary.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ -// @ts-nocheck -/** - * API - * Audius V1 API - * - * The version of the OpenAPI document: 1.0 - * - * - * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). - * https://openapi-generator.tech - * Do not edit the class manually. - */ - -import { exists, mapValues } from '../runtime'; -import type { CreateUserRequestBodyPlaylistLibraryContentsInner } from './CreateUserRequestBodyPlaylistLibraryContentsInner'; -import { - CreateUserRequestBodyPlaylistLibraryContentsInnerFromJSON, - CreateUserRequestBodyPlaylistLibraryContentsInnerFromJSONTyped, - CreateUserRequestBodyPlaylistLibraryContentsInnerToJSON, -} from './CreateUserRequestBodyPlaylistLibraryContentsInner'; - -/** - * User's playlist library with support for folders and playlists - * @export - * @interface UpdateUserRequestBodyPlaylistLibrary - */ -export interface UpdateUserRequestBodyPlaylistLibrary { - /** - * Array of folders and playlist identifiers - * @type {Array} - * @memberof UpdateUserRequestBodyPlaylistLibrary - */ - contents?: Array; -} - -/** - * Check if a given object implements the UpdateUserRequestBodyPlaylistLibrary interface. - */ -export function instanceOfUpdateUserRequestBodyPlaylistLibrary(value: object): value is UpdateUserRequestBodyPlaylistLibrary { - let isInstance = true; - - return isInstance; -} - -export function UpdateUserRequestBodyPlaylistLibraryFromJSON(json: any): UpdateUserRequestBodyPlaylistLibrary { - return UpdateUserRequestBodyPlaylistLibraryFromJSONTyped(json, false); -} - -export function UpdateUserRequestBodyPlaylistLibraryFromJSONTyped(json: any, ignoreDiscriminator: boolean): UpdateUserRequestBodyPlaylistLibrary { - if ((json === undefined) || (json === null)) { - return json; - } - return { - - 'contents': !exists(json, 'contents') ? undefined : ((json['contents'] as Array).map(CreateUserRequestBodyPlaylistLibraryContentsInnerFromJSON)), - }; -} - -export function UpdateUserRequestBodyPlaylistLibraryToJSON(value?: UpdateUserRequestBodyPlaylistLibrary | null): any { - if (value === undefined) { - return undefined; - } - if (value === null) { - return null; - } - return { - - 'contents': value.contents === undefined ? undefined : ((value.contents as Array).map(CreateUserRequestBodyPlaylistLibraryContentsInnerToJSON)), - }; -} - diff --git a/packages/sdk/src/sdk/api/generated/default/models/UserPlaylistLibrary.ts b/packages/sdk/src/sdk/api/generated/default/models/UserPlaylistLibrary.ts new file mode 100644 index 00000000000..ef034bd1d8c --- /dev/null +++ b/packages/sdk/src/sdk/api/generated/default/models/UserPlaylistLibrary.ts @@ -0,0 +1,74 @@ +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck +/** + * API + * Audius V1 API + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { UserPlaylistLibraryContentsInner } from './UserPlaylistLibraryContentsInner'; +import { + UserPlaylistLibraryContentsInnerFromJSON, + UserPlaylistLibraryContentsInnerFromJSONTyped, + UserPlaylistLibraryContentsInnerToJSON, +} from './UserPlaylistLibraryContentsInner'; + +/** + * User's playlist library with support for folders and playlists + * @export + * @interface UserPlaylistLibrary + */ +export interface UserPlaylistLibrary { + /** + * Array of folders and playlist identifiers + * @type {Array} + * @memberof UserPlaylistLibrary + */ + contents: Array; +} + +/** + * Check if a given object implements the UserPlaylistLibrary interface. + */ +export function instanceOfUserPlaylistLibrary(value: object): value is UserPlaylistLibrary { + let isInstance = true; + isInstance = isInstance && "contents" in value && value["contents"] !== undefined; + + return isInstance; +} + +export function UserPlaylistLibraryFromJSON(json: any): UserPlaylistLibrary { + return UserPlaylistLibraryFromJSONTyped(json, false); +} + +export function UserPlaylistLibraryFromJSONTyped(json: any, ignoreDiscriminator: boolean): UserPlaylistLibrary { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'contents': ((json['contents'] as Array).map(UserPlaylistLibraryContentsInnerFromJSON)), + }; +} + +export function UserPlaylistLibraryToJSON(value?: UserPlaylistLibrary | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'contents': ((value.contents as Array).map(UserPlaylistLibraryContentsInnerToJSON)), + }; +} + diff --git a/packages/sdk/src/sdk/api/generated/default/models/CreateUserRequestBodyPlaylistLibraryContentsInner.ts b/packages/sdk/src/sdk/api/generated/default/models/UserPlaylistLibraryContentsInner.ts similarity index 72% rename from packages/sdk/src/sdk/api/generated/default/models/CreateUserRequestBodyPlaylistLibraryContentsInner.ts rename to packages/sdk/src/sdk/api/generated/default/models/UserPlaylistLibraryContentsInner.ts index 4ba0e0b53b2..f3524c34107 100644 --- a/packages/sdk/src/sdk/api/generated/default/models/CreateUserRequestBodyPlaylistLibraryContentsInner.ts +++ b/packages/sdk/src/sdk/api/generated/default/models/UserPlaylistLibraryContentsInner.ts @@ -36,24 +36,24 @@ import { } from './PlaylistLibraryPlaylistIdentifier'; /** - * @type CreateUserRequestBodyPlaylistLibraryContentsInner + * @type UserPlaylistLibraryContentsInner * * @export */ -export type CreateUserRequestBodyPlaylistLibraryContentsInner = PlaylistLibraryExplorePlaylistIdentifier | PlaylistLibraryFolder | PlaylistLibraryPlaylistIdentifier; +export type UserPlaylistLibraryContentsInner = PlaylistLibraryExplorePlaylistIdentifier | PlaylistLibraryFolder | PlaylistLibraryPlaylistIdentifier; -export function CreateUserRequestBodyPlaylistLibraryContentsInnerFromJSON(json: any): CreateUserRequestBodyPlaylistLibraryContentsInner { - return CreateUserRequestBodyPlaylistLibraryContentsInnerFromJSONTyped(json, false); +export function UserPlaylistLibraryContentsInnerFromJSON(json: any): UserPlaylistLibraryContentsInner { + return UserPlaylistLibraryContentsInnerFromJSONTyped(json, false); } -export function CreateUserRequestBodyPlaylistLibraryContentsInnerFromJSONTyped(json: any, ignoreDiscriminator: boolean): CreateUserRequestBodyPlaylistLibraryContentsInner { +export function UserPlaylistLibraryContentsInnerFromJSONTyped(json: any, ignoreDiscriminator: boolean): UserPlaylistLibraryContentsInner { if ((json === undefined) || (json === null)) { return json; } return { ...PlaylistLibraryExplorePlaylistIdentifierFromJSONTyped(json, true), ...PlaylistLibraryFolderFromJSONTyped(json, true), ...PlaylistLibraryPlaylistIdentifierFromJSONTyped(json, true) }; } -export function CreateUserRequestBodyPlaylistLibraryContentsInnerToJSON(value?: CreateUserRequestBodyPlaylistLibraryContentsInner | null): any { +export function UserPlaylistLibraryContentsInnerToJSON(value?: UserPlaylistLibraryContentsInner | null): any { if (value === undefined) { return undefined; } diff --git a/packages/sdk/src/sdk/api/generated/default/models/index.ts b/packages/sdk/src/sdk/api/generated/default/models/index.ts index f6560cc3c36..53fa9e609f9 100644 --- a/packages/sdk/src/sdk/api/generated/default/models/index.ts +++ b/packages/sdk/src/sdk/api/generated/default/models/index.ts @@ -64,8 +64,6 @@ export * from './CreateUserDeveloperAppRequestBody'; export * from './CreateUserDeveloperAppResponse'; export * from './CreateUserRequestBody'; export * from './CreateUserRequestBodyEvents'; -export * from './CreateUserRequestBodyPlaylistLibrary'; -export * from './CreateUserRequestBodyPlaylistLibraryContentsInner'; export * from './CreateUserResponse'; export * from './DashboardWalletUser'; export * from './DashboardWalletUsersResponse'; @@ -182,7 +180,6 @@ export * from './UpdateDeveloperAppRequestBody'; export * from './UpdatePlaylistRequestBody'; export * from './UpdateTrackRequestBody'; export * from './UpdateUserRequestBody'; -export * from './UpdateUserRequestBodyPlaylistLibrary'; export * from './User'; export * from './UserCoin'; export * from './UserCoinAccount'; @@ -192,6 +189,8 @@ export * from './UserCoinsResponse'; export * from './UserCommentsResponse'; export * from './UserIdAddress'; export * from './UserIdsAddressesResponse'; +export * from './UserPlaylistLibrary'; +export * from './UserPlaylistLibraryContentsInner'; export * from './UserResponse'; export * from './UserSearch'; export * from './UserTrackListenCountsResponse'; diff --git a/packages/sdk/src/sdk/api/playlists/PlaylistsApi.ts b/packages/sdk/src/sdk/api/playlists/PlaylistsApi.ts index 766e0278756..c7e92250a96 100644 --- a/packages/sdk/src/sdk/api/playlists/PlaylistsApi.ts +++ b/packages/sdk/src/sdk/api/playlists/PlaylistsApi.ts @@ -10,7 +10,7 @@ import { AdvancedOptions } from '../../services/EntityManager/types' import type { LoggerService } from '../../services/Logger' -import { encodeHashId } from '../../utils/hashId' +import { decodeHashId, encodeHashId } from '../../utils/hashId' import { parseParams } from '../../utils/parseParams' import { retry3 } from '../../utils/retry' import { @@ -22,9 +22,12 @@ import { type FavoritePlaylistRequest, type UnfavoritePlaylistRequest, type SharePlaylistRequest, - type UpdateTrackRequestBody + TracksApi, + type CreateTrackRequestBody, + type CreatePlaylistRequestBody } from '../generated/default' import { TrackUploadHelper } from '../tracks/TrackUploadHelper' +import { UploadTrackSchema } from '../tracks/types' import { AddTrackToPlaylistRequest, @@ -65,6 +68,7 @@ const getCurrentTimestamp = () => { export class PlaylistsApi extends GeneratedPlaylistsApi { private readonly trackUploadHelper: TrackUploadHelper + private readonly tracksApi: TracksApi constructor( configuration: Configuration, @@ -73,6 +77,7 @@ export class PlaylistsApi extends GeneratedPlaylistsApi { private readonly logger: LoggerService ) { super(configuration) + this.tracksApi = new TracksApi(configuration) this.trackUploadHelper = new TrackUploadHelper(configuration) this.logger = logger.createPrefixedLogger('[playlists-api]') } @@ -85,19 +90,64 @@ export class PlaylistsApi extends GeneratedPlaylistsApi { advancedOptions?: AdvancedOptions ) { // Parse inputs - const parsedParameters = await parseParams( - 'createPlaylist', - CreatePlaylistSchema - )(params) + const { + userId, + playlistId: providedPlaylistId, + metadata, + trackIds + } = await parseParams('createPlaylist', CreatePlaylistSchema)(params) + + const playlistId = providedPlaylistId || (await this.generatePlaylistId()) + const timestamp = getCurrentTimestamp() - // Call createPlaylistInternal with parsed inputs - return await this.createPlaylistInternal(parsedParameters, advancedOptions) + // Update metadata to include track ids + const updatedMetadata = { + ...metadata, + playlistContents: (trackIds ?? []).map((trackId) => ({ + trackId, + timestamp + })) + } + + // Write playlist metadata to chain + const response = await this.entityManager.manageEntity({ + userId, + entityType: EntityType.PLAYLIST, + entityId: playlistId, + action: Action.CREATE, + metadata: JSON.stringify({ + cid: '', + data: snakecaseKeys(updatedMetadata) + }), + ...advancedOptions + }) + + return { + ...response, + playlistId: encodeHashId(playlistId) + } } override async createPlaylist( params: CreatePlaylistRequestWithFiles, requestInit?: RequestInit ) { + if (params.imageFile) { + const imageUploadResponse = await this.storage + .uploadFile({ + file: params.imageFile, + onProgress: (event) => + params.onProgress?.(event.loaded / event.total, { + ...event, + key: 'image' + }), + metadata: { + template: 'img_square' + } + }) + .start() + params.metadata.coverArtCid = imageUploadResponse.orig_file_cid + } if (this.entityManager) { const { metadata } = params const res = await this.createPlaylistWithEntityManager({ @@ -120,14 +170,149 @@ export class PlaylistsApi extends GeneratedPlaylistsApi { params: UploadPlaylistRequest, advancedOptions?: AdvancedOptions ) { - // Parse inputs - const parsedParameters = await parseParams( + const { userId, onProgress, audioFiles, imageFile } = await parseParams( 'uploadPlaylist', UploadPlaylistSchema )(params) - // Call uploadPlaylistInternal with parsed inputs - return await this.uploadPlaylistInternal(parsedParameters, advancedOptions) + const trackMetadatas = params.trackMetadatas + const playlistMetadata = params.metadata + + const progresses = audioFiles.map(() => 0) + const [imageUploadResponse, ...audioUploadResponses] = await Promise.all([ + params.imageFile && + this.storage + .uploadFile({ + file: imageFile, + onProgress: (event) => + onProgress?.(event.loaded / event.total, { + ...event, + key: 'image' + }), + metadata: { + template: 'img_square' + } + }) + .start(), + ...audioFiles.map((trackFile, idx) => + this.storage + .uploadFile({ + file: trackFile, + onProgress: (progress) => { + progresses[idx] = + (progress.loaded / progress.total) * 0.5 + + progress.transcode * 0.5 + const overallProgress = + progresses.reduce((a, b) => a + b, 0) / audioFiles.length + onProgress?.(overallProgress, { + ...progress, + key: idx + }) + }, + metadata: { + template: 'audio', + placementHosts: trackMetadatas[idx]?.placementHosts, + previewStartSeconds: trackMetadatas[idx]?.previewStartSeconds + } + }) + .start() + ) + ]) + + // Write tracks to chain + const trackIds = await Promise.all( + trackMetadatas.map(async (t, i) => { + // Transform track metadata + const trackMetadata = this.combineMetadata( + this.trackUploadHelper.transformTrackUploadMetadata(t, userId), + playlistMetadata + ) + + const audioResponse = audioUploadResponses[i] + + if (!audioResponse) { + throw new Error(`Failed to upload track: ${t.title}`) + } + + // Update metadata to include uploaded CIDs + const updatedMetadata = + this.trackUploadHelper.populateTrackMetadataWithUploadResponse( + trackMetadata, + audioResponse, + imageUploadResponse + ) + + if (this.entityManager) { + const { metadata } = UploadTrackSchema.parse(updatedMetadata) + const trackId = await this.trackUploadHelper.generateId('track') + await this.entityManager.manageEntity({ + userId, + entityType: EntityType.TRACK, + entityId: trackId, + action: Action.CREATE, + metadata: JSON.stringify({ + cid: '', + data: snakecaseKeys(metadata) + }), + ...advancedOptions + }) + + return trackId + } + + const res = await this.tracksApi.createTrack({ + userId: encodeHashId(userId)!, + metadata: updatedMetadata + }) + return decodeHashId(res.trackId!)! + }) + ) + + const timestamp = getCurrentTimestamp() + + if (this.entityManager) { + // Update metadata to include track ids + const updatedMetadata = { + ...params.metadata, + playlistContents: (trackIds ?? []).map((trackId) => ({ + trackId, + timestamp + })), + playlistImageSizesMultihash: imageUploadResponse?.orig_file_cid + } + const playlistId = await this.generatePlaylistId() + // Write playlist metadata to chain + const response = await this.entityManager.manageEntity({ + userId, + entityType: EntityType.PLAYLIST, + entityId: playlistId, + action: Action.CREATE, + metadata: JSON.stringify({ + cid: '', + data: snakecaseKeys(updatedMetadata) + }), + ...advancedOptions + }) + + return { + ...response, + playlistId: encodeHashId(playlistId) + } + } + + // Update metadata to include track ids + const updatedMetadata = { + ...params.metadata, + playlistContents: (trackIds ?? []).map((trackId) => ({ + trackId: encodeHashId(trackId)!, + timestamp + })), + playlistImageSizesMultihash: imageUploadResponse?.orig_file_cid + } + return super.createPlaylist({ + userId: encodeHashId(userId)!, + metadata: updatedMetadata + }) } /** @hidden @@ -506,8 +691,8 @@ export class PlaylistsApi extends GeneratedPlaylistsApi { * taking the metadata from the playlist when the track is missing it. */ private combineMetadata( - trackMetadata: UpdateTrackRequestBody, - playlistMetadata: PlaylistMetadata + trackMetadata: CreateTrackRequestBody, + playlistMetadata: CreatePlaylistRequestBody ) { const metadata = trackMetadata @@ -588,153 +773,6 @@ export class PlaylistsApi extends GeneratedPlaylistsApi { ) } - /** @internal - * Method to upload a playlist with already parsed inputs - * This is used for both playlists and albums - */ - public async uploadPlaylistInternal( - { - userId, - imageFile, - audioFiles, - onProgress, - metadata, - trackMetadatas - }: z.infer & { - metadata: Metadata - }, - advancedOptions?: AdvancedOptions - ) { - const progresses = audioFiles.map(() => 0) - // Upload track audio and cover art to storage node - const [coverArtResponse, ...audioResponses] = await Promise.all([ - retry3( - async () => - await this.storage - .uploadFile({ - file: imageFile, - onProgress: (progress) => - onProgress?.( - progresses.reduce((a, b) => a + b, 0) / audioFiles.length, - { ...progress, key: 'image' } - ), - metadata: { - template: 'img_square' - } - }) - .start(), - (e) => { - this.logger.info('Retrying uploadPlaylistCoverArt', e) - } - ), - ...audioFiles.map( - async (trackFile, idx) => - await retry3( - async () => - await this.storage - .uploadFile({ - file: trackFile, - onProgress: (progress) => { - progresses[idx] = - (progress.loaded / progress.total) * 0.5 + - progress.transcode * 0.5 - const overallProgress = - progresses.reduce((a, b) => a + b, 0) / audioFiles.length - onProgress?.(overallProgress, { - ...progress, - key: idx - }) - }, - metadata: { - template: 'audio', - ...this.trackUploadHelper.extractMediorumUploadOptions( - trackMetadatas[idx]! - ) - } - }) - .start(), - (e) => { - this.logger.info('Retrying uploadTrackAudio', e) - } - ) - ) - ]) - - // Write tracks to chain - const trackIds = await Promise.all( - trackMetadatas.map(async (parsedTrackMetadata, i) => { - // Transform track metadata - const trackMetadata = this.combineMetadata( - this.trackUploadHelper.transformTrackUploadMetadata( - parsedTrackMetadata, - userId - ), - metadata - ) - - const audioResponse = audioResponses[i] - - if (!audioResponse) { - throw new Error(`Failed to upload track: ${trackMetadata.title}`) - } - - // Update metadata to include uploaded CIDs - const updatedMetadata = - this.trackUploadHelper.populateTrackMetadataWithUploadResponse( - trackMetadata, - audioResponse, - coverArtResponse - ) - - const trackId = await this.trackUploadHelper.generateId('track') - await this.entityManager.manageEntity({ - userId, - entityType: EntityType.TRACK, - entityId: trackId, - action: Action.CREATE, - metadata: JSON.stringify({ - cid: '', - data: snakecaseKeys(updatedMetadata) - }), - ...advancedOptions - }) - - return trackId - }) - ) - - const playlistId = await this.trackUploadHelper.generateId('playlist') - const timestamp = getCurrentTimestamp() - - // Update metadata to include track ids and cover art cid - const updatedMetadata = { - ...metadata, - isPrivate: false, - playlistContents: trackIds.map((trackId) => ({ - trackId, - timestamp - })), - playlistImageSizesMultihash: coverArtResponse?.orig_file_cid - } - - // Write playlist metadata to chain - const response = await this.entityManager.manageEntity({ - userId, - entityType: EntityType.PLAYLIST, - entityId: playlistId, - action: Action.CREATE, - metadata: JSON.stringify({ - cid: '', - data: snakecaseKeys(updatedMetadata) - }), - ...advancedOptions - }) - return { - ...response, - playlistId: encodeHashId(playlistId) - } - } - /** @internal * Method to create a playlist with already parsed inputs * This is used for both playlists and albums diff --git a/packages/sdk/src/sdk/api/playlists/types.ts b/packages/sdk/src/sdk/api/playlists/types.ts index 4c720ae0ed8..4de0721bdd0 100644 --- a/packages/sdk/src/sdk/api/playlists/types.ts +++ b/packages/sdk/src/sdk/api/playlists/types.ts @@ -8,9 +8,10 @@ import { Genre, Mood, type CreatePlaylistRequest, + type CreatePlaylistRequestBody, + type CreateTrackRequestBody, type UpdatePlaylistRequest } from '../generated/default' -import { UploadTrackMetadataSchema } from '../tracks/types' const CreatePlaylistMetadataSchema = z .object({ @@ -61,18 +62,6 @@ export const UploadPlaylistMetadataSchema = CreatePlaylistMetadataSchema.extend( export type PlaylistMetadata = z.input -const PlaylistTrackMetadataSchema = UploadTrackMetadataSchema.partial({ - genre: true, - mood: true, - tags: true -}) - -/** - * PlaylistTrackMetadata is less strict than TrackMetadata because - * `genre`, `mood`, and `tags` are optional - */ -export type PlaylistTrackMetadata = z.infer - export const UploadPlaylistProgressEventSchema = ProgressEventSchema.extend({ /** * Index of the track being uploaded in the playlist tracks array, or 'image' if for the image @@ -103,25 +92,20 @@ export type UploadPlaylistProgressHandler = ( event: UploadPlaylistProgressEvent ) => void -export const UploadPlaylistSchema = z - .object({ - userId: HashId, - imageFile: ImageFile, - metadata: UploadPlaylistMetadataSchema, - onProgress: z.optional(UploadPlaylistProgressHandlerSchema), - /** - * Track metadata is populated from the playlist if fields are missing - */ - trackMetadatas: z.array(PlaylistTrackMetadataSchema), - audioFiles: z.array(AudioFile) - }) - .strict() +export const UploadPlaylistSchema = z.object({ + userId: HashId, + imageFile: ImageFile, + onProgress: z.optional(UploadPlaylistProgressHandlerSchema), + audioFiles: z.array(AudioFile) +}) export type UploadPlaylistRequest = Omit< z.input, 'onProgress' > & { onProgress?: UploadPlaylistProgressHandler + metadata: CreatePlaylistRequestBody + trackMetadatas: CreateTrackRequestBody[] } export const UpdatePlaylistMetadataSchema = @@ -162,12 +146,6 @@ export type PlaylistImageParameters = { onProgress?: UploadPlaylistProgressHandler } -export type PlaylistAudioParameters = { - audioFiles?: z.input[] - trackMetadatas?: z.input[] - onProgress?: UploadPlaylistProgressHandler -} - export type CreatePlaylistRequestWithFiles = CreatePlaylistRequest & PlaylistImageParameters diff --git a/packages/sdk/src/sdk/api/tracks/TrackUploadHelper.ts b/packages/sdk/src/sdk/api/tracks/TrackUploadHelper.ts index c923f7facce..0e3aa46789a 100644 --- a/packages/sdk/src/sdk/api/tracks/TrackUploadHelper.ts +++ b/packages/sdk/src/sdk/api/tracks/TrackUploadHelper.ts @@ -5,7 +5,6 @@ import { type CreateTrackRequestBody, type UpdateTrackRequestBody } from '../generated/default' -import type { PlaylistTrackMetadata } from '../playlists/types' export class TrackUploadHelper extends BaseAPI { public async generateId(type: 'track' | 'playlist') { @@ -24,11 +23,10 @@ export class TrackUploadHelper extends BaseAPI { return id } - public transformTrackUploadMetadata( - inputMetadata: CreateTrackRequestBody | UpdateTrackRequestBody, - userId: number - ) { - const metadata = { + public transformTrackUploadMetadata< + T extends CreateTrackRequestBody | UpdateTrackRequestBody + >(inputMetadata: T, userId: number) { + const metadata: T = { ...inputMetadata, ownerId: userId } @@ -57,8 +55,10 @@ export class TrackUploadHelper extends BaseAPI { return metadata } - public populateTrackMetadataWithUploadResponse( - trackMetadata: CreateTrackRequestBody | UpdateTrackRequestBody, + public populateTrackMetadataWithUploadResponse< + T extends CreateTrackRequestBody | UpdateTrackRequestBody + >( + trackMetadata: T, audioResponse?: UploadResponse, coverArtResponse?: UploadResponse ) { @@ -95,19 +95,4 @@ export class TrackUploadHelper extends BaseAPI { } return updated } - - public extractMediorumUploadOptions(metadata: PlaylistTrackMetadata) { - const uploadOptions: { [key: string]: string } = {} - if ( - metadata.previewStartSeconds !== undefined && - metadata.previewStartSeconds !== null - ) { - uploadOptions.previewStartSeconds = - metadata.previewStartSeconds.toString() - } - if (metadata.placementHosts) { - uploadOptions.placement_hosts = metadata.placementHosts - } - return uploadOptions - } } diff --git a/packages/sdk/src/sdk/api/tracks/TracksApi.ts b/packages/sdk/src/sdk/api/tracks/TracksApi.ts index ddfd73c8b86..d95e8608088 100644 --- a/packages/sdk/src/sdk/api/tracks/TracksApi.ts +++ b/packages/sdk/src/sdk/api/tracks/TracksApi.ts @@ -34,7 +34,8 @@ import { type ShareTrackRequest, type RepostTrackRequest, type UnrepostTrackRequest, - type RecordTrackDownloadRequest + type RecordTrackDownloadRequest, + type CreateTrackRequest } from '../generated/default' import { RequiredError } from '../generated/default/runtime' @@ -51,7 +52,6 @@ import { EntityManagerUnfavoriteTrackRequest, UnfavoriteTrackSchema, EntityManagerUpdateTrackRequest, - UploadTrackRequest, PurchaseTrackRequest, PurchaseTrackSchema, GetPurchaseTrackInstructionsRequest, @@ -69,7 +69,8 @@ import { type PublishStemRequest, PublishStemSchema, type UploadTrackFilesTask, - type UpdateTrackRequestWithFiles + type UpdateTrackRequestWithFiles, + type CreateTrackRequestWithFiles } from './types' // Extend that new class @@ -224,30 +225,28 @@ export class TracksApi extends GeneratedTracksApi { /** @hidden * Publishes a track that was uploaded using storage node uploadFileV2 uploads. */ - async publishTrack( - params: PublishTrackRequest, - advancedOptions?: AdvancedOptions - ) { - const { - userId, - metadata: parsedMetadata, - audioUploadResponse, - imageUploadResponse - } = await parseParams('publishTrack', PublishTrackSchema)(params) + async publishTrack(params: PublishTrackRequest, requestInit?: RequestInit) { + const { userId, audioUploadResponse, imageUploadResponse } = + await parseParams('publishTrack', PublishTrackSchema)(params) + const metadata = params.metadata - const metadata = this.trackUploadHelper.transformTrackUploadMetadata( - parsedMetadata, - userId - ) + const transformedMetadata = + this.trackUploadHelper.transformTrackUploadMetadata(metadata, userId) const populatedMetadata = this.trackUploadHelper.populateTrackMetadataWithUploadResponse( - metadata, + transformedMetadata, audioUploadResponse, imageUploadResponse ) - return this.writeTrackToChain(userId, populatedMetadata, advancedOptions) + if (this.entityManager) { + return this.writeTrackToChain(userId, populatedMetadata) + } + return super.createTrack( + { userId: params.userId, metadata: populatedMetadata }, + requestInit + ) } /** @hidden @@ -263,9 +262,8 @@ export class TracksApi extends GeneratedTracksApi { audioUploadResponse } = await parseParams('publishStem', PublishStemSchema)(params) - const trackMetadata = { + const trackMetadata: Partial = { title: audioUploadResponse.orig_filename || 'Untitled Stem', - isStreamGated: false, streamConditions: undefined, isUnlisted: false, fieldVisibility: { @@ -277,7 +275,10 @@ export class TracksApi extends GeneratedTracksApi { playCount: false }, isDownloadable: true, - stemOf: parsedMetadata + stemOf: { + parentTrackId: params.metadata.parentTrackId, + category: parsedMetadata.category + } } const metadata = this.trackUploadHelper.transformTrackUploadMetadata( @@ -299,17 +300,14 @@ export class TracksApi extends GeneratedTracksApi { */ async writeTrackToChain( userId: number, - metadata: ReturnType< - typeof this.trackUploadHelper.populateTrackMetadataWithUploadResponse - >, + metadata: Partial, advancedOptions?: AdvancedOptions ) { - // Write metadata to chain - this.logger.info('Writing metadata to chain') + const { metadata: parsedMetadata } = UploadTrackSchema.parse(metadata) const entityId = - 'trackId' in metadata && metadata.trackId - ? metadata.trackId + 'trackId' in parsedMetadata && parsedMetadata.trackId + ? parsedMetadata.trackId : await this.trackUploadHelper.generateId('track') const response = await this.entityManager.manageEntity({ @@ -320,38 +318,32 @@ export class TracksApi extends GeneratedTracksApi { metadata: JSON.stringify({ cid: '', data: { - ...snakecaseKeys(metadata), + ...snakecaseKeys(parsedMetadata), owner_id: userId, download_conditions: - metadata.downloadConditions && - snakecaseKeys(metadata.downloadConditions), + parsedMetadata.downloadConditions && + snakecaseKeys(parsedMetadata.downloadConditions), stream_conditions: - metadata.streamConditions && - snakecaseKeys(metadata.streamConditions), - stem_of: metadata.stemOf && snakecaseKeys(metadata.stemOf) + parsedMetadata.streamConditions && + snakecaseKeys(parsedMetadata.streamConditions), + stem_of: parsedMetadata.stemOf && snakecaseKeys(parsedMetadata.stemOf) } }), ...advancedOptions }) - this.logger.info('Successfully uploaded track') return { ...response, trackId: encodeHashId(entityId)! } } - /** - * Upload a track - */ - async uploadTrack( - params: UploadTrackRequest, - advancedOptions?: AdvancedOptions + override async createTrack( + params: CreateTrackRequestWithFiles, + requestInit?: RequestInit ) { - // Validate inputs - await parseParams('uploadTrack', UploadTrackSchema)(params) - - // Upload track files + // Upload files + let metadata = params.metadata const { audioUploadResponse, imageUploadResponse } = await this.uploadTrackFiles({ audioFile: params.audioFile, @@ -363,19 +355,26 @@ export class TracksApi extends GeneratedTracksApi { onProgress: params.onProgress }).start() - if (!audioUploadResponse || !imageUploadResponse) { - throw new Error('uploadTrack: Missing upload responses') - } + metadata = this.trackUploadHelper.transformTrackUploadMetadata( + metadata, + decodeHashId(params.userId)! + ) + + metadata = this.trackUploadHelper.populateTrackMetadataWithUploadResponse( + metadata, + audioUploadResponse, + imageUploadResponse + ) - // Write track metadata to chain - return this.publishTrack( + if (this.entityManager) { + return this.writeTrackToChain(decodeHashId(params.userId)!, metadata) + } + return super.createTrack( { userId: params.userId, - metadata: params.metadata, - audioUploadResponse, - imageUploadResponse + metadata }, - advancedOptions + requestInit ) } @@ -387,62 +386,10 @@ export class TracksApi extends GeneratedTracksApi { advancedOptions?: AdvancedOptions ) { // Parse inputs - const { - userId, - trackId, - audioFile, - imageFile, - metadata: parsedMetadata, - onProgress, - generatePreview - } = await parseParams('updateTrack', UpdateTrackSchema)(params) - - // Transform metadata - const metadata = this.trackUploadHelper.transformTrackUploadMetadata( - parsedMetadata, - userId - ) - - const { audioUploadResponse, imageUploadResponse } = - await this.uploadTrackFiles({ - audioFile, - imageFile, - fileMetadata: { - placementHosts: parsedMetadata.placementHosts, - previewStartSeconds: parsedMetadata.previewStartSeconds - }, - onProgress - }).start() - - // Update metadata to include uploaded CIDs - const updatedMetadata = - this.trackUploadHelper.populateTrackMetadataWithUploadResponse( - metadata, - audioUploadResponse, - imageUploadResponse - ) - - // Generate preview if requested and no audio file was uploaded - // (as that would handle the preview generation already) - if (generatePreview && !audioFile) { - if (updatedMetadata.previewStartSeconds === undefined) { - throw new Error('No track preview start time specified') - } - - const previewCid = await retry3( - async () => - await this.storage.generatePreview({ - cid: updatedMetadata.trackCid!, - secondOffset: updatedMetadata.previewStartSeconds! - }), - (e) => { - this.logger.info('Retrying generatePreview', e) - } - ) - - // Update metadata to include updated preview CID - updatedMetadata.previewCid = previewCid - } + const { userId, trackId, metadata } = await parseParams( + 'updateTrack', + UpdateTrackSchema + )(params) // Write metadata to chain return await this.entityManager.manageEntity({ @@ -453,13 +400,13 @@ export class TracksApi extends GeneratedTracksApi { metadata: JSON.stringify({ cid: '', data: { - ...snakecaseKeys(updatedMetadata), + ...snakecaseKeys(metadata), download_conditions: - updatedMetadata.downloadConditions && - snakecaseKeys(updatedMetadata.downloadConditions), + metadata.downloadConditions && + snakecaseKeys(metadata.downloadConditions), stream_conditions: - updatedMetadata.streamConditions && - snakecaseKeys(updatedMetadata.streamConditions), + metadata.streamConditions && + snakecaseKeys(metadata.streamConditions), stem_of: metadata.stemOf && snakecaseKeys(metadata.stemOf) } }), @@ -473,28 +420,44 @@ export class TracksApi extends GeneratedTracksApi { ) { // Upload files let metadata = params.metadata - if (params.audioFile || params.imageFile) { - const { audioUploadResponse, imageUploadResponse } = - await this.uploadTrackFiles({ - audioFile: params.audioFile, - imageFile: params.imageFile, - fileMetadata: { - placementHosts: metadata.placementHosts, - previewStartSeconds: metadata.previewStartSeconds - }, - onProgress: params.onProgress - }).start() - - metadata = this.trackUploadHelper.transformTrackUploadMetadata( - metadata, - decodeHashId(params.userId)! - ) + const { audioUploadResponse, imageUploadResponse } = + await this.uploadTrackFiles({ + audioFile: params.audioFile, + imageFile: params.imageFile, + fileMetadata: { + placementHosts: params.metadata.placementHosts, + previewStartSeconds: params.metadata.previewStartSeconds + }, + onProgress: params.onProgress + }).start() - metadata = this.trackUploadHelper.populateTrackMetadataWithUploadResponse( - metadata, - audioUploadResponse, - imageUploadResponse + metadata = this.trackUploadHelper.transformTrackUploadMetadata( + metadata, + decodeHashId(params.userId)! + ) + + metadata = this.trackUploadHelper.populateTrackMetadataWithUploadResponse( + metadata, + audioUploadResponse, + imageUploadResponse + ) + + // Generate preview if requested and no audio file was uploaded + // (as that would handle the preview generation already) + if (metadata.previewStartSeconds !== undefined && !params.audioFile) { + const previewCid = await retry3( + async () => + await this.storage.generatePreview({ + cid: metadata.trackCid!, + secondOffset: metadata.previewStartSeconds! + }), + (e) => { + this.logger.info('Retrying generatePreview', e) + } ) + + // Update metadata to include updated preview CID + metadata.previewCid = previewCid } if (this.entityManager) { @@ -512,7 +475,7 @@ export class TracksApi extends GeneratedTracksApi { { trackId: params.trackId, userId: params.userId, - metadata + metadata: params.metadata }, requestInit ) diff --git a/packages/sdk/src/sdk/api/tracks/types.ts b/packages/sdk/src/sdk/api/tracks/types.ts index 4640b5e5d85..81714ab4c12 100644 --- a/packages/sdk/src/sdk/api/tracks/types.ts +++ b/packages/sdk/src/sdk/api/tracks/types.ts @@ -14,7 +14,8 @@ import { Mood, Genre, StemCategory, - type UpdateTrackRequest + type UpdateTrackRequest, + type CreateTrackRequest } from '../generated/default' import { MAX_DESCRIPTION_LENGTH } from './constants' @@ -97,7 +98,7 @@ export const UploadStemMetadataSchema = z.object({ category: z .enum(Object.values(StemCategory) as [StemCategory, ...StemCategory[]]) .default(StemCategory.Other), - parentTrackId: HashId.or(z.number()) + parentTrackId: HashId }) export const UploadTrackMetadataSchema = z.object({ @@ -149,7 +150,7 @@ export const UploadTrackMetadataSchema = z.object({ tracks: z .array( z.object({ - parentTrackId: HashId.or(z.number()) + parentTrackId: HashId }) ) .min(1) @@ -453,16 +454,16 @@ const UploadResponseSchema = z.object({ export type UploadResponse = z.input -export const PublishTrackSchema = z - .object({ - userId: HashId, - metadata: UploadTrackMetadataSchema.strict(), - audioUploadResponse: UploadResponseSchema, - imageUploadResponse: UploadResponseSchema - }) - .strict() +export const PublishTrackSchema = z.object({ + userId: HashId, + audioUploadResponse: UploadResponseSchema, + imageUploadResponse: UploadResponseSchema +}) -export type PublishTrackRequest = z.input +export type PublishTrackRequest = CreateTrackRequest & { + audioUploadResponse: UploadResponse + imageUploadResponse: UploadResponse +} export const PublishStemSchema = z .object({ @@ -482,8 +483,14 @@ export type UploadTrackFilesTask = { abort: () => void } -export type UpdateTrackRequestWithFiles = UpdateTrackRequest & { +export type TrackFileUploadParams = { audioFile?: z.input imageFile?: z.input onProgress?: UploadTrackFilesProgressHandler } + +export type CreateTrackRequestWithFiles = CreateTrackRequest & + TrackFileUploadParams + +export type UpdateTrackRequestWithFiles = UpdateTrackRequest & + TrackFileUploadParams diff --git a/packages/sdk/src/sdk/api/users/UsersApi.test.ts b/packages/sdk/src/sdk/api/users/UsersApi.test.ts index cf7df74c39b..be068dd88f8 100644 --- a/packages/sdk/src/sdk/api/users/UsersApi.test.ts +++ b/packages/sdk/src/sdk/api/users/UsersApi.test.ts @@ -117,8 +117,9 @@ describe('UsersApi', () => { describe('updateProfile', () => { it('updates the user profile if valid metadata is provided', async () => { - const result = await users.updateProfile({ + const result = await users.updateUser({ userId: '7eP5n', + id: '7eP5n', profilePictureFile: { buffer: pngFile, name: 'profilePicture' @@ -143,7 +144,8 @@ describe('UsersApi', () => { }) it('updates the user profile if partial valid metadata is provided', async () => { - const result = await users.updateProfile({ + const result = await users.updateUser({ + id: '7eP5n', userId: '7eP5n', metadata: { bio: 'The bio has been updated' @@ -158,8 +160,9 @@ describe('UsersApi', () => { it('throws an error if invalid metadata is provided', async () => { await expect(async () => { - await users.updateProfile({ + await users.updateUser({ userId: '7eP5n', + id: '7eP5n', metadata: { asdf: '123' } as any @@ -169,7 +172,9 @@ describe('UsersApi', () => { it('throws an error if invalid request is sent', async () => { await expect(async () => { - await users.updateProfile({ + await users.updateUser({ + id: '7eP5n', + userId: '7eP5n', metadata: { bio: 'New bio' } } as any) }).rejects.toThrow() diff --git a/packages/sdk/src/sdk/api/users/UsersApi.ts b/packages/sdk/src/sdk/api/users/UsersApi.ts index 3b52079b1a2..8bf5c92d4ec 100644 --- a/packages/sdk/src/sdk/api/users/UsersApi.ts +++ b/packages/sdk/src/sdk/api/users/UsersApi.ts @@ -16,14 +16,13 @@ import { HashId, Id } from '../../types/HashId' import { generateMetadataCidV1 } from '../../utils/cid' import { decodeHashId, encodeHashId } from '../../utils/hashId' import { parseParams } from '../../utils/parseParams' -import { retry3 } from '../../utils/retry' import { Configuration, DownloadPurchasesAsCSVRequest, DownloadSalesAsCSVRequest, DownloadUSDCWithdrawalsAsCSVRequest, UsersApi as GeneratedUsersApi, - type CreateUserRequest + type UserPlaylistLibrary } from '../generated/default' import * as runtime from '../generated/default/runtime' @@ -51,7 +50,11 @@ import { UpdateCollectiblesSchema, UpdateProfileSchema, type EntityManagerCreateUserRequest, - type EntityManagerUpdateProfileRequest + type EntityManagerUpdateProfileRequest, + type UpdateUserRequestWithFiles, + type CreateUserRequestWithFiles, + type UserFileUploadParams, + type EntityManagerPlaylistLibraryContents } from './types' export class UsersApi extends GeneratedUsersApi { @@ -92,8 +95,10 @@ export class UsersApi extends GeneratedUsersApi { params: EntityManagerCreateUserRequest, advancedOptions?: AdvancedOptions ) { - const { onProgress, profilePictureFile, coverArtFile, metadata } = - await parseParams('createUser', CreateUserSchema)(params) + const { metadata } = await parseParams( + 'createUser', + CreateUserSchema + )(params) const { data } = await this.generateUserId() if (!data) { @@ -101,59 +106,7 @@ export class UsersApi extends GeneratedUsersApi { } const userId = HashId.parse(data) - const [profilePictureResp, coverArtResp] = await Promise.all([ - profilePictureFile && - retry3( - async () => - await this.storage - .uploadFile({ - file: profilePictureFile, - onProgress, - metadata: { - template: 'img_square' - } - }) - .start(), - (e) => { - this.logger.info('Retrying uploadProfilePicture', e) - } - ), - coverArtFile && - retry3( - async () => - await this.storage - .uploadFile({ - file: coverArtFile, - onProgress, - metadata: { - template: 'img_backdrop' - } - }) - .start(), - (e) => { - this.logger.info('Retrying uploadProfileCoverArt', e) - } - ) - ]) - - const updatedMetadata = { - ...metadata, - userId, - ...(profilePictureResp - ? { - profilePicture: profilePictureResp?.orig_file_cid, - profilePictureSizes: profilePictureResp?.orig_file_cid - } - : {}), - ...(coverArtResp - ? { - coverPhoto: coverArtResp?.orig_file_cid, - coverPhotoSizes: coverArtResp?.orig_file_cid - } - : {}) - } - - const entityMetadata = snakecaseKeys(updatedMetadata) + const entityMetadata = snakecaseKeys(metadata) const cid = (await generateMetadataCidV1(entityMetadata)).toString() @@ -170,30 +123,29 @@ export class UsersApi extends GeneratedUsersApi { ...advancedOptions }) - return { blockHash, blockNumber, metadata: updatedMetadata } + return { blockHash, blockNumber, metadata } } override async createUser( - params: CreateUserRequest, + params: CreateUserRequestWithFiles, requestInit?: RequestInit ) { + const metadata = await this.updateMetadataWithFiles(params.metadata, params) if (this.entityManager) { - const { metadata } = params const res = await this.createUserWithEntityManager({ - metadata: { - ...metadata, - events: { - ...metadata.events, - referrer: Id.parse(metadata.events?.referrer)! - } - } + metadata }) return { success: true, transactionHash: res.blockHash } } - return super.createUser(params, requestInit) + return super.createUser( + { + metadata + }, + requestInit + ) } /** @hidden @@ -229,66 +181,15 @@ export class UsersApi extends GeneratedUsersApi { /** @hidden * Update a user profile */ - async updateProfile( + async updateUserWithEntityManager( params: EntityManagerUpdateProfileRequest, advancedOptions?: AdvancedOptions ) { - // Parse inputs - const { onProgress, profilePictureFile, coverArtFile, userId, metadata } = - await parseParams('updateProfile', UpdateProfileSchema)(params) - - const [profilePictureResp, coverArtResp] = await Promise.all([ - profilePictureFile && - retry3( - async () => - await this.storage - .uploadFile({ - file: profilePictureFile, - onProgress, - metadata: { - template: 'img_square' - } - }) - .start(), - (e) => { - this.logger.info('Retrying uploadProfilePicture', e) - } - ), - coverArtFile && - retry3( - async () => - await this.storage - .uploadFile({ - file: coverArtFile, - onProgress, - metadata: { - template: 'img_backdrop' - } - }) - .start(), - (e) => { - this.logger.info('Retrying uploadProfileCoverArt', e) - } - ) - ]) - - const updatedMetadata = snakecaseKeys({ - ...metadata, - ...(profilePictureResp - ? { - profilePicture: profilePictureResp?.orig_file_cid, - profilePictureSizes: profilePictureResp?.orig_file_cid - } - : {}), - ...(coverArtResp - ? { - coverPhoto: coverArtResp?.orig_file_cid, - coverPhotoSizes: coverArtResp?.orig_file_cid - } - : {}) - }) - - const cid = (await generateMetadataCidV1(updatedMetadata)).toString() + const { userId, metadata } = await parseParams( + 'updateUser', + UpdateProfileSchema + )(params) + const cid = (await generateMetadataCidV1(metadata)).toString() // Write metadata to chain return await this.entityManager.manageEntity({ @@ -298,12 +199,113 @@ export class UsersApi extends GeneratedUsersApi { action: Action.UPDATE, metadata: JSON.stringify({ cid, - data: updatedMetadata + data: snakecaseKeys(metadata) }), ...advancedOptions }) } + private async updateMetadataWithFiles< + T extends + | CreateUserRequestWithFiles['metadata'] + | UpdateUserRequestWithFiles['metadata'] + >(metadata: T, fileUploadParams: UserFileUploadParams) { + const { onProgress, profilePictureFile, coverArtFile } = fileUploadParams + const [profilePictureResp, coverArtResp] = await Promise.all([ + profilePictureFile + ? await this.storage + .uploadFile({ + file: profilePictureFile, + onProgress, + metadata: { + template: 'img_square' + } + }) + .start() + : null, + coverArtFile + ? await this.storage + .uploadFile({ + file: coverArtFile, + onProgress, + metadata: { + template: 'img_backdrop' + } + }) + .start() + : null + ]) + if (profilePictureResp) { + metadata.profilePicture = profilePictureResp.orig_file_cid + metadata.profilePictureSizes = profilePictureResp.orig_file_cid + } + if (coverArtResp) { + metadata.coverPhoto = coverArtResp.orig_file_cid + metadata.coverPhotoSizes = coverArtResp.orig_file_cid + } + return metadata + } + + private mapLibraryContentsToEntityManagerFormat( + libraryItems: UserPlaylistLibrary['contents'] + ): EntityManagerPlaylistLibraryContents { + const items = [] + for (const item of libraryItems) { + if (item.type === 'folder') { + const folder = { + id: item.id, + type: 'folder' as const, + name: item.name, + contents: this.mapLibraryContentsToEntityManagerFormat(item.contents) + } + items.push(folder) + } + if (item.type === 'playlist') { + items.push({ + playlist_id: item.playlistId, + type: 'playlist' as const + }) + } + if (item.type === 'explore_playlist') { + items.push({ + playlist_id: item.playlistId, + type: 'explore_playlist' as const + }) + } + } + return items + } + + override async updateUser( + params: UpdateUserRequestWithFiles, + requestInit?: RequestInit + ) { + const metadata = await this.updateMetadataWithFiles(params.metadata, params) + if (this.entityManager) { + return await this.updateUserWithEntityManager({ + userId: Id.parse(params.id)!, + metadata: { + ...metadata, + playlistLibrary: metadata.playlistLibrary?.contents + ? { + contents: this.mapLibraryContentsToEntityManagerFormat( + metadata.playlistLibrary?.contents || [] + ) + } + : undefined + } + }) + } + return super.updateUser( + { + id: params.id, + userId: params.userId, + metadata + }, + requestInit + ) + } + /** @hidden * Follow a user */ diff --git a/packages/sdk/src/sdk/api/users/types.ts b/packages/sdk/src/sdk/api/users/types.ts index 93ca50743ad..3fedc4d7af1 100644 --- a/packages/sdk/src/sdk/api/users/types.ts +++ b/packages/sdk/src/sdk/api/users/types.ts @@ -1,5 +1,6 @@ import { z } from 'zod' +import type { CreateUserRequest, UpdateUserRequest } from '../..' import { ProgressHandler } from '../../services/Storage/types' import { EthAddressSchema } from '../../types/EthAddress' import { ImageFile } from '../../types/File' @@ -100,6 +101,10 @@ const PlaylistLibrarySchema = z.object({ ) }) +export type EntityManagerPlaylistLibraryContents = z.input< + typeof PlaylistLibrarySchema +>['contents'] + export const UpdateProfileSchema = z .object({ userId: HashId, @@ -283,3 +288,15 @@ export const UpdateCollectiblesSchema = z.object({ }) export type UpdateCollectiblesRequest = z.input + +export type UserFileUploadParams = { + profilePictureFile?: z.input + coverArtFile?: z.input + onProgress?: ProgressHandler +} + +export type CreateUserRequestWithFiles = CreateUserRequest & + UserFileUploadParams + +export type UpdateUserRequestWithFiles = UpdateUserRequest & + UserFileUploadParams