Split TrackApplicationResponse into a Local/External hierarchy
Same treatment as AlbumApplicationResponse, but Track is the cleaner
case: OwnerUserId (local-only) and ExternalId (external-only) are
both genuinely exclusive to one side, so the base carries neither and
is abstract — every real value is one of the two derived types.
TrackApplicationResponse (abstract, shared fields + FromEntity dispatch)
LocalTrackApplicationResponse (+ OwnerUserId)
ExternalTrackApplicationResponse (+ ExternalId)
Hit a real gotcha along the way: the response already had an explicit
Source property, which collides with the "source" discriminator STJ's
polymorphism support injects ("contains property 'source' that
conflicts with an existing metadata property name" — only surfaced at
runtime in the Api integration tests, not at build time). Fixed by
dropping the now-redundant explicit Source field entirely; the
discriminator already serializes the identical "Local"/"YouTube"
value under the same name, so the wire contract is unchanged. Updated
the two call sites that read .Source directly (TrackSearchItemResponse
and a playlist track filter that needed flatMap instead of
filter().map() to keep the narrowing across the chain).
This commit is contained in:
Vendored
+51
-7
@@ -549,7 +549,34 @@ export interface components {
|
||||
source: null | components["schemas"]["TrackSource"];
|
||||
externalId: null | string;
|
||||
};
|
||||
AlbumApplicationResponse: {
|
||||
AlbumApplicationResponse: components["schemas"]["AlbumApplicationResponseAlbumApplicationResponse"] | components["schemas"]["AlbumApplicationResponseExternalAlbumApplicationResponse"];
|
||||
AlbumApplicationResponseAlbumApplicationResponse: {
|
||||
/** @enum {string} */
|
||||
source?: "Local";
|
||||
/** Format: uuid */
|
||||
id: string;
|
||||
title: string;
|
||||
description: null | string;
|
||||
/** Format: int32 */
|
||||
releaseYear: null | number | string;
|
||||
/** Format: int64 */
|
||||
ownerUserId: number | string;
|
||||
/** Format: int32 */
|
||||
trackCount: number | string;
|
||||
smallImageKeyName: null | string;
|
||||
mediumImageKeyName: null | string;
|
||||
largeImageKeyName: null | string;
|
||||
/** Format: date-time */
|
||||
createdAt: string;
|
||||
/** Format: date-time */
|
||||
updatedAt: string;
|
||||
coverTrackIds: string[];
|
||||
};
|
||||
AlbumApplicationResponseExternalAlbumApplicationResponse: {
|
||||
/** @enum {string} */
|
||||
source?: "YouTube";
|
||||
externalId: string;
|
||||
thumbnailUrl: null | string;
|
||||
/** Format: uuid */
|
||||
id: string;
|
||||
title: string;
|
||||
@@ -568,8 +595,6 @@ export interface components {
|
||||
/** Format: date-time */
|
||||
updatedAt: string;
|
||||
coverTrackIds: string[];
|
||||
youTubeAlbumId?: null | string;
|
||||
thumbnailUrl?: null | string;
|
||||
};
|
||||
AlbumPictureUploadResponse: {
|
||||
/** Format: uuid */
|
||||
@@ -766,20 +791,39 @@ export interface components {
|
||||
/** Format: int64 */
|
||||
expectedAudioSizeBytes?: null | number | string;
|
||||
};
|
||||
TrackApplicationResponse: {
|
||||
TrackApplicationResponse: components["schemas"]["TrackApplicationResponseLocalTrackApplicationResponse"] | components["schemas"]["TrackApplicationResponseExternalTrackApplicationResponse"];
|
||||
TrackApplicationResponseExternalTrackApplicationResponse: {
|
||||
/** @enum {string} */
|
||||
source?: "YouTube";
|
||||
externalId: string;
|
||||
/** Format: uuid */
|
||||
id: string;
|
||||
title: string;
|
||||
artist: null | string;
|
||||
source: components["schemas"]["TrackSource"];
|
||||
externalId: null | string;
|
||||
audioStatus: components["schemas"]["ProcessingStatus"];
|
||||
/** Format: double */
|
||||
duration: number | string;
|
||||
/** Format: int32 */
|
||||
listensCount: number | string;
|
||||
/** Format: date-time */
|
||||
createdAt: string;
|
||||
/** Format: date-time */
|
||||
updatedAt: string;
|
||||
};
|
||||
TrackApplicationResponseLocalTrackApplicationResponse: {
|
||||
/** @enum {string} */
|
||||
source?: "Local";
|
||||
/** Format: int64 */
|
||||
ownerUserId: null | number | string;
|
||||
ownerUserId: number | string;
|
||||
/** Format: uuid */
|
||||
id: string;
|
||||
title: string;
|
||||
artist: null | string;
|
||||
audioStatus: components["schemas"]["ProcessingStatus"];
|
||||
/** Format: double */
|
||||
duration: number | string;
|
||||
/** Format: int32 */
|
||||
listensCount: number | string;
|
||||
/** Format: date-time */
|
||||
createdAt: string;
|
||||
/** Format: date-time */
|
||||
|
||||
@@ -40,7 +40,7 @@ export const load: PageServerLoad = async ({ params, locals, url, fetch }) => {
|
||||
const library = (libraryRes.data?.items ?? []).filter((t) => !inPlaylist.has(t.id));
|
||||
|
||||
const alreadyLinked = new Set(
|
||||
tracks.filter((t) => t.source === 'YouTube' && t.externalId).map((t) => t.externalId)
|
||||
tracks.flatMap((t) => (t.source === 'YouTube' && t.externalId ? [t.externalId] : []))
|
||||
);
|
||||
const seeds = [...new Set(tracks.map((t) => t.artist).filter((a): a is string => !!a))];
|
||||
const seed = seeds.length > 0 ? shuffle(seeds)[0] : undefined;
|
||||
|
||||
Reference in New Issue
Block a user