From fe36535aee3331a0a3d9727c97acabe6f8b4b0bf Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sat, 12 Jul 2025 21:52:12 -0700 Subject: [PATCH] improve domain types to better match OS, update normalizer functions --- src/shared/api/subsonic/subsonic-normalize.ts | 558 +++++++++--------- src/shared/types/domain/album-domain-types.ts | 62 +- .../types/domain/artist-domain-types.ts | 41 +- src/shared/types/domain/genre-domain-types.ts | 15 +- .../types/domain/playlist-domain-types.ts | 18 +- .../types/domain/search-domain-types.ts | 4 +- .../types/domain/shared-domain-types.ts | 31 +- src/shared/types/domain/song-domain-types.ts | 52 +- src/shared/types/types.ts | 4 +- 9 files changed, 417 insertions(+), 368 deletions(-) diff --git a/src/shared/api/subsonic/subsonic-normalize.ts b/src/shared/api/subsonic/subsonic-normalize.ts index 6dbb01ffb..c7fdb3648 100644 --- a/src/shared/api/subsonic/subsonic-normalize.ts +++ b/src/shared/api/subsonic/subsonic-normalize.ts @@ -1,42 +1,179 @@ -import { nanoid } from 'nanoid'; -import { z } from 'zod'; +import { components } from './subsonic-schema.d'; import { ssType } from '/@/shared/api/subsonic/subsonic-types'; import { Album } from '/@/shared/types/domain/album-domain-types'; -import { AlbumArtist, RelatedArtist } from '/@/shared/types/domain/artist-domain-types'; -import { Genre } from '/@/shared/types/domain/genre-domain-types'; -import { QueueSong } from '/@/shared/types/domain/player-domain-types'; +import { Artist, RelatedArtist } from '/@/shared/types/domain/artist-domain-types'; +import { Genre, RelatedGenre } from '/@/shared/types/domain/genre-domain-types'; import { Playlist } from '/@/shared/types/domain/playlist-domain-types'; import { ServerListItem, ServerType } from '/@/shared/types/domain/server-domain-types'; import { LibraryItem } from '/@/shared/types/domain/shared-domain-types'; +import { Song } from '/@/shared/types/domain/song-domain-types'; +import { formatDate } from '/@/shared/utils/format-date'; -const getCoverArtUrl = (args: { - baseUrl: string | undefined; - coverArtId?: string; - credential: string | undefined; - size: number; -}) => { - const size = args.size ? args.size : 250; +export const normalize = { + album: ( + item: components['schemas']['AlbumID3'] | components['schemas']['AlbumID3WithSongs'], + server: ServerListItem, + ): Album => { + const imageUrl = item.coverArt ? getCoverArtUrl(item.coverArt, server) : null; - if (!args.coverArtId || args.coverArtId.match('2a96cbd8b46e442fc41c2b86b821562f')) { - return null; - } - - return ( - `${args.baseUrl}/rest/getCoverArt.view` + - `?id=${args.coverArtId}` + - `&${args.credential}` + - '&v=1.13.0' + - '&c=Feishin' + - `&size=${size}` - ); + return { + _itemType: LibraryItem.ALBUM, + _serverId: server?.id || 'unknown', + _serverType: ServerType.SUBSONIC, + artistName: item.artist || null, + artists: getArtistList(item.artists, item.artistId, item.artist), + comment: null, + createdDate: item.created, + discTitles: getDiscTitles(item), + displayArtist: null, + duration: getDuration(item.duration), + explicit: item.explicitStatus === 'explicit', + genres: getGenres(item), + id: item.id.toString(), + imagePlaceholderUrl: null, + imageUrl, + isCompilation: item.isCompilation || null, + mbzId: item.musicBrainzId || null, + mbzReleaseGroupId: null, + missing: null, + moods: getMoods(item), + name: item.name, + originalReleaseDate: getOriginalReleaseDate(item), + participants: {}, + recordLabels: getRecordLabels(item), + releaseDate: getReleaseDate(item), + releaseTypes: getReleaseTypes(item), + releaseYear: item.year || null, + size: null, + songCount: item.songCount, + sortName: item.sortName || item.name, + tags: {}, + updatedDate: null, + userFavorite: Boolean(item.starred), + userFavoriteDate: item.starred || null, + userLastPlayedDate: item.played || null, + userPlayCount: item.playCount ?? null, + userRating: item.userRating || null, + version: item.version || null, + }; + }, + albumArtist: ( + item: components['schemas']['ArtistID3'] & components['schemas']['ArtistInfo2'], + server: ServerListItem, + ): Artist => { + return { + _serverId: server?.id || 'unknown', + _serverType: ServerType.SUBSONIC, + albumCount: item.albumCount ? Number(item.albumCount) : 0, + biography: item.biography || null, + duration: null, + genres: [], + id: item.id.toString(), + imageUrl: item.coverArt ? getCoverArtUrl(item.coverArt.toString(), server) : null, + itemType: LibraryItem.ALBUM_ARTIST, + mbzId: item.musicBrainzId || null, + name: item.name, + playCount: null, + similarArtists: [], + songCount: null, + userFavorite: false, + userLastPlayedDate: null, + userRating: null, + }; + }, + genre: (item: components['schemas']['Genre'], server: ServerListItem): Genre => { + return { + _itemType: LibraryItem.GENRE, + _serverId: server.id, + _serverType: ServerType.SUBSONIC, + albumCount: item.albumCount, + id: item.value, + imageUrl: null, + name: item.value, + songCount: item.songCount, + }; + }, + playlist: (item: components['schemas']['Playlist'], server: ServerListItem): Playlist => { + return { + _itemType: LibraryItem.PLAYLIST, + _serverId: server.id, + _serverType: ServerType.SUBSONIC, + createdDate: item.created || null, + description: item.comment || null, + duration: getDuration(item.duration), + genres: [], + id: item.id.toString(), + imageUrl: item.coverArt ? getCoverArtUrl(item.coverArt.toString(), server) : null, + name: item.name, + owner: item.owner || null, + ownerId: item.owner || null, + public: item.public || null, + size: null, + songCount: item.songCount, + updatedDate: item.changed, + }; + }, + song: (item: components['schemas']['Child'], server: ServerListItem): Song => { + return { + _itemType: LibraryItem.SONG, + _serverId: server.id, + _serverType: ServerType.SUBSONIC, + album: item.album || null, + albumArtistName: item.displayAlbumArtist || null, + albumArtists: getArtistList(item.albumArtists, item.artistId, item.artist), + albumId: item.albumId || null, + artistName: item.displayArtist || item.artist || null, + artists: getArtistList(item.artists, item.artistId, item.artist), + bitDepth: item.bitDepth || null, + bitRate: item.bitRate || null, + bpm: item.bpm || null, + channels: item.channelCount || null, + comment: item.comment || null, + composer: item.displayComposer || null, + container: item.contentType || null, + createdDate: item.created || null, + discNumber: item.discNumber || 1, + discSubtitle: null, + duration: getDuration(item.duration), + explicit: item.explicitStatus === 'explicit', + gain: getGainInfo(item), + genres: getGenres(item), + id: item.id.toString(), + imageUrl: item.coverArt ? getCoverArtUrl(item.coverArt, server) : null, + isCompilation: null, + isrc: item.isrc || [], + lyrics: null, + mbzId: item.musicBrainzId || null, + missing: false, + moods: getMoods(item), + name: item.title, + participants: getParticipants(item), + path: item.path || null, + peak: getPeakInfo(item), + playCount: item?.playCount || 0, + releaseDate: null, + releaseYear: item.year ?? null, + samplingRate: item.samplingRate || null, + size: item.size ?? 0, + sortName: item.sortName || item.title, + streamUrl: getStreamUrl(item.id, server), + tags: {}, + trackNumber: item.track || 1, + updatedDate: null, + userFavorite: Boolean(item.starred), + userFavoriteDate: item.starred || null, + userLastPlayedDate: item.played || null, + userRating: item.userRating || null, + }; + }, }; -const getArtistList = ( +function getArtistList( artists?: typeof ssType._response.song._type.artists, artistId?: number | string, artistName?: string, -) => { +) { return artists ? artists.map((item) => ({ id: item.id.toString(), @@ -50,19 +187,94 @@ const getArtistList = ( name: artistName || '', }, ]; -}; +} -const getParticipants = ( +function getCoverArtUrl(id: string, server: ServerListItem) { + return ( + `${server.url}/rest/getCoverArt.view` + + `?id=${id}` + + `&${server.credential}` + + '&v=1.16.1' + + '&c=Feishin' + ); +} + +function getDiscTitles( + item: components['schemas']['AlbumID3'] | components['schemas']['AlbumID3WithSongs'], +) { + return (item.discTitles || []).map((discTitle) => ({ + disc: discTitle.disc, + title: discTitle.title, + })); +} + +function getDuration(duration?: number) { + // Transform from seconds to milliseconds + return duration ? duration * 1000 : 0; +} + +function getGainInfo(item: components['schemas']['Child']) { + return item.replayGain && (item.replayGain.albumGain || item.replayGain.trackGain) + ? { + album: item.replayGain.albumGain, + track: item.replayGain.trackGain, + } + : null; +} + +function getGenres( item: - | z.infer - | z.infer - | z.infer, -) => { - let participants: null | Record = null; + | components['schemas']['AlbumID3'] + | components['schemas']['AlbumID3WithSongs'] + | components['schemas']['Child'], +): RelatedGenre[] { + if (item.genres) { + return item.genres.map((genre) => ({ + id: genre.name, + imageUrl: null, + name: genre.name, + })); + } + + if (item.genre) { + return [ + { + id: item.genre, + imageUrl: null, + name: item.genre, + }, + ]; + } + + return []; +} + +function getMoods( + item: + | components['schemas']['AlbumID3'] + | components['schemas']['AlbumID3WithSongs'] + | components['schemas']['Child'], +) { + return (item.moods || []).map((mood) => ({ + id: mood, + name: mood, + })); +} + +function getOriginalReleaseDate( + item: components['schemas']['AlbumID3'] | components['schemas']['AlbumID3WithSongs'], +) { + return item.originalReleaseDate + ? formatDate.toUTCDate( + `${item.originalReleaseDate.year}-${item.originalReleaseDate.month}-${item.originalReleaseDate.day}`, + ) + : null; +} + +function getParticipants(item: components['schemas']['Child']) { + const participants: Record = {}; if (item.contributors) { - participants = {}; - for (const contributor of item.contributors) { const artist = { id: contributor.artist.id?.toString() || '', @@ -74,248 +286,54 @@ const getParticipants = ( ? `${contributor.role} (${contributor.subRole})` : contributor.role; - if (role in participants) { - participants[role].push(artist); - } else { - participants[role] = [artist]; + if (!participants[role]) { + participants[role] = []; } + + participants[role].push(artist); } } return participants; -}; +} -const getGenres = ( - item: - | z.infer - | z.infer - | z.infer, -): Genre[] => { - return item.genres - ? item.genres.map((genre) => ({ - id: genre.name, - imageUrl: null, - itemType: LibraryItem.GENRE, - name: genre.name, - })) - : item.genre - ? [ - { - id: item.genre, - imageUrl: null, - itemType: LibraryItem.GENRE, - name: item.genre, - }, - ] - : []; -}; +function getPeakInfo(item: components['schemas']['Child']) { + return item.replayGain && (item.replayGain.albumPeak || item.replayGain.trackPeak) + ? { + album: item.replayGain.albumPeak, + track: item.replayGain.trackPeak, + } + : null; +} -const normalizeSong = ( - item: z.infer, - server: null | ServerListItem, - size?: number, -): QueueSong => { - const imageUrl = - getCoverArtUrl({ - baseUrl: server?.url, - coverArtId: item.coverArt?.toString(), - credential: server?.credential, - size: size || 300, - }) || null; +function getRecordLabels( + item: components['schemas']['AlbumID3'] | components['schemas']['AlbumID3WithSongs'], +) { + return (item.recordLabels || []).map((recordLabel) => ({ + id: recordLabel.name, + name: recordLabel.name, + })); +} - const streamUrl = `${server?.url}/rest/stream.view?id=${item.id}&v=1.13.0&c=Feishin&${server?.credential}`; +function getReleaseDate( + item: components['schemas']['AlbumID3'] | components['schemas']['AlbumID3WithSongs'], +) { + return item.releaseDate + ? formatDate.toUTCDate( + `${item.releaseDate.year}-${item.releaseDate.month}-${item.releaseDate.day}`, + ) + : null; +} - return { - album: item.album || '', - albumArtists: getArtistList(item.albumArtists, item.artistId, item.artist), - albumId: item.albumId?.toString() || '', - artistName: item.artist || '', - artists: getArtistList(item.artists, item.artistId, item.artist), - bitDepth: item.bitDepth || null, - bitRate: item.bitRate || 0, - bpm: item.bpm || null, - channels: item.channelCount || null, - comment: null, - compilation: null, - container: item.contentType, - createdAt: item.created, - discNumber: item.discNumber || 1, - discSubtitle: null, - duration: item.duration ? item.duration * 1000 : 0, - gain: - item.replayGain && (item.replayGain.albumGain || item.replayGain.trackGain) - ? { - album: item.replayGain.albumGain, - track: item.replayGain.trackGain, - } - : null, - genres: getGenres(item), - id: item.id.toString(), - imagePlaceholderUrl: null, - imageUrl, - itemType: LibraryItem.SONG, - lastPlayedAt: null, - lyrics: null, - name: item.title, - participants: getParticipants(item), - path: item.path, - peak: - item.replayGain && (item.replayGain.albumPeak || item.replayGain.trackPeak) - ? { - album: item.replayGain.albumPeak, - track: item.replayGain.trackPeak, - } - : null, - playCount: item?.playCount || 0, - releaseDate: null, - releaseYear: item.year ? String(item.year) : null, - sampleRate: item.samplingRate || null, - serverId: server?.id || 'unknown', - serverType: ServerType.SUBSONIC, - size: item.size, - streamUrl, - tags: null, - trackNumber: item.track || 1, - uniqueId: nanoid(), - updatedAt: '', - userFavorite: item.starred || false, - userRating: item.userRating || null, - }; -}; +function getReleaseTypes( + item: components['schemas']['AlbumID3'] | components['schemas']['AlbumID3WithSongs'], +) { + return (item.releaseTypes || []).map((releaseType) => ({ + id: releaseType, + name: releaseType, + })); +} -const normalizeAlbumArtist = ( - item: - | z.infer - | z.infer, - server: null | ServerListItem, - imageSize?: number, -): AlbumArtist => { - const imageUrl = - getCoverArtUrl({ - baseUrl: server?.url, - coverArtId: item.coverArt?.toString(), - credential: server?.credential, - size: imageSize || 100, - }) || null; - - return { - albumCount: item.albumCount ? Number(item.albumCount) : 0, - backgroundImageUrl: null, - biography: null, - duration: null, - genres: [], - id: item.id.toString(), - imageUrl, - itemType: LibraryItem.ALBUM_ARTIST, - lastPlayedAt: null, - mbz: null, - name: item.name, - playCount: null, - serverId: server?.id || 'unknown', - serverType: ServerType.SUBSONIC, - similarArtists: [], - songCount: null, - userFavorite: false, - userRating: null, - }; -}; - -const normalizeAlbum = ( - item: z.infer | z.infer, - server: null | ServerListItem, - imageSize?: number, -): Album => { - const imageUrl = - getCoverArtUrl({ - baseUrl: server?.url, - coverArtId: item.coverArt?.toString(), - credential: server?.credential, - size: imageSize || 300, - }) || null; - - return { - albumArtist: item.artist, - albumArtists: getArtistList(item.artists, item.artistId, item.artist), - artists: [], - backdropImageUrl: null, - comment: null, - createdAt: item.created, - duration: item.duration * 1000, - genres: getGenres(item), - id: item.id.toString(), - imagePlaceholderUrl: null, - imageUrl, - isCompilation: null, - itemType: LibraryItem.ALBUM, - lastPlayedAt: null, - mbzId: null, - name: item.name, - originalDate: null, - participants: getParticipants(item), - playCount: null, - releaseDate: item.year ? new Date(Date.UTC(item.year, 0, 1)).toISOString() : null, - releaseYear: item.year ? Number(item.year) : null, - serverId: server?.id || 'unknown', - serverType: ServerType.SUBSONIC, - size: null, - songCount: item.songCount, - songs: - (item as z.infer).song?.map((song) => - normalizeSong(song, server), - ) || [], - tags: null, - uniqueId: nanoid(), - updatedAt: item.created, - userFavorite: item.starred || false, - userRating: item.userRating || null, - }; -}; - -const normalizePlaylist = ( - item: - | z.infer - | z.infer, - server: null | ServerListItem, -): Playlist => { - return { - description: item.comment || null, - duration: item.duration * 1000, - genres: [], - id: item.id.toString(), - imagePlaceholderUrl: null, - imageUrl: getCoverArtUrl({ - baseUrl: server?.url, - coverArtId: item.coverArt?.toString(), - credential: server?.credential, - size: 300, - }), - itemType: LibraryItem.PLAYLIST, - name: item.name, - owner: item.owner, - ownerId: item.owner, - public: item.public, - serverId: server?.id || 'unknown', - serverType: ServerType.SUBSONIC, - size: null, - songCount: item.songCount, - }; -}; - -const normalizeGenre = (item: z.infer): Genre => { - return { - albumCount: item.albumCount, - id: item.value, - imageUrl: null, - itemType: LibraryItem.GENRE, - name: item.value, - songCount: item.songCount, - }; -}; - -export const ssNormalize = { - album: normalizeAlbum, - albumArtist: normalizeAlbumArtist, - genre: normalizeGenre, - playlist: normalizePlaylist, - song: normalizeSong, -}; +function getStreamUrl(id: string, server: ServerListItem) { + return `${server.url}/rest/stream.view?id=${id}&v=1.16.1&c=Feishin&${server.credential}`; +} diff --git a/src/shared/types/domain/album-domain-types.ts b/src/shared/types/domain/album-domain-types.ts index 8c4f12401..c00d94fdd 100644 --- a/src/shared/types/domain/album-domain-types.ts +++ b/src/shared/types/domain/album-domain-types.ts @@ -8,9 +8,17 @@ import { NDAlbumListSort } from '/@/shared/api/navidrome.types'; import { ndType } from '/@/shared/api/navidrome/navidrome-types'; import { BasePaginatedResponse, BaseQuery } from '/@/shared/types/adapter/api-controller-types'; import { RelatedArtist } from '/@/shared/types/domain/artist-domain-types'; -import { Genre } from '/@/shared/types/domain/genre-domain-types'; +import { RelatedGenre } from '/@/shared/types/domain/genre-domain-types'; import { ServerType } from '/@/shared/types/domain/server-domain-types'; -import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types'; +import { + LibraryItem, + ListSortOrder, + Mood, + Participants, + RecordLabel, + ReleaseType, + Tags, +} from '/@/shared/types/domain/shared-domain-types'; import { Song } from '/@/shared/types/domain/song-domain-types'; export enum AlbumListSortOptions { @@ -150,52 +158,45 @@ export const albumListSortMap: AlbumListSortMap = { }; export type Album = { - albumArtist: string; - albumArtists: RelatedArtist[]; + _itemType: LibraryItem.ALBUM; + _serverId: string; + _serverType: ServerType; + artistName: null | string; artists: RelatedArtist[]; - backdropImageUrl: null | string; comment: null | string; - createdDate: string; - description: null | string; - discTitles: { - disc: number; - title: string; - }[]; + createdDate: null | string; + discTitles: DiscTitle[]; displayArtist: null | string; duration: null | number; - genres: Genre[]; + explicit: boolean | null; + genres: RelatedGenre[]; id: string; imagePlaceholderUrl: null | string; imageUrl: null | string; isCompilation: boolean | null; - itemType: LibraryItem.ALBUM; - mbzAlbumId: null | string; + mbzId: null | string; mbzReleaseGroupId: null | string; - missing: boolean; + missing: boolean | null; + moods: Mood[]; name: string; originalReleaseDate: null | string; - participants: null | Record; + participants: Participants; + recordLabels: RecordLabel[]; releaseDate: null | string; - releaseTypes: { - id: string; - name: string; - }[]; + releaseTypes: ReleaseType[]; releaseYear: null | number; - serverId: string; - serverType: ServerType; size: null | number; songCount: null | number; - songs?: Song[]; sortName: string; - tags: Record; - uniqueId: string; - updatedDate: string; + tags: Tags; + updatedDate: null | string; userFavorite: boolean; userFavoriteDate: null | string; userLastPlayedDate: null | string; userPlayCount: null | number; userRating: null | number; -} & { songs?: Song[] }; + version: null | string; +}; export type AlbumDetailQuery = { id: string }; @@ -208,6 +209,13 @@ export type AlbumInfo = { notes: null | string; }; +export type AlbumWithSongs = Album & { songs: Song[] }; + +export type DiscTitle = { + disc: number; + title: string; +}; + export const sortAlbumList = (albums: Album[], sortBy: AlbumListSort, sortOrder: ListSortOrder) => { let results = albums; diff --git a/src/shared/types/domain/artist-domain-types.ts b/src/shared/types/domain/artist-domain-types.ts index 9b1f3ee58..a1e8bda79 100644 --- a/src/shared/types/domain/artist-domain-types.ts +++ b/src/shared/types/domain/artist-domain-types.ts @@ -7,7 +7,7 @@ import { jfType } from '/@/shared/api/jellyfin/jellyfin-types'; import { NDAlbumArtistListSort } from '/@/shared/api/navidrome.types'; import { ndType } from '/@/shared/api/navidrome/navidrome-types'; import { BasePaginatedResponse, BaseQuery } from '/@/shared/types/adapter/api-controller-types'; -import { Genre } from '/@/shared/types/domain/genre-domain-types'; +import { RelatedGenre } from '/@/shared/types/domain/genre-domain-types'; import { ServerType } from '/@/shared/types/domain/server-domain-types'; import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types'; @@ -31,45 +31,26 @@ export const ArtistListSortOptionsLabels = { [ArtistListSortOptions.TRACK_COUNT]: i18n.t('filter.trackCount'), }; -export type AlbumArtist = { +export type Artist = { + _serverId: string; + _serverType: ServerType; albumCount: null | number; - backgroundImageUrl: null | string; biography: null | string; duration: null | number; - genres: Genre[]; + genres: RelatedGenre[]; id: string; imageUrl: null | string; itemType: LibraryItem.ALBUM_ARTIST; - lastPlayedAt: null | string; - mbz: null | string; + mbzId: null | string; name: string; playCount: null | number; - serverId: string; - serverType: ServerType; similarArtists: null | RelatedArtist[]; songCount: null | number; userFavorite: boolean; + userLastPlayedDate: null | string; userRating: null | number; }; -export type Artist = { - biography: null | string; - createdAt: string; - id: string; - itemType: LibraryItem.ARTIST; - name: string; - remoteCreatedAt: null | string; - serverFolderId: string; - serverId: string; - serverType: ServerType; - updatedAt: string; -}; - -export type RelatedAlbumArtist = { - id: string; - name: string; -}; - export type RelatedArtist = { id: string; imageUrl: null | string; @@ -142,7 +123,7 @@ export type AlbumArtistDetailQuery = { id: string }; export type AlbumArtistDetailRequest = { query: AlbumArtistDetailQuery }; -export type AlbumArtistDetailResponse = AlbumArtist | null; +export type AlbumArtistDetailResponse = Artist | null; export interface ArtistListQuery extends BaseQuery { _custom?: { @@ -158,7 +139,7 @@ export interface ArtistListQuery extends BaseQuery { export type ArtistListRequest = { query: ArtistListQuery }; -export type ArtistListResponse = BasePaginatedResponse | null | undefined; +export type ArtistListResponse = BasePaginatedResponse | null | undefined; type ArtistListSortMap = { jellyfin: Record; navidrome: Record; @@ -233,7 +214,7 @@ export interface AlbumArtistListQuery extends BaseQuery { export type AlbumArtistListRequest = { query: AlbumArtistListQuery }; -export type AlbumArtistListResponse = BasePaginatedResponse | null | undefined; +export type AlbumArtistListResponse = BasePaginatedResponse | null | undefined; export type ArtistInfoQuery = { artistId: string; @@ -244,7 +225,7 @@ export type ArtistInfoQuery = { export type ArtistInfoRequest = { query: ArtistInfoQuery }; export const sortAlbumArtistList = ( - artists: AlbumArtist[], + artists: Artist[], sortBy: AlbumArtistListSort | ArtistListSort, sortOrder: ListSortOrder, ) => { diff --git a/src/shared/types/domain/genre-domain-types.ts b/src/shared/types/domain/genre-domain-types.ts index 1b8b29aa2..2ca32142c 100644 --- a/src/shared/types/domain/genre-domain-types.ts +++ b/src/shared/types/domain/genre-domain-types.ts @@ -2,6 +2,7 @@ import i18n from '/@/i18n/i18n'; import { JFGenreListSort } from '/@/shared/api/jellyfin.types'; import { NDGenreListSort } from '/@/shared/api/navidrome.types'; import { BasePaginatedResponse, BaseQuery } from '/@/shared/types/adapter/api-controller-types'; +import { ServerType } from '/@/shared/types/domain/server-domain-types'; import { LibraryItem } from '/@/shared/types/domain/shared-domain-types'; import { UserListSort } from '/@/shared/types/domain/user-domain-types'; @@ -18,12 +19,14 @@ export const GenreListSortOptionsLabels = { }; export type Genre = { - albumCount?: number; + _itemType: LibraryItem.GENRE; + _serverId: string; + _serverType: ServerType; + albumCount: null | number; id: string; imageUrl: null | string; - itemType: LibraryItem.GENRE; name: string; - songCount?: number; + songCount: null | number; }; export interface GenreListQuery extends BaseQuery { @@ -41,6 +44,12 @@ export type GenreListRequest = { query: GenreListQuery }; export type GenreListResponse = BasePaginatedResponse | null | undefined; +export type RelatedGenre = { + id: string; + imageUrl: null | string; + name: string; +}; + type GenreListSortMap = { jellyfin: Record; navidrome: Record; diff --git a/src/shared/types/domain/playlist-domain-types.ts b/src/shared/types/domain/playlist-domain-types.ts index 034ad8486..d3fe6b1f8 100644 --- a/src/shared/types/domain/playlist-domain-types.ts +++ b/src/shared/types/domain/playlist-domain-types.ts @@ -10,7 +10,7 @@ import { BasePaginatedResponse, BaseQuery, } from '/@/shared/types/adapter/api-controller-types'; -import { Genre } from '/@/shared/types/domain/genre-domain-types'; +import { Genre, RelatedGenre } from '/@/shared/types/domain/genre-domain-types'; import { ServerType } from '/@/shared/types/domain/server-domain-types'; import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types'; import { Song, SongListSort } from '/@/shared/types/domain/song-domain-types'; @@ -89,24 +89,26 @@ export type DeletePlaylistRequest = { export type DeletePlaylistResponse = null | undefined; export type Playlist = { + _itemType: LibraryItem.PLAYLIST; + _serverId: string; + _serverType: ServerType; + createdDate: null | string; description: null | string; duration: null | number; - genres: Genre[]; + genres: RelatedGenre[]; id: string; - imagePlaceholderUrl: null | string; imageUrl: null | string; - itemType: LibraryItem.PLAYLIST; name: string; owner: null | string; ownerId: null | string; public: boolean | null; rules?: null | Record; - serverId: string; - serverType: ServerType; size: null | number; songCount: null | number; sync?: boolean | null; + updatedDate: null | string; }; + export interface PlaylistListQuery extends BaseQuery { _custom?: { jellyfin?: Partial>; @@ -121,6 +123,10 @@ export type PlaylistListRequest = { query: PlaylistListQuery }; export type PlaylistListResponse = BasePaginatedResponse | null | undefined; +export type PlaylistSong = Song & { + playlistItemId: string; +}; + export type RemoveFromPlaylistQuery = { id: string; songId: string[]; diff --git a/src/shared/types/domain/search-domain-types.ts b/src/shared/types/domain/search-domain-types.ts index ce9ad7543..cfa91acaa 100644 --- a/src/shared/types/domain/search-domain-types.ts +++ b/src/shared/types/domain/search-domain-types.ts @@ -1,5 +1,5 @@ import { Album } from '/@/shared/types/domain/album-domain-types'; -import { AlbumArtist } from '/@/shared/types/domain/artist-domain-types'; +import { Artist } from '/@/shared/types/domain/artist-domain-types'; import { Song } from '/@/shared/types/domain/song-domain-types'; export type SearchAlbumArtistsQuery = { @@ -30,7 +30,7 @@ export type SearchQuery = { export type SearchRequest = { query: SearchQuery }; export type SearchResponse = { - albumArtists: AlbumArtist[]; + albumArtists: Artist[]; albums: Album[]; songs: Song[]; }; diff --git a/src/shared/types/domain/shared-domain-types.ts b/src/shared/types/domain/shared-domain-types.ts index 739878c9b..e01ce6e40 100644 --- a/src/shared/types/domain/shared-domain-types.ts +++ b/src/shared/types/domain/shared-domain-types.ts @@ -1,7 +1,7 @@ import { JFSortOrder } from '/@/shared/api/jellyfin.types'; import { NDSortOrder } from '/@/shared/api/navidrome.types'; import { Album } from '/@/shared/types/domain/album-domain-types'; -import { AlbumArtist, Artist } from '/@/shared/types/domain/artist-domain-types'; +import { Artist, Artist, RelatedArtist } from '/@/shared/types/domain/artist-domain-types'; import { QueueSong } from '/@/shared/types/domain/player-domain-types'; import { Playlist } from '/@/shared/types/domain/playlist-domain-types'; import { Song } from '/@/shared/types/domain/song-domain-types'; @@ -19,15 +19,9 @@ export enum ListSortOrder { DESC = 'DESC', } -export type AnyLibraryItem = Album | AlbumArtist | Artist | Playlist | QueueSong | Song; +export type AnyLibraryItem = Album | Artist | Artist | Playlist | QueueSong | Song; -export type AnyLibraryItems = - | Album[] - | AlbumArtist[] - | Artist[] - | Playlist[] - | QueueSong[] - | Song[]; +export type AnyLibraryItems = Album[] | Artist[] | Artist[] | Playlist[] | QueueSong[] | Song[]; type SortOrderMap = { jellyfin: Record; navidrome: Record; @@ -56,3 +50,22 @@ export type FontData = { postscriptName: string; style: string; }; + +export type Mood = { + id: string; + name: string; +}; + +export type Participants = Record; + +export type RecordLabel = { + id: string; + name: string; +}; + +export type ReleaseType = { + id: string; + name: string; +}; + +export type Tags = Record; diff --git a/src/shared/types/domain/song-domain-types.ts b/src/shared/types/domain/song-domain-types.ts index 66d9942e3..a11432b89 100644 --- a/src/shared/types/domain/song-domain-types.ts +++ b/src/shared/types/domain/song-domain-types.ts @@ -8,10 +8,16 @@ import { NDSongListSort } from '/@/shared/api/navidrome.types'; import { ndType } from '/@/shared/api/navidrome/navidrome-types'; import { BasePaginatedResponse, BaseQuery } from '/@/shared/types/adapter/api-controller-types'; import { RelatedArtist } from '/@/shared/types/domain/artist-domain-types'; -import { Genre } from '/@/shared/types/domain/genre-domain-types'; +import { RelatedGenre } from '/@/shared/types/domain/genre-domain-types'; import { Played, QueueSong } from '/@/shared/types/domain/player-domain-types'; import { ServerType } from '/@/shared/types/domain/server-domain-types'; -import { LibraryItem, ListSortOrder } from '/@/shared/types/domain/shared-domain-types'; +import { + LibraryItem, + ListSortOrder, + Mood, + Participants, + Tags, +} from '/@/shared/types/domain/shared-domain-types'; export enum SongListSortOptions { ALBUM = 'album', @@ -77,46 +83,54 @@ export enum SongListSort { } export type Song = { + _itemType: LibraryItem.SONG; + _serverId: string; + _serverType: ServerType; album: null | string; + albumArtistName: null | string; albumArtists: RelatedArtist[]; - albumId: string; - artistName: string; + albumId: null | string; + artistName: null | string; artists: RelatedArtist[]; - bitRate: number; + bitDepth: null | number; + bitRate: null | number; bpm: null | number; channels: null | number; comment: null | string; - compilation: boolean | null; + composer: null | string; container: null | string; - createdAt: string; + createdDate: null | string; discNumber: number; discSubtitle: null | string; duration: number; + explicit: boolean | null; gain: GainInfo | null; - genres: Genre[]; + genres: RelatedGenre[]; id: string; - imagePlaceholderUrl: null | string; imageUrl: null | string; - itemType: LibraryItem.SONG; - lastPlayedAt: null | string; + isCompilation: boolean | null; + isrc: string[]; lyrics: null | string; + mbzId: null | string; + missing: boolean | null; + moods: Mood[]; name: string; - participants: null | Record; + participants: Participants; path: null | string; peak: GainInfo | null; playCount: number; - playlistItemId?: string; releaseDate: null | string; - releaseYear: null | string; - serverId: string; - serverType: ServerType; + releaseYear: null | number; + samplingRate: null | number; size: number; + sortName: string; streamUrl: string; - tags: null | Record; + tags: Tags; trackNumber: number; - uniqueId: string; - updatedAt: string; + updatedDate: null | string; userFavorite: boolean; + userFavoriteDate: null | string; + userLastPlayedDate: null | string; userRating: null | number; }; diff --git a/src/shared/types/types.ts b/src/shared/types/types.ts index ca5992de0..a5bd78b44 100644 --- a/src/shared/types/types.ts +++ b/src/shared/types/types.ts @@ -3,7 +3,7 @@ import { ReactNode } from 'react'; import { Song } from 'src/main/features/core/lyrics/netease'; import { Album } from '/@/shared/types/domain/album-domain-types'; -import { AlbumArtist, Artist } from '/@/shared/types/domain/artist-domain-types'; +import { Artist } from '/@/shared/types/domain/artist-domain-types'; import { QueueSong } from '/@/shared/types/domain/player-domain-types'; import { Playlist } from '/@/shared/types/domain/playlist-domain-types'; import { ServerType } from '/@/shared/types/domain/server-domain-types'; @@ -159,7 +159,7 @@ export enum TableColumn { export type GridCardData = { cardControls: any; - cardRows: CardRow[]; + cardRows: CardRow[]; columnCount: number; display: ListDisplayType; handleFavorite: (options: { id: string[]; isFavorite: boolean; itemType: LibraryItem }) => void;