reorganize global types to shared directory

This commit is contained in:
jeffvli 2025-05-20 18:08:51 -07:00
parent 26c02e03c5
commit 9db2e51d2d
17 changed files with 160 additions and 144 deletions

View file

@ -1,400 +0,0 @@
import { nanoid } from 'nanoid';
import z from 'zod';
import { ndType } from './navidrome-types';
import { NDGenre } from '/@/renderer/api/navidrome.types';
import { ssType } from '/@/renderer/api/subsonic/subsonic-types';
import {
Album,
AlbumArtist,
Genre,
LibraryItem,
Playlist,
RelatedArtist,
ServerListItem,
ServerType,
Song,
User,
} from '/@/renderer/api/types';
const getImageUrl = (args: { url: null | string }) => {
const { url } = args;
if (url === '/app/artist-placeholder.webp') {
return null;
}
return url;
};
const getCoverArtUrl = (args: {
baseUrl: string | undefined;
coverArtId: string;
credential: string | undefined;
size: number;
}) => {
const size = args.size ? args.size : 250;
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}`
);
};
interface WithDate {
playDate?: string;
}
const normalizePlayDate = (item: WithDate): null | string => {
return !item.playDate || item.playDate.includes('0001-') ? null : item.playDate;
};
const getArtists = (
item:
| z.infer<typeof ndType._response.album>
| z.infer<typeof ndType._response.playlistSong>
| z.infer<typeof ndType._response.song>,
) => {
let albumArtists: RelatedArtist[] | undefined;
let artists: RelatedArtist[] | undefined;
let participants: null | Record<string, RelatedArtist[]> = null;
if (item.participants) {
participants = {};
for (const [role, list] of Object.entries(item.participants)) {
if (role === 'albumartist' || role === 'artist') {
const roleList = list.map((item) => ({
id: item.id,
imageUrl: null,
name: item.name,
}));
if (role === 'albumartist') {
albumArtists = roleList;
} else {
artists = roleList;
}
} else {
const subRoles = new Map<string | undefined, RelatedArtist[]>();
for (const artist of list) {
const item: RelatedArtist = {
id: artist.id,
imageUrl: null,
name: artist.name,
};
if (subRoles.has(artist.subRole)) {
subRoles.get(artist.subRole)!.push(item);
} else {
subRoles.set(artist.subRole, [item]);
}
}
for (const [subRole, items] of subRoles.entries()) {
if (subRole) {
participants[`${role} (${subRole})`] = items;
} else {
participants[role] = items;
}
}
}
}
}
if (albumArtists === undefined) {
albumArtists = [{ id: item.albumArtistId, imageUrl: null, name: item.albumArtist }];
}
if (artists === undefined) {
artists = [{ id: item.artistId, imageUrl: null, name: item.artist }];
}
return { albumArtists, artists, participants };
};
const normalizeSong = (
item: z.infer<typeof ndType._response.playlistSong> | z.infer<typeof ndType._response.song>,
server: null | ServerListItem,
imageSize?: number,
): Song => {
let id;
let playlistItemId;
// Dynamically determine the id field based on whether or not the item is a playlist song
if ('mediaFileId' in item) {
id = item.mediaFileId;
playlistItemId = item.id;
} else {
id = item.id;
}
const imageUrl = getCoverArtUrl({
baseUrl: server?.url,
coverArtId: id,
credential: server?.credential,
size: imageSize || 100,
});
const imagePlaceholderUrl = null;
return {
album: item.album,
albumId: item.albumId,
...getArtists(item),
artistName: item.artist,
bitRate: item.bitRate,
bpm: item.bpm ? item.bpm : null,
channels: item.channels ? item.channels : null,
comment: item.comment ? item.comment : null,
compilation: item.compilation,
container: item.suffix,
createdAt: item.createdAt.split('T')[0],
discNumber: item.discNumber,
discSubtitle: item.discSubtitle ? item.discSubtitle : null,
duration: item.duration * 1000,
gain:
item.rgAlbumGain || item.rgTrackGain
? { album: item.rgAlbumGain, track: item.rgTrackGain }
: null,
genres: (item.genres || []).map((genre) => ({
id: genre.id,
imageUrl: null,
itemType: LibraryItem.GENRE,
name: genre.name,
})),
id,
imagePlaceholderUrl,
imageUrl,
itemType: LibraryItem.SONG,
lastPlayedAt: normalizePlayDate(item),
lyrics: item.lyrics ? item.lyrics : null,
name: item.title,
path: item.path,
peak:
item.rgAlbumPeak || item.rgTrackPeak
? { album: item.rgAlbumPeak, track: item.rgTrackPeak }
: null,
playCount: item.playCount || 0,
playlistItemId,
releaseDate: (item.releaseDate
? new Date(item.releaseDate)
: new Date(Date.UTC(item.year, 0, 1))
).toISOString(),
releaseYear: String(item.year),
serverId: server?.id || 'unknown',
serverType: ServerType.NAVIDROME,
size: item.size,
streamUrl: `${server?.url}/rest/stream.view?id=${id}&v=1.13.0&c=Feishin&${server?.credential}`,
tags: item.tags || null,
trackNumber: item.trackNumber,
uniqueId: nanoid(),
updatedAt: item.updatedAt,
userFavorite: item.starred || false,
userRating: item.rating || null,
};
};
const normalizeAlbum = (
item: z.infer<typeof ndType._response.album> & {
songs?: z.infer<typeof ndType._response.songList>;
},
server: null | ServerListItem,
imageSize?: number,
): Album => {
const imageUrl = getCoverArtUrl({
baseUrl: server?.url,
coverArtId: item.coverArtId || item.id,
credential: server?.credential,
size: imageSize || 300,
});
const imagePlaceholderUrl = null;
const imageBackdropUrl = imageUrl?.replace(/size=\d+/, 'size=1000') || null;
return {
albumArtist: item.albumArtist,
...getArtists(item),
backdropImageUrl: imageBackdropUrl,
comment: item.comment || null,
createdAt: item.createdAt.split('T')[0],
duration: item.duration !== undefined ? item.duration * 1000 : null,
genres: (item.genres || []).map((genre) => ({
id: genre.id,
imageUrl: null,
itemType: LibraryItem.GENRE,
name: genre.name,
})),
id: item.id,
imagePlaceholderUrl,
imageUrl,
isCompilation: item.compilation,
itemType: LibraryItem.ALBUM,
lastPlayedAt: normalizePlayDate(item),
mbzId: item.mbzAlbumId || null,
name: item.name,
originalDate: item.originalDate
? new Date(item.originalDate).toISOString()
: item.originalYear
? new Date(Date.UTC(item.originalYear, 0, 1)).toISOString()
: null,
playCount: item.playCount || 0,
releaseDate: (item.releaseDate
? new Date(item.releaseDate)
: new Date(Date.UTC(item.minYear, 0, 1))
).toISOString(),
releaseYear: item.minYear,
serverId: server?.id || 'unknown',
serverType: ServerType.NAVIDROME,
size: item.size,
songCount: item.songCount,
songs: item.songs ? item.songs.map((song) => normalizeSong(song, server)) : undefined,
tags: item.tags || null,
uniqueId: nanoid(),
updatedAt: item.updatedAt,
userFavorite: item.starred,
userRating: item.rating || null,
};
};
const normalizeAlbumArtist = (
item: z.infer<typeof ndType._response.albumArtist> & {
similarArtists?: z.infer<typeof ssType._response.artistInfo>['artistInfo']['similarArtist'];
},
server: null | ServerListItem,
): AlbumArtist => {
let imageUrl = getImageUrl({ url: item?.largeImageUrl || null });
if (!imageUrl) {
imageUrl = getCoverArtUrl({
baseUrl: server?.url,
coverArtId: `ar-${item.id}`,
credential: server?.credential,
size: 300,
});
}
let albumCount: number;
let songCount: number;
if (item.stats) {
albumCount = Math.max(
item.stats.albumartist?.albumCount ?? 0,
item.stats.artist?.albumCount ?? 0,
);
songCount = Math.max(
item.stats.albumartist?.songCount ?? 0,
item.stats.artist?.songCount ?? 0,
);
} else {
albumCount = item.albumCount;
songCount = item.songCount;
}
return {
albumCount,
backgroundImageUrl: null,
biography: item.biography || null,
duration: null,
genres: (item.genres || []).map((genre) => ({
id: genre.id,
imageUrl: null,
itemType: LibraryItem.GENRE,
name: genre.name,
})),
id: item.id,
imageUrl: imageUrl || null,
itemType: LibraryItem.ALBUM_ARTIST,
lastPlayedAt: normalizePlayDate(item),
mbz: item.mbzArtistId || null,
name: item.name,
playCount: item.playCount || 0,
serverId: server?.id || 'unknown',
serverType: ServerType.NAVIDROME,
similarArtists:
item.similarArtists?.map((artist) => ({
id: artist.id,
imageUrl: artist?.artistImageUrl || null,
name: artist.name,
})) || null,
songCount,
userFavorite: item.starred,
userRating: item.rating,
};
};
const normalizePlaylist = (
item: z.infer<typeof ndType._response.playlist>,
server: null | ServerListItem,
imageSize?: number,
): Playlist => {
const imageUrl = getCoverArtUrl({
baseUrl: server?.url,
coverArtId: item.id,
credential: server?.credential,
size: imageSize || 300,
});
const imagePlaceholderUrl = null;
return {
description: item.comment,
duration: item.duration * 1000,
genres: [],
id: item.id,
imagePlaceholderUrl,
imageUrl,
itemType: LibraryItem.PLAYLIST,
name: item.name,
owner: item.ownerName,
ownerId: item.ownerId,
public: item.public,
rules: item?.rules || null,
serverId: server?.id || 'unknown',
serverType: ServerType.NAVIDROME,
size: item.size,
songCount: item.songCount,
sync: item.sync,
};
};
const normalizeGenre = (item: NDGenre): Genre => {
return {
albumCount: undefined,
id: item.id,
imageUrl: null,
itemType: LibraryItem.GENRE,
name: item.name,
songCount: undefined,
};
};
const normalizeUser = (item: z.infer<typeof ndType._response.user>): User => {
return {
createdAt: item.createdAt,
email: item.email || null,
id: item.id,
isAdmin: item.isAdmin,
lastLoginAt: item.lastLoginAt,
name: item.userName,
updatedAt: item.updatedAt,
};
};
export const ndNormalize = {
album: normalizeAlbum,
albumArtist: normalizeAlbumArtist,
genre: normalizeGenre,
playlist: normalizePlaylist,
song: normalizeSong,
user: normalizeUser,
};

View file

@ -1,402 +0,0 @@
import { z } from 'zod';
import {
NDAlbumArtistListSort,
NDAlbumListSort,
NDPlaylistListSort,
NDSongListSort,
} from '/@/renderer/api/navidrome.types';
const sortOrderValues = ['ASC', 'DESC'] as const;
const error = z.string();
const paginationParameters = z.object({
_end: z.number().optional(),
_order: z.enum(sortOrderValues),
_start: z.number().optional(),
});
const authenticate = z.object({
id: z.string(),
isAdmin: z.boolean(),
name: z.string(),
subsonicSalt: z.string(),
subsonicToken: z.string(),
token: z.string(),
username: z.string(),
});
const authenticateParameters = z.object({
password: z.string(),
username: z.string(),
});
const user = z.object({
createdAt: z.string(),
email: z.string().optional(),
id: z.string(),
isAdmin: z.boolean(),
lastAccessAt: z.string(),
lastLoginAt: z.string(),
name: z.string(),
updatedAt: z.string(),
userName: z.string(),
});
const userList = z.array(user);
const ndUserListSort = {
NAME: 'name',
} as const;
const userListParameters = paginationParameters.extend({
_sort: z.nativeEnum(ndUserListSort).optional(),
});
const genre = z.object({
id: z.string(),
name: z.string(),
});
const genreListSort = {
NAME: 'name',
SONG_COUNT: 'songCount',
} as const;
const genreListParameters = paginationParameters.extend({
_sort: z.nativeEnum(genreListSort).optional(),
name: z.string().optional(),
});
const genreList = z.array(genre);
const stats = z.object({
albumCount: z.number(),
size: z.number(),
songCount: z.number(),
});
const albumArtist = z.object({
albumCount: z.number(),
biography: z.string(),
externalInfoUpdatedAt: z.string(),
externalUrl: z.string(),
fullText: z.string(),
genres: z.array(genre).nullable(),
id: z.string(),
largeImageUrl: z.string().optional(),
mbzArtistId: z.string().optional(),
mediumImageUrl: z.string().optional(),
name: z.string(),
orderArtistName: z.string(),
playCount: z.number().optional(),
playDate: z.string().optional(),
rating: z.number(),
size: z.number(),
smallImageUrl: z.string().optional(),
songCount: z.number(),
starred: z.boolean(),
starredAt: z.string(),
stats: z.record(z.string(), stats).optional(),
});
const albumArtistList = z.array(albumArtist);
const albumArtistListParameters = paginationParameters.extend({
_sort: z.nativeEnum(NDAlbumArtistListSort).optional(),
genre_id: z.string().optional(),
missing: z.boolean().optional(),
name: z.string().optional(),
role: z.string().optional(),
starred: z.boolean().optional(),
});
const participant = z.object({
id: z.string(),
name: z.string(),
subRole: z.string().optional(),
});
const participants = z.record(z.string(), z.array(participant));
const album = z.object({
albumArtist: z.string(),
albumArtistId: z.string(),
allArtistIds: z.string(),
artist: z.string(),
artistId: z.string(),
comment: z.string().optional(),
compilation: z.boolean(),
coverArtId: z.string().optional(), // Removed after v0.48.0
coverArtPath: z.string().optional(), // Removed after v0.48.0
createdAt: z.string(),
duration: z.number().optional(),
fullText: z.string(),
genre: z.string(),
genres: z.array(genre).nullable(),
id: z.string(),
maxYear: z.number(),
mbzAlbumArtistId: z.string().optional(),
mbzAlbumId: z.string().optional(),
minYear: z.number(),
name: z.string(),
orderAlbumArtistName: z.string(),
orderAlbumName: z.string(),
originalDate: z.string().optional(),
originalYear: z.number().optional(),
participants: z.optional(participants),
playCount: z.number().optional(),
playDate: z.string().optional(),
rating: z.number().optional(),
releaseDate: z.string().optional(),
size: z.number(),
songCount: z.number(),
sortAlbumArtistName: z.string(),
sortArtistName: z.string(),
starred: z.boolean(),
starredAt: z.string().optional(),
tags: z.record(z.string(), z.array(z.string())).optional(),
updatedAt: z.string(),
});
const albumList = z.array(album);
const albumListParameters = paginationParameters.extend({
_sort: z.nativeEnum(NDAlbumListSort).optional(),
album_id: z.string().optional(),
artist_id: z.string().optional(),
compilation: z.boolean().optional(),
genre_id: z.string().optional(),
has_rating: z.boolean().optional(),
id: z.string().optional(),
name: z.string().optional(),
recently_added: z.boolean().optional(),
recently_played: z.boolean().optional(),
starred: z.boolean().optional(),
year: z.number().optional(),
});
const song = z.object({
album: z.string(),
albumArtist: z.string(),
albumArtistId: z.string(),
albumId: z.string(),
artist: z.string(),
artistId: z.string(),
bitRate: z.number(),
bookmarkPosition: z.number(),
bpm: z.number().optional(),
catalogNum: z.string().optional(),
channels: z.number().optional(),
comment: z.string().optional(),
compilation: z.boolean(),
createdAt: z.string(),
discNumber: z.number(),
discSubtitle: z.string().optional(),
duration: z.number(),
embedArtPath: z.string().optional(),
externalInfoUpdatedAt: z.string().optional(),
externalUrl: z.string().optional(),
fullText: z.string(),
genre: z.string(),
genres: z.array(genre).nullable(),
hasCoverArt: z.boolean(),
id: z.string(),
imageFiles: z.string().optional(),
largeImageUrl: z.string().optional(),
lyrics: z.string().optional(),
mbzAlbumArtistId: z.string().optional(),
mbzAlbumId: z.string().optional(),
mbzArtistId: z.string().optional(),
mbzTrackId: z.string().optional(),
mediumImageUrl: z.string().optional(),
orderAlbumArtistName: z.string(),
orderAlbumName: z.string(),
orderArtistName: z.string(),
orderTitle: z.string(),
participants: z.optional(participants),
path: z.string(),
playCount: z.number().optional(),
playDate: z.string().optional(),
rating: z.number().optional(),
releaseDate: z.string().optional(),
rgAlbumGain: z.number().optional(),
rgAlbumPeak: z.number().optional(),
rgTrackGain: z.number().optional(),
rgTrackPeak: z.number().optional(),
size: z.number(),
smallImageUrl: z.string().optional(),
sortAlbumArtistName: z.string(),
sortArtistName: z.string(),
starred: z.boolean(),
starredAt: z.string().optional(),
suffix: z.string(),
tags: z.record(z.string(), z.array(z.string())).optional(),
title: z.string(),
trackNumber: z.number(),
updatedAt: z.string(),
year: z.number(),
});
const songList = z.array(song);
const songListParameters = paginationParameters.extend({
_sort: z.nativeEnum(NDSongListSort).optional(),
album_artist_id: z.array(z.string()).optional(),
album_id: z.array(z.string()).optional(),
artist_id: z.array(z.string()).optional(),
genre_id: z.array(z.string()).optional(),
path: z.string().optional(),
starred: z.boolean().optional(),
title: z.string().optional(),
year: z.number().optional(),
});
const playlist = z.object({
comment: z.string(),
createdAt: z.string(),
duration: z.number(),
evaluatedAt: z.string(),
id: z.string(),
name: z.string(),
ownerId: z.string(),
ownerName: z.string(),
path: z.string(),
public: z.boolean(),
rules: z.record(z.string(), z.any()),
size: z.number(),
songCount: z.number(),
sync: z.boolean(),
updatedAt: z.string(),
});
const playlistList = z.array(playlist);
const playlistListParameters = paginationParameters.extend({
_sort: z.nativeEnum(NDPlaylistListSort).optional(),
owner_id: z.string().optional(),
q: z.string().optional(),
smart: z.boolean().optional(),
});
const playlistSong = song.extend({
mediaFileId: z.string(),
playlistId: z.string(),
});
const playlistSongList = z.array(playlistSong);
const createPlaylist = playlist.pick({
id: true,
});
const createPlaylistParameters = z.object({
comment: z.string().optional(),
name: z.string(),
public: z.boolean().optional(),
rules: z.record(z.any()).optional(),
sync: z.boolean().optional(),
});
const updatePlaylist = playlist;
const updatePlaylistParameters = createPlaylistParameters.partial();
const deletePlaylist = z.null();
const addToPlaylist = z.object({
added: z.number(),
});
const addToPlaylistParameters = z.object({
ids: z.array(z.string()),
});
const removeFromPlaylist = z.object({
ids: z.array(z.string()),
});
const removeFromPlaylistParameters = z.object({
id: z.array(z.string()),
});
const shareItem = z.object({
id: z.string(),
});
const shareItemParameters = z.object({
description: z.string(),
downloadable: z.boolean(),
expires: z.number(),
resourceIds: z.string(),
resourceType: z.string(),
});
const moveItemParameters = z.object({
insert_before: z.string(),
});
const moveItem = z.null();
const tag = z.object({
albumCount: z.number().optional(),
id: z.string(),
songCount: z.number().optional(),
tagName: z.string(),
tagValue: z.string(),
});
const tags = z.array(tag);
export const ndType = {
_enum: {
albumArtistList: NDAlbumArtistListSort,
albumList: NDAlbumListSort,
genreList: genreListSort,
playlistList: NDPlaylistListSort,
songList: NDSongListSort,
userList: ndUserListSort,
},
_parameters: {
addToPlaylist: addToPlaylistParameters,
albumArtistList: albumArtistListParameters,
albumList: albumListParameters,
authenticate: authenticateParameters,
createPlaylist: createPlaylistParameters,
genreList: genreListParameters,
moveItem: moveItemParameters,
playlistList: playlistListParameters,
removeFromPlaylist: removeFromPlaylistParameters,
shareItem: shareItemParameters,
songList: songListParameters,
updatePlaylist: updatePlaylistParameters,
userList: userListParameters,
},
_response: {
addToPlaylist,
album,
albumArtist,
albumArtistList,
albumList,
authenticate,
createPlaylist,
deletePlaylist,
error,
genre,
genreList,
moveItem,
playlist,
playlistList,
playlistSong,
playlistSongList,
removeFromPlaylist,
shareItem,
song,
songList,
tags,
updatePlaylist,
user,
userList,
},
};