Files
feishin/server/helpers/api-model.ts
T
2022-11-01 11:30:40 -07:00

658 lines
16 KiB
TypeScript

/* eslint-disable no-underscore-dangle */
import {
Album,
AlbumArtist,
AlbumArtistRating,
AlbumRating,
Artist,
ArtistRating,
External,
Genre,
Image,
ImageType,
Server,
ServerFolder,
ServerFolderPermission,
ServerPermission,
ServerType,
ServerUrl,
Song,
SongRating,
Task,
User,
UserServerUrl,
} from '@prisma/client';
const getSubsonicStreamUrl = (options: {
deviceId: string;
remoteId: string;
token?: string;
url: string;
}) => {
const { deviceId, remoteId, token, url } = options;
return (
`${url}/rest/stream.view` +
`?id=${remoteId}` +
`&v=1.13.0` +
`&c=Feishin_${deviceId}` +
`&${token ? `${token}` : ''}`
);
};
const getJellyfinStreamUrl = (options: {
deviceId: string;
remoteId: string;
token?: string;
url: string;
userId: string;
}) => {
const { deviceId, remoteId, token, url, userId } = options;
return (
`${url}/audio` +
`/${remoteId}/universal` +
`?userId=${userId}` +
`&audioCodec=aac` +
`&container=opus,mp3,aac,m4a,m4b,flac,wav,ogg` +
`&transcodingContainer=ts` +
`&transcodingProtocol=hls` +
`&deviceId=Feishin_${deviceId}` +
`&playSessionId=${deviceId}` +
`&api_key=${token ? `${token}` : ''}`
);
};
const buildStreamUrl = (
type: ServerType,
options: {
deviceId: string;
noCredential: boolean;
remoteId: string;
token: string;
url: string;
userId?: string;
}
) => {
if (type === ServerType.JELLYFIN) {
return getJellyfinStreamUrl({
deviceId: options.deviceId,
remoteId: options.remoteId,
token: options.noCredential ? undefined : options.token,
url: options.url,
userId: options.userId || '',
});
}
if (type === ServerType.SUBSONIC) {
return getSubsonicStreamUrl({
deviceId: options.deviceId,
remoteId: options.remoteId,
token: options.noCredential ? undefined : options.token,
url: options.url,
});
}
if (type === ServerType.NAVIDROME) {
const [_ndToken, ssToken] = options.token.split('||');
if (options.noCredential) {
return getSubsonicStreamUrl({
deviceId: options.deviceId,
remoteId: options.remoteId,
url: options.url,
});
}
return getSubsonicStreamUrl({
deviceId: options.deviceId,
remoteId: options.remoteId,
token: ssToken,
url: options.url,
});
}
return null;
};
const imageUrl = (
type: ServerType,
imageType: ImageType,
baseUrl: string,
imageId: string,
token?: string
) => {
if (type === ServerType.JELLYFIN) {
if (imageType === ImageType.PRIMARY) {
return (
`${baseUrl}/Items` +
`/${imageId}` +
`/Images/Primary` +
'?fillHeight=250' +
`&fillWidth=250` +
'&quality=90'
);
}
return (
`${baseUrl}/Items` +
`/${imageId}` +
`/Images/Backdrop` +
'?fillHeight=250' +
`&fillWidth=250` +
'&quality=90'
);
}
if (type === ServerType.SUBSONIC || type === ServerType.NAVIDROME) {
return (
`${baseUrl}/rest/getCoverArt.view` +
`?id=${imageId}` +
`&size=250` +
`&v=1.13.0` +
`&c=Feishin` +
`&${token ? `${token}` : ''}`
);
}
return null;
};
const relatedAlbum = (item: Album) => {
return {
/* eslint-disable sort-keys-fix/sort-keys-fix */
id: item.id,
name: item.name,
remoteId: item.remoteId,
deleted: item.deleted,
/* eslint-enable sort-keys-fix/sort-keys-fix */
};
};
const relatedArtists = (items: Artist[]) => {
return (
items?.map((item) => {
return {
/* eslint-disable sort-keys-fix/sort-keys-fix */
id: item.id,
name: item.name,
remoteId: item.remoteId,
deleted: item.deleted,
/* eslint-enable sort-keys-fix/sort-keys-fix */
};
}) || []
);
};
const relatedAlbumArtists = (items: AlbumArtist[]) => {
return (
items?.map((item) => {
return {
/* eslint-disable sort-keys-fix/sort-keys-fix */
id: item.id,
name: item.name,
remoteId: item.remoteId,
deleted: item.deleted,
/* eslint-enable sort-keys-fix/sort-keys-fix */
};
}) || []
);
};
const relatedGenres = (items: Genre[]) => {
return (
items?.map((item) => {
return {
/* eslint-disable sort-keys-fix/sort-keys-fix */
id: item.id,
name: item.name,
/* eslint-enable sort-keys-fix/sort-keys-fix */
};
}) || []
);
};
const genres = (items: (Genre & { _count?: any })[]) => {
return (
items?.map((item) => {
const totalCount = Object.keys(item._count)
.map((key) => item._count[key])
.reduce((a, b) => a + b, 0);
return {
/* eslint-disable sort-keys-fix/sort-keys-fix */
id: item.id,
name: item.name,
songCount: item._count?.songs,
albumCount: item._count?.albums,
artistCount: item._count?.artists,
albumArtistCount: item._count?.albumArtists,
totalCount,
/* eslint-enable sort-keys-fix/sort-keys-fix */
};
}) || []
);
};
const relatedServerFolders = (items: ServerFolder[]) => {
const serverFolders = items?.map((item) => {
return {
/* eslint-disable sort-keys-fix/sort-keys-fix */
id: item.id,
name: item.name,
enabled: item.enabled,
remoteId: item.remoteId,
lastScannedAt: item.lastScannedAt,
/* eslint-enable sort-keys-fix/sort-keys-fix */
};
});
return serverFolders || [];
};
const relatedServerUrls = (
items: (ServerUrl & {
userServerUrls?: UserServerUrl[];
})[]
) => {
const serverUrls = items?.map((item) => {
const userServerUrlIds = item.userServerUrls?.map(
(userServerUrl) => userServerUrl.serverUrlId
);
const enabled = userServerUrlIds?.some((id) => id === item.id);
return {
/* eslint-disable sort-keys-fix/sort-keys-fix */
id: item.id,
url: item.url,
enabled,
/* eslint-enable sort-keys-fix/sort-keys-fix */
};
});
return serverUrls || [];
};
const rating = (
items: AlbumRating[] | SongRating[] | ArtistRating[] | AlbumArtistRating[]
) => {
if (items.length > 0) {
return items[0].value;
}
return null;
};
const buildImageUrl = (options: {
imageType: ImageType;
images: Image[];
noCredential?: boolean;
remoteId: string;
token?: string;
type: ServerType;
url: string;
}) => {
const { imageType, images, remoteId, token, type, url, noCredential } =
options;
const image = images.find((i) => i.type === imageType);
if (!image) return null;
if (type === ServerType.JELLYFIN) {
return imageUrl(type, imageType, url, remoteId);
}
if (type === ServerType.SUBSONIC) {
if (noCredential) {
return imageUrl(type, imageType, url, image.remoteUrl);
}
return imageUrl(type, imageType, url, image.remoteUrl, token);
}
if (type === ServerType.NAVIDROME) {
const [_ndToken, ssToken] = token!.split('||');
if (noCredential) {
return imageUrl(type, imageType, url, image.remoteUrl);
}
return imageUrl(type, imageType, url, image.remoteUrl, ssToken);
}
return null;
};
type DbSong = Song & DbSongInclude;
type DbSongInclude = {
album: Album & { images: Image[] };
artists: Artist[];
externals: External[];
genres: Genre[];
images: Image[];
ratings: SongRating[];
server: Server & { serverUrls: ServerUrl[] };
};
const songs = (
items: DbSong[],
options: {
deviceId: string;
imageUrl?: string;
serverFolderId?: number;
token: string;
type: ServerType;
url: string;
userId: string;
},
noCredential: boolean
) => {
return (
items?.map((item) => {
const customUrl = item.server.serverUrls[0]?.url;
const baseUrl = customUrl ? customUrl : options.url;
const streamUrl = buildStreamUrl(options.type, {
deviceId: options.deviceId,
noCredential,
remoteId: item.remoteId,
token: options.token,
url: baseUrl,
userId: options.userId,
});
let imageUrl = buildImageUrl({
imageType: ImageType.PRIMARY,
images: item.images,
noCredential,
remoteId: item.remoteId,
token: options.token,
type: options.type,
url: baseUrl,
});
if (!imageUrl) {
imageUrl = buildImageUrl({
imageType: ImageType.PRIMARY,
images: item.album.images,
noCredential,
remoteId: item.remoteId,
token: options.token,
type: options.type,
url: baseUrl,
});
}
return {
/* eslint-disable sort-keys-fix/sort-keys-fix */
id: item.id,
name: item.name,
artistName: item.artistName,
album: item.album && relatedAlbum(item.album),
artists: relatedArtists(item.artists),
bitRate: item.bitRate,
container: item.container,
createdAt: item.createdAt,
deleted: item.deleted,
discNumber: item.discNumber,
duration: item.duration,
genres: relatedGenres(item.genres),
imageUrl,
releaseDate: item.releaseDate,
releaseYear: item.releaseYear,
remoteCreatedAt: item.remoteCreatedAt,
remoteId: item.remoteId,
// serverFolderId: item.serverFolderId,
serverId: item.serverId,
streamUrl,
trackNumber: item.trackNumber,
updatedAt: item.updatedAt,
/* eslint-enable sort-keys-fix/sort-keys-fix */
};
}) || []
);
};
type DbAlbum = Album & DbAlbumInclude;
type DbAlbumInclude = {
_count: {
favorites: number;
songs: number;
};
albumArtists: AlbumArtist[];
genres: Genre[];
images: Image[];
ratings: AlbumRating[];
server: Server;
serverFolders: ServerFolder[];
songs?: DbSong[];
};
const albums = (options: {
items: DbAlbum[] | any[];
serverUrl?: string;
user: User;
}) => {
const { items, serverUrl, user } = options;
return (
items?.map((item) => {
const { type, token, remoteUserId, noCredential } = item.server;
const url = serverUrl || item.server.url;
// Jellyfin does not require credentials for image url
const shouldBuildImage = type === ServerType.JELLYFIN || !noCredential;
const tokenForImage = shouldBuildImage ? token : undefined;
const imageUrl = buildImageUrl({
imageType: ImageType.PRIMARY,
images: item.images,
noCredential,
remoteId: item.remoteId,
token,
type,
url,
});
const backdropImageUrl = buildImageUrl({
imageType: ImageType.BACKDROP,
images: item.images,
noCredential,
remoteId: item.remoteId,
token,
type,
url,
});
return {
/* eslint-disable sort-keys-fix/sort-keys-fix */
id: item.id,
name: item.name,
sortName: item.sortName,
releaseDate: item.releaseDate,
releaseYear: item.releaseYear,
isFavorite: item.favorites.length === 1,
rating: rating(item.ratings),
songCount: item._count.songs,
type,
imageUrl,
backdropImageUrl: backdropImageUrl,
deleted: item.deleted,
remoteId: item.remoteId,
remoteCreatedAt: item.remoteCreatedAt,
createdAt: item.createdAt,
updatedAt: item.updatedAt,
genres: item.genres ? relatedGenres(item.genres) : [],
albumArtists: item.albumArtists
? relatedAlbumArtists(item.albumArtists)
: [],
artists: item.artists ? relatedArtists(item.artists) : [],
serverFolders: relatedServerFolders(item.serverFolders),
songs:
item.songs &&
songs(
item?.songs?.map((s: any) => ({
...s,
album: { images: item?.images, ...relatedAlbum(item) },
})),
{
deviceId: user.deviceId,
token,
type,
url,
userId: remoteUserId,
},
noCredential
),
/* eslint-enable sort-keys-fix/sort-keys-fix */
};
}) || []
);
};
const servers = (
items: (Server & {
serverFolders?: ServerFolder[];
serverUrls?: (ServerUrl & {
userServerUrls?: UserServerUrl[];
})[];
})[]
) => {
return (
items.map((item) => {
return {
/* eslint-disable sort-keys-fix/sort-keys-fix */
id: item.id,
name: item.name,
url: item.url,
type: item.type,
noCredential: item.noCredential,
username: item.username,
createdAt: item.createdAt,
updatedAt: item.updatedAt,
serverFolders:
item.serverFolders && relatedServerFolders(item.serverFolders),
serverUrls: item.serverUrls && relatedServerUrls(item.serverUrls),
/* eslint-enable sort-keys-fix/sort-keys-fix */
};
}) || []
);
};
const relatedServers = (items: Server[]) => {
const result = items.map((item) => ({
id: item.id,
name: item.name,
type: item.type,
url: item.url,
}));
return result || [];
};
const relatedServerFolderPermissions = (items: ServerFolderPermission[]) => {
return items.map((item) => {
return {
/* eslint-disable sort-keys-fix/sort-keys-fix */
id: item.id,
serverFolderId: item.serverFolderId,
createdAt: item.createdAt,
updatedAt: item.updatedAt,
/* eslint-enable sort-keys-fix/sort-keys-fix */
};
});
};
const relatedServerPermissions = (items: ServerPermission[]) => {
return items.map((item) => {
return {
/* eslint-disable sort-keys-fix/sort-keys-fix */
id: item.id,
type: item.type,
serverId: item.serverId,
createdAt: item.createdAt,
updatedAt: item.updatedAt,
/* eslint-enable sort-keys-fix/sort-keys-fix */
};
});
};
const users = (
items: (User & {
accessToken?: string;
refreshToken?: string;
serverFolderPermissions?: ServerFolderPermission[];
serverPermissions?: ServerPermission[];
})[]
) => {
return (
items.map((item) => {
return {
/* eslint-disable sort-keys-fix/sort-keys-fix */
id: item.id,
username: item.username,
accessToken: item.accessToken,
refreshToken: item.refreshToken,
enabled: item.enabled,
isAdmin: item.isAdmin,
deviceId: item.deviceId,
createdAt: item.createdAt,
updatedAt: item.updatedAt,
flatServerPermissions:
item.serverPermissions && item.serverPermissions.map((s) => s.id),
serverFolderPermissions:
item.serverFolderPermissions &&
relatedServerFolderPermissions(item.serverFolderPermissions),
serverPermissions:
item.serverPermissions &&
relatedServerPermissions(item.serverPermissions),
/* eslint-enable sort-keys-fix/sort-keys-fix */
};
}) || []
);
};
const relatedUsers = (items: User[]) => {
const result = items.map((item) => ({
enabled: item.enabled,
id: item.id,
isAdmin: item.isAdmin,
username: item.username,
}));
return result || [];
};
type DbTask = Task & DbTaskInclude;
type DbTaskInclude = {
server: Server;
user: User;
};
const tasks = (options: { items: DbTask[] | any[] }) => {
const { items } = options;
const result = items.map((item) => ({
createdAt: item.createdAt,
id: item.id,
isCompleted: item.completed,
isError: item.isError,
message: item.message,
server: item.server ? relatedServers([item.server])[0] : null,
type: item.type,
updatedAt: item.updatedAt,
user: item.user ? relatedUsers([item.user])[0] : null,
}));
return result;
};
export const toApiModel = {
albums,
servers,
songs,
tasks,
users,
};