begin reorganizing domain types

This commit is contained in:
jeffvli
2025-07-06 15:04:14 -07:00
parent c36735575f
commit 9147b041f3
20 changed files with 1400 additions and 1165 deletions
+5 -1
View File
@@ -227,7 +227,11 @@
"songCount": "song count",
"title": "title",
"toYear": "to year",
"trackNumber": "track"
"trackNumber": "track",
"createdAt": "created at",
"updatedAt": "updated at",
"type": "type",
"email": "email"
},
"form": {
"addServer": {
+1 -1
View File
@@ -17,7 +17,7 @@ import {
getSearchResults as searchNetease,
} from './netease';
import { Song } from '/@/shared/types/domain-types';
import { Song } from '/@/shared/types/domain/song-domain-types';
export enum LyricSource {
GENIUS = 'Genius',
+2 -4
View File
@@ -1,9 +1,7 @@
import Fuse from 'fuse.js';
import {
InternetProviderLyricSearchResponse,
LyricSearchQuery,
} from '/@/shared/types/domain-types';
import { InternetProviderLyricSearchResponse } from '/@/shared/types/domain/lyric-domain-types';
import { LyricSearchQuery } from '/@/shared/types/domain/lyric-domain-types';
export const orderSearchResults = (args: {
params: LyricSearchQuery;
+11 -11
View File
@@ -3,17 +3,17 @@ import { z } from 'zod';
import { JFAlbum, JFGenre, JFMusicFolder, JFPlaylist } from '/@/shared/api/jellyfin.types';
import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
import { LibraryItem } from '/@/shared/types/domain-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 { Playlist } from '/@/shared/types/domain/playlist-domain-types';
import {
Album,
AlbumArtist,
Genre,
LibraryItem,
MusicFolder,
Playlist,
RelatedArtist,
Song,
} from '/@/shared/types/domain-types';
import { ServerListItem, ServerType } from '/@/shared/types/types';
ServerListItem,
ServerMusicFolder,
ServerType,
} from '/@/shared/types/domain/server-domain-types';
import { Song } from '/@/shared/types/domain/song-domain-types';
const getStreamUrl = (args: {
container?: string;
@@ -432,7 +432,7 @@ const normalizePlaylist = (
};
};
const normalizeMusicFolder = (item: JFMusicFolder): MusicFolder => {
const normalizeMusicFolder = (item: JFMusicFolder): ServerMusicFolder => {
return {
id: item.Id,
name: item.Name,
@@ -4,17 +4,14 @@ import z from 'zod';
import { NDGenre } from '/@/shared/api/navidrome.types';
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
import { ssType } from '/@/shared/api/subsonic/subsonic-types';
import {
Album,
AlbumArtist,
Genre,
LibraryItem,
Playlist,
RelatedArtist,
Song,
User,
} from '/@/shared/types/domain-types';
import { ServerListItem, ServerType } from '/@/shared/types/types';
import { LibraryItem } from '/@/shared/types/domain-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 { Playlist } from '/@/shared/types/domain/playlist-domain-types';
import { ServerListItem, ServerType } from '/@/shared/types/domain/server-domain-types';
import { Song } from '/@/shared/types/domain/song-domain-types';
import { User } from '/@/shared/types/domain/user-domain-types';
const getImageUrl = (args: { url: null | string }) => {
const { url } = args;
+6 -11
View File
@@ -2,17 +2,12 @@ import { nanoid } from 'nanoid';
import { z } from 'zod';
import { ssType } from '/@/shared/api/subsonic/subsonic-types';
import {
Album,
AlbumArtist,
Genre,
LibraryItem,
Playlist,
QueueSong,
RelatedArtist,
ServerListItem,
ServerType,
} from '/@/shared/types/domain-types';
import { LibraryItem, QueueSong } from '/@/shared/types/domain-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 { Playlist } from '/@/shared/types/domain/playlist-domain-types';
import { ServerListItem, ServerType } from '/@/shared/types/domain/server-domain-types';
const getCoverArtUrl = (args: {
baseUrl: string | undefined;
+1 -1
View File
@@ -4,7 +4,7 @@ import semverCoerce from 'semver/functions/coerce';
import semverGte from 'semver/functions/gte';
import { z } from 'zod';
import { ServerListItem } from '/@/shared/types/domain-types';
import { ServerListItem } from '/@/shared/types/domain/server-domain-types';
import { ServerFeature } from '/@/shared/types/features-types';
// Since ts-rest client returns a strict response type, we need to add the headers to the body object
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,196 @@
import i18n from 'src/i18n/i18n';
import { z } from 'zod';
import { JFAlbumListSort } from '/@/shared/api/jellyfin.types';
import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
import { NDAlbumListSort } from '/@/shared/api/navidrome.types';
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
import {
BaseEndpointArgs,
BasePaginatedResponse,
BaseQuery,
LibraryItem,
} from '/@/shared/types/domain-types';
import { RelatedArtist } from '/@/shared/types/domain/artist-domain-types';
import { Genre } from '/@/shared/types/domain/genre-domain-types';
import { ServerType } from '/@/shared/types/domain/server-domain-types';
import { Song } from '/@/shared/types/domain/song-domain-types';
export enum AlbumListSortOptions {
ALBUM_ARTIST = 'albumArtist',
ARTIST = 'artist',
COMMUNITY_RATING = 'communityRating',
CRITIC_RATING = 'criticRating',
DATE_ADDED = 'dateAdded',
DATE_PLAYED = 'datePlayed',
DURATION = 'duration',
IS_FAVORITE = 'isFavorite',
NAME = 'name',
PLAY_COUNT = 'playCount',
RANDOM = 'random',
RATING = 'rating',
RELEASE_DATE = 'releaseDate',
TRACK_COUNT = 'trackCount',
YEAR = 'year',
}
export const AlbumListSortOptionsLabels = {
[AlbumListSortOptions.ALBUM_ARTIST]: i18n.t('filter.albumArtist'),
[AlbumListSortOptions.ARTIST]: i18n.t('filter.artist'),
[AlbumListSortOptions.COMMUNITY_RATING]: i18n.t('filter.communityRating'),
[AlbumListSortOptions.CRITIC_RATING]: i18n.t('filter.criticRating'),
[AlbumListSortOptions.DATE_ADDED]: i18n.t('filter.dateAdded'),
[AlbumListSortOptions.DATE_PLAYED]: i18n.t('filter.datePlayed'),
[AlbumListSortOptions.DURATION]: i18n.t('filter.duration'),
[AlbumListSortOptions.IS_FAVORITE]: i18n.t('filter.isFavorite'),
[AlbumListSortOptions.NAME]: i18n.t('filter.name'),
[AlbumListSortOptions.PLAY_COUNT]: i18n.t('filter.playCount'),
[AlbumListSortOptions.RANDOM]: i18n.t('filter.random'),
[AlbumListSortOptions.RATING]: i18n.t('filter.rating'),
[AlbumListSortOptions.RELEASE_DATE]: i18n.t('filter.releaseDate'),
[AlbumListSortOptions.TRACK_COUNT]: i18n.t('filter.trackCount'),
[AlbumListSortOptions.YEAR]: i18n.t('filter.year'),
};
export enum AlbumListSort {
ALBUM_ARTIST = 'albumArtist',
ARTIST = 'artist',
COMMUNITY_RATING = 'communityRating',
CRITIC_RATING = 'criticRating',
DURATION = 'duration',
FAVORITED = 'favorited',
NAME = 'name',
PLAY_COUNT = 'playCount',
RANDOM = 'random',
RATING = 'rating',
RECENTLY_ADDED = 'recentlyAdded',
RECENTLY_PLAYED = 'recentlyPlayed',
RELEASE_DATE = 'releaseDate',
SONG_COUNT = 'songCount',
YEAR = 'year',
}
export type AlbumListArgs = BaseEndpointArgs & { query: AlbumListQuery };
export interface AlbumListQuery extends BaseQuery<AlbumListSort> {
_custom?: {
jellyfin?: Partial<z.infer<typeof jfType._parameters.albumList>>;
navidrome?: Partial<z.infer<typeof ndType._parameters.albumList>>;
};
artistIds?: string[];
compilation?: boolean;
favorite?: boolean;
genres?: string[];
limit?: number;
maxYear?: number;
minYear?: number;
musicFolderId?: string;
searchTerm?: string;
startIndex: number;
}
// Album List
export type AlbumListResponse = BasePaginatedResponse<Album[]> | null | undefined;
type AlbumListSortMap = {
jellyfin: Record<AlbumListSort, JFAlbumListSort | undefined>;
navidrome: Record<AlbumListSort, NDAlbumListSort | undefined>;
subsonic: Record<AlbumListSort, undefined>;
};
export const albumListSortMap: AlbumListSortMap = {
jellyfin: {
albumArtist: JFAlbumListSort.ALBUM_ARTIST,
artist: undefined,
communityRating: JFAlbumListSort.COMMUNITY_RATING,
criticRating: JFAlbumListSort.CRITIC_RATING,
duration: undefined,
favorited: undefined,
name: JFAlbumListSort.NAME,
playCount: JFAlbumListSort.PLAY_COUNT,
random: JFAlbumListSort.RANDOM,
rating: undefined,
recentlyAdded: JFAlbumListSort.RECENTLY_ADDED,
recentlyPlayed: undefined,
releaseDate: JFAlbumListSort.RELEASE_DATE,
songCount: undefined,
year: undefined,
},
navidrome: {
albumArtist: NDAlbumListSort.ALBUM_ARTIST,
artist: NDAlbumListSort.ARTIST,
communityRating: undefined,
criticRating: undefined,
duration: NDAlbumListSort.DURATION,
favorited: NDAlbumListSort.STARRED,
name: NDAlbumListSort.NAME,
playCount: NDAlbumListSort.PLAY_COUNT,
random: NDAlbumListSort.RANDOM,
rating: NDAlbumListSort.RATING,
recentlyAdded: NDAlbumListSort.RECENTLY_ADDED,
recentlyPlayed: NDAlbumListSort.PLAY_DATE,
// Recent versions of Navidrome support release date, but fallback to year for now
releaseDate: NDAlbumListSort.YEAR,
songCount: NDAlbumListSort.SONG_COUNT,
year: NDAlbumListSort.YEAR,
},
subsonic: {
albumArtist: undefined,
artist: undefined,
communityRating: undefined,
criticRating: undefined,
duration: undefined,
favorited: undefined,
name: undefined,
playCount: undefined,
random: undefined,
rating: undefined,
recentlyAdded: undefined,
recentlyPlayed: undefined,
releaseDate: undefined,
songCount: undefined,
year: undefined,
},
};
export type Album = {
albumArtist: string;
albumArtists: RelatedArtist[];
artists: RelatedArtist[];
backdropImageUrl: null | string;
comment: null | string;
createdAt: string;
duration: null | number;
genres: Genre[];
id: string;
imagePlaceholderUrl: null | string;
imageUrl: null | string;
isCompilation: boolean | null;
itemType: LibraryItem.ALBUM;
lastPlayedAt: null | string;
mbzId: null | string;
name: string;
originalDate: null | string;
participants: null | Record<string, RelatedArtist[]>;
playCount: null | number;
releaseDate: null | string;
releaseYear: null | number;
serverId: string;
serverType: ServerType;
size: null | number;
songCount: null | number;
songs?: Song[];
tags: null | Record<string, string[]>;
uniqueId: string;
updatedAt: string;
userFavorite: boolean;
userRating: null | number;
} & { songs?: Song[] };
export type AlbumDetailArgs = BaseEndpointArgs & { query: AlbumDetailQuery };
// Album Detail
export type AlbumDetailQuery = { id: string };
export type AlbumDetailResponse = Album | null | undefined;
export type AlbumInfo = {
imageUrl: null | string;
notes: null | string;
};
@@ -0,0 +1,245 @@
import i18n from 'src/i18n/i18n';
import { z } from 'zod';
import { JFAlbumArtistListSort, JFArtistListSort } from '/@/shared/api/jellyfin.types';
import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
import { NDAlbumArtistListSort } from '/@/shared/api/navidrome.types';
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
import {
BaseEndpointArgs,
BasePaginatedResponse,
BaseQuery,
LibraryItem,
} from '/@/shared/types/domain-types';
import { Genre } from '/@/shared/types/domain/genre-domain-types';
import { ServerType } from '/@/shared/types/domain/server-domain-types';
export enum ArtistListSortOptions {
ALBUM_COUNT = 'albumCount',
DURATION = 'duration',
IS_FAVORITE = 'isFavorite',
NAME = 'name',
RANDOM = 'random',
RATING = 'rating',
TRACK_COUNT = 'trackCount',
}
export const ArtistListSortOptionsLabels = {
[ArtistListSortOptions.ALBUM_COUNT]: i18n.t('filter.albumCount'),
[ArtistListSortOptions.DURATION]: i18n.t('filter.duration'),
[ArtistListSortOptions.IS_FAVORITE]: i18n.t('filter.isFavorite'),
[ArtistListSortOptions.NAME]: i18n.t('filter.name'),
[ArtistListSortOptions.RANDOM]: i18n.t('filter.random'),
[ArtistListSortOptions.RATING]: i18n.t('filter.rating'),
[ArtistListSortOptions.TRACK_COUNT]: i18n.t('filter.trackCount'),
};
export type AlbumArtist = {
albumCount: null | number;
backgroundImageUrl: null | string;
biography: null | string;
duration: null | number;
genres: Genre[];
id: string;
imageUrl: null | string;
itemType: LibraryItem.ALBUM_ARTIST;
lastPlayedAt: null | string;
mbz: null | string;
name: string;
playCount: null | number;
serverId: string;
serverType: ServerType;
similarArtists: null | RelatedArtist[];
songCount: null | number;
userFavorite: boolean;
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;
name: string;
};
type AlbumArtistListSortMap = {
jellyfin: Record<AlbumArtistListSort, JFAlbumArtistListSort | undefined>;
navidrome: Record<AlbumArtistListSort, NDAlbumArtistListSort | undefined>;
subsonic: Record<AlbumArtistListSort, undefined>;
};
export const albumArtistListSortMap: AlbumArtistListSortMap = {
jellyfin: {
album: JFAlbumArtistListSort.ALBUM,
albumCount: undefined,
duration: JFAlbumArtistListSort.DURATION,
favorited: undefined,
name: JFAlbumArtistListSort.NAME,
playCount: undefined,
random: JFAlbumArtistListSort.RANDOM,
rating: undefined,
recentlyAdded: JFAlbumArtistListSort.RECENTLY_ADDED,
releaseDate: undefined,
songCount: undefined,
},
navidrome: {
album: undefined,
albumCount: NDAlbumArtistListSort.ALBUM_COUNT,
duration: undefined,
favorited: NDAlbumArtistListSort.FAVORITED,
name: NDAlbumArtistListSort.NAME,
playCount: NDAlbumArtistListSort.PLAY_COUNT,
random: undefined,
rating: NDAlbumArtistListSort.RATING,
recentlyAdded: undefined,
releaseDate: undefined,
songCount: NDAlbumArtistListSort.SONG_COUNT,
},
subsonic: {
album: undefined,
albumCount: undefined,
duration: undefined,
favorited: undefined,
name: undefined,
playCount: undefined,
random: undefined,
rating: undefined,
recentlyAdded: undefined,
releaseDate: undefined,
songCount: undefined,
},
};
export enum ArtistListSort {
ALBUM = 'album',
ALBUM_COUNT = 'albumCount',
DURATION = 'duration',
FAVORITED = 'favorited',
NAME = 'name',
PLAY_COUNT = 'playCount',
RANDOM = 'random',
RATING = 'rating',
RECENTLY_ADDED = 'recentlyAdded',
RELEASE_DATE = 'releaseDate',
SONG_COUNT = 'songCount',
}
export type AlbumArtistDetailArgs = BaseEndpointArgs & { query: AlbumArtistDetailQuery };
export type AlbumArtistDetailQuery = { id: string };
export type AlbumArtistDetailResponse = AlbumArtist | null;
export type ArtistListArgs = BaseEndpointArgs & { query: ArtistListQuery };
export interface ArtistListQuery extends BaseQuery<ArtistListSort> {
_custom?: {
jellyfin?: Partial<z.infer<typeof jfType._parameters.albumArtistList>>;
navidrome?: Partial<z.infer<typeof ndType._parameters.albumArtistList>>;
};
limit?: number;
musicFolderId?: string;
role?: string;
searchTerm?: string;
startIndex: number;
}
export type ArtistListResponse = BasePaginatedResponse<AlbumArtist[]> | null | undefined;
type ArtistListSortMap = {
jellyfin: Record<ArtistListSort, JFArtistListSort | undefined>;
navidrome: Record<ArtistListSort, undefined>;
subsonic: Record<ArtistListSort, undefined>;
};
export const artistListSortMap: ArtistListSortMap = {
jellyfin: {
album: JFArtistListSort.ALBUM,
albumCount: undefined,
duration: JFArtistListSort.DURATION,
favorited: undefined,
name: JFArtistListSort.NAME,
playCount: undefined,
random: JFArtistListSort.RANDOM,
rating: undefined,
recentlyAdded: JFArtistListSort.RECENTLY_ADDED,
releaseDate: undefined,
songCount: undefined,
},
navidrome: {
album: undefined,
albumCount: undefined,
duration: undefined,
favorited: undefined,
name: undefined,
playCount: undefined,
random: undefined,
rating: undefined,
recentlyAdded: undefined,
releaseDate: undefined,
songCount: undefined,
},
subsonic: {
album: undefined,
albumCount: undefined,
duration: undefined,
favorited: undefined,
name: undefined,
playCount: undefined,
random: undefined,
rating: undefined,
recentlyAdded: undefined,
releaseDate: undefined,
songCount: undefined,
},
};
export enum AlbumArtistListSort {
ALBUM = 'album',
ALBUM_COUNT = 'albumCount',
DURATION = 'duration',
FAVORITED = 'favorited',
NAME = 'name',
PLAY_COUNT = 'playCount',
RANDOM = 'random',
RATING = 'rating',
RECENTLY_ADDED = 'recentlyAdded',
RELEASE_DATE = 'releaseDate',
SONG_COUNT = 'songCount',
}
export type AlbumArtistListArgs = BaseEndpointArgs & { query: AlbumArtistListQuery };
export interface AlbumArtistListQuery extends BaseQuery<AlbumArtistListSort> {
_custom?: {
jellyfin?: Partial<z.infer<typeof jfType._parameters.albumArtistList>>;
navidrome?: Partial<z.infer<typeof ndType._parameters.albumArtistList>>;
};
limit?: number;
musicFolderId?: string;
searchTerm?: string;
startIndex: number;
}
export type AlbumArtistListResponse = BasePaginatedResponse<AlbumArtist[]> | null | undefined;
export type ArtistInfoArgs = BaseEndpointArgs & { query: ArtistInfoQuery };
export type ArtistInfoQuery = {
artistId: string;
limit: number;
musicFolderId?: string;
};
@@ -0,0 +1,70 @@
import i18n from 'src/i18n/i18n';
import { UserListSort } from './user-domain-types';
import { JFGenreListSort } from '/@/shared/api/jellyfin.types';
import { NDGenreListSort } from '/@/shared/api/navidrome.types';
import {
BaseEndpointArgs,
BasePaginatedResponse,
BaseQuery,
LibraryItem,
} from '/@/shared/types/domain-types';
export enum GenreListSortOptions {
ALBUM_COUNT = 'albumCount',
NAME = 'name',
TRACK_COUNT = 'trackCount',
}
export const GenreListSortOptionsLabels = {
[GenreListSortOptions.ALBUM_COUNT]: i18n.t('filter.albumCount'),
[GenreListSortOptions.NAME]: i18n.t('filter.name'),
[GenreListSortOptions.TRACK_COUNT]: i18n.t('filter.trackCount'),
};
export type Genre = {
albumCount?: number;
id: string;
imageUrl: null | string;
itemType: LibraryItem.GENRE;
name: string;
songCount?: number;
};
export type GenreListArgs = BaseEndpointArgs & { query: GenreListQuery };
export interface GenreListQuery extends BaseQuery<GenreListSort> {
_custom?: {
jellyfin?: null;
navidrome?: null;
};
limit?: number;
musicFolderId?: string;
searchTerm?: string;
startIndex: number;
}
// Genre List
export type GenreListResponse = BasePaginatedResponse<Genre[]> | null | undefined;
export type GenresResponse = Genre[];
type GenreListSortMap = {
jellyfin: Record<GenreListSort, JFGenreListSort | undefined>;
navidrome: Record<GenreListSort, NDGenreListSort | undefined>;
subsonic: Record<UserListSort, undefined>;
};
export const genreListSortMap: GenreListSortMap = {
jellyfin: {
name: JFGenreListSort.NAME,
},
navidrome: {
name: NDGenreListSort.NAME,
},
subsonic: {
name: undefined,
},
};
export enum GenreListSort {
NAME = 'name',
}
@@ -0,0 +1,66 @@
import { BaseEndpointArgs, LyricSource } from '/@/shared/types/domain-types';
import { Song } from '/@/shared/types/domain/song-domain-types';
export type FullLyricsMetadata = Omit<InternetProviderLyricResponse, 'id' | 'lyrics' | 'source'> & {
lyrics: LyricsResponse;
remote: boolean;
source: string;
};
export type InternetProviderLyricResponse = {
artist: string;
id: string;
lyrics: string;
name: string;
source: LyricSource;
};
export type InternetProviderLyricSearchResponse = {
artist: string;
id: string;
name: string;
score?: number;
source: LyricSource;
};
export type LyricGetQuery = {
remoteSongId: string;
remoteSource: LyricSource;
song: Song;
};
export type LyricOverride = Omit<InternetProviderLyricResponse, 'lyrics'>;
export type LyricsArgs = BaseEndpointArgs & {
query: LyricsQuery;
};
export type LyricSearchQuery = {
album?: string;
artist?: string;
duration?: number;
name?: string;
};
export type LyricsOverride = Omit<FullLyricsMetadata, 'lyrics'> & { id: string };
export type LyricsQuery = {
songId: string;
};
export type LyricsResponse = string | SynchronizedLyricsArray;
export type StructuredLyric = (StructuredSyncedLyric | StructuredUnsyncedLyric) & {
lang: string;
};
export type StructuredLyricsArgs = BaseEndpointArgs & {
query: LyricsQuery;
};
export type StructuredSyncedLyric = Omit<FullLyricsMetadata, 'lyrics'> & {
lyrics: SynchronizedLyricsArray;
synced: true;
};
export type StructuredUnsyncedLyric = Omit<FullLyricsMetadata, 'lyrics'> & {
lyrics: string;
synced: false;
};
export type SynchronizedLyricsArray = Array<[number, string]>;
@@ -0,0 +1,12 @@
export interface AuthUserPermissions {
'jukebox.manage': boolean;
'media.download': boolean;
'media.share': boolean;
'media.stream': boolean;
'media.upload': boolean;
'playlist.create': boolean;
'playlist.delete': boolean;
'playlist.edit': boolean;
'server.admin': boolean;
'user.edit': boolean;
}
@@ -0,0 +1,212 @@
import i18n from 'src/i18n/i18n';
import { z } from 'zod';
import { Genre } from './genre-domain-types';
import { JFPlaylistListSort } from '/@/shared/api/jellyfin.types';
import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
import { NDPlaylistListSort } from '/@/shared/api/navidrome.types';
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
import {
BaseEndpointArgs,
BasePaginatedResponse,
BaseQuery,
LibraryItem,
} from '/@/shared/types/domain-types';
import { ServerType } from '/@/shared/types/domain/server-domain-types';
import { ListSortOrder } from '/@/shared/types/domain/shared-domain-types';
import { Song, SongListSort } from '/@/shared/types/domain/song-domain-types';
export enum PlaylistListSortOptions {
DURATION = 'duration',
NAME = 'name',
OWNER = 'owner',
PUBLIC = 'public',
TRACK_COUNT = 'trackCount',
UPDATED_AT = 'updatedAt',
}
export const PlaylistListSortOptionsLabels = {
[PlaylistListSortOptions.DURATION]: i18n.t('filter.duration'),
[PlaylistListSortOptions.NAME]: i18n.t('filter.name'),
[PlaylistListSortOptions.OWNER]: i18n.t('filter.owner'),
[PlaylistListSortOptions.PUBLIC]: i18n.t('filter.public'),
[PlaylistListSortOptions.TRACK_COUNT]: i18n.t('filter.trackCount'),
[PlaylistListSortOptions.UPDATED_AT]: i18n.t('filter.updatedAt'),
};
export enum PlaylistListSort {
DURATION = 'duration',
NAME = 'name',
OWNER = 'owner',
PUBLIC = 'public',
SONG_COUNT = 'songCount',
UPDATED_AT = 'updatedAt',
}
export type AddToPlaylistArgs = BaseEndpointArgs & {
body: AddToPlaylistBody;
query: AddToPlaylistQuery;
serverId?: string;
};
export type AddToPlaylistBody = {
songId: string[];
};
export type AddToPlaylistQuery = {
id: string;
};
export type AddToPlaylistResponse = null | undefined;
export type CreatePlaylistArgs = BaseEndpointArgs & { body: CreatePlaylistBody; serverId?: string };
export type CreatePlaylistBody = {
_custom?: {
navidrome?: {
owner?: string;
ownerId?: string;
rules?: Record<string, any>;
sync?: boolean;
};
};
comment?: string;
name: string;
public?: boolean;
};
export type CreatePlaylistResponse = undefined | { id: string };
export type DeletePlaylistArgs = BaseEndpointArgs & {
query: DeletePlaylistQuery;
serverId?: string;
};
export type DeletePlaylistQuery = { id: string };
export type DeletePlaylistResponse = null | undefined;
export type Playlist = {
description: null | string;
duration: null | number;
genres: Genre[];
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<string, any>;
serverId: string;
serverType: ServerType;
size: null | number;
songCount: null | number;
sync?: boolean | null;
};
export type PlaylistListArgs = BaseEndpointArgs & { query: PlaylistListQuery };
export interface PlaylistListQuery extends BaseQuery<PlaylistListSort> {
_custom?: {
jellyfin?: Partial<z.infer<typeof jfType._parameters.playlistList>>;
navidrome?: Partial<z.infer<typeof ndType._parameters.playlistList>>;
};
limit?: number;
searchTerm?: string;
startIndex: number;
}
export type PlaylistListResponse = BasePaginatedResponse<Playlist[]> | null | undefined;
export type RemoveFromPlaylistArgs = BaseEndpointArgs & {
query: RemoveFromPlaylistQuery;
serverId?: string;
};
export type RemoveFromPlaylistQuery = {
id: string;
songId: string[];
};
export type RemoveFromPlaylistResponse = null | undefined;
export type UpdatePlaylistArgs = BaseEndpointArgs & {
body: UpdatePlaylistBody;
query: UpdatePlaylistQuery;
serverId?: string;
};
export type UpdatePlaylistBody = {
_custom?: {
navidrome?: {
owner?: string;
ownerId?: string;
rules?: Record<string, any>;
sync?: boolean;
};
};
comment?: string;
genres?: Genre[];
name: string;
public?: boolean;
};
export type UpdatePlaylistQuery = {
id: string;
};
export type UpdatePlaylistResponse = null | undefined;
type PlaylistListSortMap = {
jellyfin: Record<PlaylistListSort, JFPlaylistListSort | undefined>;
navidrome: Record<PlaylistListSort, NDPlaylistListSort | undefined>;
subsonic: Record<PlaylistListSort, undefined>;
};
export const playlistListSortMap: PlaylistListSortMap = {
jellyfin: {
duration: JFPlaylistListSort.DURATION,
name: JFPlaylistListSort.NAME,
owner: undefined,
public: undefined,
songCount: JFPlaylistListSort.SONG_COUNT,
updatedAt: undefined,
},
navidrome: {
duration: NDPlaylistListSort.DURATION,
name: NDPlaylistListSort.NAME,
owner: NDPlaylistListSort.OWNER,
public: NDPlaylistListSort.PUBLIC,
songCount: NDPlaylistListSort.SONG_COUNT,
updatedAt: NDPlaylistListSort.UPDATED_AT,
},
subsonic: {
duration: undefined,
name: undefined,
owner: undefined,
public: undefined,
songCount: undefined,
updatedAt: undefined,
},
};
export type PlaylistDetailArgs = BaseEndpointArgs & { query: PlaylistDetailQuery };
export type PlaylistDetailQuery = {
id: string;
};
export type PlaylistDetailResponse = Playlist;
export type PlaylistSongListArgs = BaseEndpointArgs & { query: PlaylistSongListQuery };
export type PlaylistSongListQuery = {
id: string;
limit?: number;
sortBy?: SongListSort;
sortOrder?: ListSortOrder;
startIndex: number;
};
export type PlaylistSongListResponse = BasePaginatedResponse<Song[]> | null | undefined;
@@ -0,0 +1,46 @@
import { BaseEndpointArgs } from '/@/shared/types/domain-types';
import { Album } from '/@/shared/types/domain/album-domain-types';
import { AlbumArtist } from '/@/shared/types/domain/artist-domain-types';
import { Song } from '/@/shared/types/domain/song-domain-types';
export type SearchAlbumArtistsQuery = {
albumArtistLimit?: number;
albumArtistStartIndex?: number;
musicFolderId?: string;
query?: string;
};
export type SearchAlbumsQuery = {
albumLimit?: number;
albumStartIndex?: number;
musicFolderId?: string;
query?: string;
};
export type SearchArgs = BaseEndpointArgs & {
query: SearchQuery;
};
export type SearchQuery = {
albumArtistLimit?: number;
albumArtistStartIndex?: number;
albumLimit?: number;
albumStartIndex?: number;
musicFolderId?: string;
query?: string;
songLimit?: number;
songStartIndex?: number;
};
export type SearchResponse = {
albumArtists: AlbumArtist[];
albums: Album[];
songs: Song[];
};
export type SearchSongsQuery = {
musicFolderId?: string;
query?: string;
songLimit?: number;
songStartIndex?: number;
};
@@ -0,0 +1,60 @@
import i18n from 'src/i18n/i18n';
import { BaseEndpointArgs, BasePaginatedResponse } from '/@/shared/types/domain-types';
import { ServerFeatures } from '/@/shared/types/features-types';
export enum ServerListSortOptions {
CREATED_AT = 'createdAt',
NAME = 'name',
TYPE = 'type',
UPDATED_AT = 'updatedAt',
}
export const ServerListSortOptionsLabels = {
[ServerListSortOptions.CREATED_AT]: i18n.t('filter.createdAt'),
[ServerListSortOptions.NAME]: i18n.t('filter.name'),
[ServerListSortOptions.TYPE]: i18n.t('filter.type'),
[ServerListSortOptions.UPDATED_AT]: i18n.t('filter.updatedAt'),
};
export enum ServerType {
JELLYFIN = 'jellyfin',
NAVIDROME = 'navidrome',
SUBSONIC = 'subsonic',
}
export type ServerInfo = {
features: ServerFeatures;
id?: string;
version: string;
};
export type ServerInfoArgs = BaseEndpointArgs;
export type ServerListItem = {
credential: string;
features?: ServerFeatures;
id: string;
name: string;
ndCredential?: string;
savePassword?: boolean;
type: ServerType;
url: string;
userId: null | string;
username: string;
version?: string;
};
export type ServerMusicFolder = {
id: string;
name: string;
};
export type ServerMusicFolderListArgs = BaseEndpointArgs;
export type ServerMusicFolderListQuery = null;
export type ServerMusicFolderListResponse =
| BasePaginatedResponse<ServerMusicFolder[]>
| null
| undefined;
export type ServerMusicFoldersResponse = ServerMusicFolder[];
@@ -0,0 +1,4 @@
export enum ListSortOrder {
ASC = 'ASC',
DESC = 'DESC',
}
@@ -0,0 +1,251 @@
import i18n from 'src/i18n/i18n';
import { z } from 'zod';
import { JFSongListSort } from '/@/shared/api/jellyfin.types';
import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
import { NDSongListSort } from '/@/shared/api/navidrome.types';
import { ndType } from '/@/shared/api/navidrome/navidrome-types';
import {
BaseEndpointArgs,
BasePaginatedResponse,
BaseQuery,
GainInfo,
LibraryItem,
Played,
} from '/@/shared/types/domain-types';
import { RelatedArtist } from '/@/shared/types/domain/artist-domain-types';
import { Genre } from '/@/shared/types/domain/genre-domain-types';
import { ServerType } from '/@/shared/types/domain/server-domain-types';
export enum SongListSortOptions {
ALBUM = 'album',
ALBUM_ARTIST = 'albumArtist',
ARTIST = 'artist',
BPM = 'bpm',
CHANNELS = 'channels',
COMMENT = 'comment',
DURATION = 'duration',
GENRE = 'genre',
ID = 'id',
IS_FAVORITE = 'isFavorite',
NAME = 'name',
PLAY_COUNT = 'playCount',
RANDOM = 'random',
RATING = 'rating',
RECENTLY_ADDED = 'recentlyAdded',
RECENTLY_PLAYED = 'recentlyPlayed',
RELEASE_DATE = 'releaseDate',
YEAR = 'year',
}
export const SongListSortOptionsLabels = {
[SongListSortOptions.ALBUM]: i18n.t('filter.album'),
[SongListSortOptions.ALBUM_ARTIST]: i18n.t('filter.albumArtist'),
[SongListSortOptions.ARTIST]: i18n.t('filter.artist'),
[SongListSortOptions.BPM]: i18n.t('filter.bpm'),
[SongListSortOptions.CHANNELS]: i18n.t('filter.channels'),
[SongListSortOptions.COMMENT]: i18n.t('filter.comment'),
[SongListSortOptions.DURATION]: i18n.t('filter.duration'),
[SongListSortOptions.GENRE]: i18n.t('filter.genre'),
[SongListSortOptions.ID]: i18n.t('filter.id'),
[SongListSortOptions.IS_FAVORITE]: i18n.t('filter.isFavorite'),
[SongListSortOptions.NAME]: i18n.t('filter.name'),
[SongListSortOptions.PLAY_COUNT]: i18n.t('filter.playCount'),
[SongListSortOptions.RANDOM]: i18n.t('filter.random'),
[SongListSortOptions.RATING]: i18n.t('filter.rating'),
[SongListSortOptions.RECENTLY_ADDED]: i18n.t('filter.recentlyAdded'),
[SongListSortOptions.RECENTLY_PLAYED]: i18n.t('filter.recentlyPlayed'),
[SongListSortOptions.RELEASE_DATE]: i18n.t('filter.releaseDate'),
[SongListSortOptions.YEAR]: i18n.t('filter.year'),
};
export enum SongListSort {
ALBUM = 'album',
ALBUM_ARTIST = 'albumArtist',
ARTIST = 'artist',
BPM = 'bpm',
CHANNELS = 'channels',
COMMENT = 'comment',
DURATION = 'duration',
FAVORITED = 'favorited',
GENRE = 'genre',
ID = 'id',
NAME = 'name',
PLAY_COUNT = 'playCount',
RANDOM = 'random',
RATING = 'rating',
RECENTLY_ADDED = 'recentlyAdded',
RECENTLY_PLAYED = 'recentlyPlayed',
RELEASE_DATE = 'releaseDate',
YEAR = 'year',
}
export type Song = {
album: null | string;
albumArtists: RelatedArtist[];
albumId: string;
artistName: string;
artists: RelatedArtist[];
bitRate: number;
bpm: null | number;
channels: null | number;
comment: null | string;
compilation: boolean | null;
container: null | string;
createdAt: string;
discNumber: number;
discSubtitle: null | string;
duration: number;
gain: GainInfo | null;
genres: Genre[];
id: string;
imagePlaceholderUrl: null | string;
imageUrl: null | string;
itemType: LibraryItem.SONG;
lastPlayedAt: null | string;
lyrics: null | string;
name: string;
participants: null | Record<string, RelatedArtist[]>;
path: null | string;
peak: GainInfo | null;
playCount: number;
playlistItemId?: string;
releaseDate: null | string;
releaseYear: null | string;
serverId: string;
serverType: ServerType;
size: number;
streamUrl: string;
tags: null | Record<string, string[]>;
trackNumber: number;
uniqueId: string;
updatedAt: string;
userFavorite: boolean;
userRating: null | number;
};
export type SongListArgs = BaseEndpointArgs & { query: SongListQuery };
export interface SongListQuery extends BaseQuery<SongListSort> {
_custom?: {
jellyfin?: Partial<z.infer<typeof jfType._parameters.songList>>;
navidrome?: Partial<z.infer<typeof ndType._parameters.songList>>;
};
albumArtistIds?: string[];
albumIds?: string[];
artistIds?: string[];
favorite?: boolean;
genreIds?: string[];
imageSize?: number;
limit?: number;
maxYear?: number;
minYear?: number;
musicFolderId?: string;
role?: string;
searchTerm?: string;
startIndex: number;
}
export type SongListResponse = BasePaginatedResponse<Song[]> | null | undefined;
type SongListSortMap = {
jellyfin: Record<SongListSort, JFSongListSort | undefined>;
navidrome: Record<SongListSort, NDSongListSort | undefined>;
subsonic: Record<SongListSort, undefined>;
};
export const songListSortMap: SongListSortMap = {
jellyfin: {
album: JFSongListSort.ALBUM,
albumArtist: JFSongListSort.ALBUM_ARTIST,
artist: JFSongListSort.ARTIST,
bpm: undefined,
channels: undefined,
comment: undefined,
duration: JFSongListSort.DURATION,
favorited: undefined,
genre: undefined,
id: undefined,
name: JFSongListSort.NAME,
playCount: JFSongListSort.PLAY_COUNT,
random: JFSongListSort.RANDOM,
rating: undefined,
recentlyAdded: JFSongListSort.RECENTLY_ADDED,
recentlyPlayed: JFSongListSort.RECENTLY_PLAYED,
releaseDate: JFSongListSort.RELEASE_DATE,
year: undefined,
},
navidrome: {
album: NDSongListSort.ALBUM_SONGS,
albumArtist: NDSongListSort.ALBUM_ARTIST,
artist: NDSongListSort.ARTIST,
bpm: NDSongListSort.BPM,
channels: NDSongListSort.CHANNELS,
comment: NDSongListSort.COMMENT,
duration: NDSongListSort.DURATION,
favorited: NDSongListSort.FAVORITED,
genre: NDSongListSort.GENRE,
id: NDSongListSort.ID,
name: NDSongListSort.TITLE,
playCount: NDSongListSort.PLAY_COUNT,
random: NDSongListSort.RANDOM,
rating: NDSongListSort.RATING,
recentlyAdded: NDSongListSort.RECENTLY_ADDED,
recentlyPlayed: NDSongListSort.PLAY_DATE,
releaseDate: undefined,
year: NDSongListSort.YEAR,
},
subsonic: {
album: undefined,
albumArtist: undefined,
artist: undefined,
bpm: undefined,
channels: undefined,
comment: undefined,
duration: undefined,
favorited: undefined,
genre: undefined,
id: undefined,
name: undefined,
playCount: undefined,
random: undefined,
rating: undefined,
recentlyAdded: undefined,
recentlyPlayed: undefined,
releaseDate: undefined,
year: undefined,
},
};
export type RandomSongListArgs = BaseEndpointArgs & {
query: RandomSongListQuery;
};
export type RandomSongListQuery = {
genre?: string;
limit?: number;
maxYear?: number;
minYear?: number;
musicFolderId?: string;
played: Played;
};
export type RandomSongListResponse = SongListResponse;
export type SimilarSongsArgs = BaseEndpointArgs & {
query: SimilarSongsQuery;
};
export type SimilarSongsQuery = {
albumArtistIds: string[];
count?: number;
songId: string;
};
export type SongDetailArgs = BaseEndpointArgs & { query: SongDetailQuery };
export type SongDetailQuery = { id: string };
export type SongDetailResponse = null | Song | undefined;
export type TopSongListArgs = BaseEndpointArgs & { query: TopSongListQuery };
export type TopSongListQuery = {
artist: string;
artistId: string;
limit?: number;
};
export type TopSongListResponse = BasePaginatedResponse<Song[]> | null | undefined;
@@ -0,0 +1,95 @@
import i18n from 'src/i18n/i18n';
import {
AnyLibraryItems,
BaseEndpointArgs,
BasePaginatedResponse,
BaseQuery,
LibraryItem,
} from '/@/shared/types/domain-types';
import { RatingQuery } from '/@/shared/types/domain/user-domain-types';
import { NDUserListSort } from '/@/shared/api/navidrome.types';
export enum UserListSortOptions {
CREATED_AT = 'createdAt',
EMAIL = 'email',
NAME = 'name',
UPDATED_AT = 'updatedAt',
}
export const UserListSortOptionsLabels = {
[UserListSortOptions.CREATED_AT]: i18n.t('filter.createdAt'),
[UserListSortOptions.EMAIL]: i18n.t('filter.email'),
[UserListSortOptions.NAME]: i18n.t('filter.name'),
[UserListSortOptions.UPDATED_AT]: i18n.t('filter.updatedAt'),
};
export type FavoriteArgs = BaseEndpointArgs & { query: FavoriteQuery; serverId?: string };
export type FavoriteQuery = {
id: string[];
type: LibraryItem;
};
export type FavoriteResponse = null | undefined;
export type RatingQuery = {
item: AnyLibraryItems;
rating: number;
};
export type RatingResponse = null | undefined;
export type SetRatingArgs = BaseEndpointArgs & { query: RatingQuery; serverId?: string };
export type UserListArgs = BaseEndpointArgs & { query: UserListQuery };
export interface UserListQuery extends BaseQuery<UserListSort> {
_custom?: {
navidrome?: {
owner_id?: string;
};
};
limit?: number;
searchTerm?: string;
startIndex: number;
}
export type UserListResponse = BasePaginatedResponse<User[]> | null | undefined;
type UserListSortMap = {
jellyfin: Record<UserListSort, undefined>;
navidrome: Record<UserListSort, NDUserListSort | undefined>;
subsonic: Record<UserListSort, undefined>;
};
export const userListSortMap: UserListSortMap = {
jellyfin: {
name: undefined,
},
navidrome: {
name: NDUserListSort.NAME,
},
subsonic: {
name: undefined,
},
};
export type ScrobbleArgs = BaseEndpointArgs & {
query: ScrobbleQuery;
serverId?: string;
};
export type ScrobbleQuery = {
event?: 'pause' | 'start' | 'timeupdate' | 'unpause';
id: string;
position?: number;
submission: boolean;
};
export type ScrobbleResponse = null | undefined;
export enum UserListSort {
NAME = 'name',
}
export type User = {
createdAt: null | string;
email: null | string;
id: string;
isAdmin: boolean | null;
lastLoginAt: null | string;
name: string;
updatedAt: null | string;
};
+5 -29
View File
@@ -2,15 +2,11 @@ import { AppRoute } from '@ts-rest/core';
import { ReactNode } from 'react';
import { Song } from 'src/main/features/core/lyrics/netease';
import {
Album,
AlbumArtist,
Artist,
LibraryItem,
Playlist,
QueueSong,
} from '/@/shared/types/domain-types';
import { ServerFeatures } from '/@/shared/types/features-types';
import { LibraryItem, QueueSong } from '/@/shared/types/domain-types';
import { Album } from '/@/shared/types/domain/album-domain-types';
import { AlbumArtist, Artist } from '/@/shared/types/domain/artist-domain-types';
import { Playlist } from '/@/shared/types/domain/playlist-domain-types';
import { ServerType } from '/@/shared/types/domain/server-domain-types';
export enum ListDisplayType {
CARD = 'card',
@@ -27,12 +23,6 @@ export enum Platform {
WINDOWS = 'windows',
}
export enum ServerType {
JELLYFIN = 'jellyfin',
NAVIDROME = 'navidrome',
SUBSONIC = 'subsonic',
}
export type CardRoute = {
route: AppRoute | string;
slugs?: RouteSlug[];
@@ -210,20 +200,6 @@ export type QueryBuilderRule = {
value?: any | Date | null | number | string | undefined;
};
export type ServerListItem = {
credential: string;
features?: ServerFeatures;
id: string;
name: string;
ndCredential?: string;
savePassword?: boolean;
type: ServerType;
url: string;
userId: null | string;
username: string;
version?: string;
};
export type SongState = {
position?: number;
repeat?: PlayerRepeat;