Move server directory outside of frontend src

This commit is contained in:
jeffvli
2022-10-25 16:52:45 -07:00
parent 863dce88b7
commit 0438f2d5f2
105 changed files with 16946 additions and 6901 deletions
+86
View File
@@ -0,0 +1,86 @@
import { AuthUser } from '@/middleware';
import { SortOrder } from '@/types/types';
import { songHelpers } from '@helpers/songs.helpers';
export enum AlbumSort {
DATE_ADDED = 'added',
DATE_ADDED_REMOTE = 'addedRemote',
DATE_RELEASED = 'released',
DATE_RELEASED_YEAR = 'year',
FAVORITE = 'favorite',
NAME = 'name',
RANDOM = 'random',
RATING = 'rating',
}
const include = (options: { songs?: boolean; user?: AuthUser }) => {
// Prisma.AlbumInclude
const props = {
_count: {
select: {
favorites: true,
songs: true,
},
},
albumArtists: true,
artists: true,
favorites: { where: { userId: options.user?.id } },
genres: true,
images: true,
ratings: {
where: {
userId: options.user?.id,
},
},
server: true,
serverFolders: true,
songs: options?.songs && songHelpers.findMany(),
};
return props;
};
const sort = (sortBy: AlbumSort, orderBy: SortOrder) => {
let order;
switch (sortBy) {
case AlbumSort.NAME:
order = { name: orderBy };
break;
case AlbumSort.DATE_ADDED:
order = { createdAt: orderBy };
break;
case AlbumSort.DATE_ADDED_REMOTE:
order = { remoteCreatedAt: orderBy };
break;
case AlbumSort.DATE_RELEASED:
order = { releaseDate: orderBy, year: orderBy };
break;
case AlbumSort.DATE_RELEASED_YEAR:
order = { releaseYear: orderBy };
break;
case AlbumSort.RATING:
order = { rating: orderBy };
break;
case AlbumSort.FAVORITE:
order = { favorite: orderBy };
break;
default:
order = { title: orderBy };
break;
}
return order;
};
export const albumHelpers = {
include,
sort,
};
+520
View File
@@ -0,0 +1,520 @@
/* eslint-disable no-underscore-dangle */
import {
Album,
AlbumArtist,
AlbumArtistRating,
AlbumRating,
Artist,
ArtistRating,
External,
Genre,
Image,
ImageType,
Server,
ServerFolder,
ServerFolderPermission,
ServerPermission,
ServerType,
ServerUrl,
Song,
SongRating,
User,
UserServerUrl,
} from '@prisma/client';
const getSubsonicStreamUrl = (
remoteId: string,
url: string,
token: string,
deviceId: string
) => {
return (
`${url}/rest/stream.view` +
`?id=${remoteId}` +
`&${token}` +
`&v=1.13.0` +
`&c=Feishin_${deviceId}`
);
};
const getJellyfinStreamUrl = (
remoteId: string,
url: string,
token: string,
userId: string,
deviceId: string
) => {
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}`
);
};
const streamUrl = (
type: ServerType,
args: {
deviceId: string;
remoteId: string;
token: string;
url: string;
userId?: string;
}
) => {
if (type === ServerType.JELLYFIN) {
return getJellyfinStreamUrl(
args.remoteId,
args.url,
args.token,
args.userId || '',
args.deviceId
);
}
return getSubsonicStreamUrl(
args.remoteId,
args.url,
args.token,
args.deviceId
);
};
const imageUrl = (
type: ServerType,
baseUrl: string,
imageId: string,
token?: string
) => {
if (type === ServerType.JELLYFIN) {
return (
`${baseUrl}/Items` +
`/${imageId}` +
`/Images/Primary` +
'?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}`
);
}
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 relatedServerFolders = (items: ServerFolder[]) => {
const serverFolders = items?.map((item) => {
return {
/* eslint-disable sort-keys-fix/sort-keys-fix */
id: item.id,
name: item.name,
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 image = (
images: Image[],
type: ServerType,
imageType: ImageType,
url: string,
remoteId: string,
token?: string
) => {
const imageRemoteUrl = images.find((i) => i.type === imageType)?.remoteUrl;
if (!imageRemoteUrl) return null;
if (type === ServerType.JELLYFIN) {
return imageUrl(type, url, remoteId);
}
if (type === ServerType.SUBSONIC || type === ServerType.NAVIDROME) {
return imageUrl(type, url, imageRemoteUrl, token);
}
return null;
};
type DbSong = Song & DbSongInclude;
type DbSongInclude = {
album: Album;
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;
}
) => {
return (
items?.map((item) => {
const url = options.url ? options.url : item.server.serverUrls[0].url;
const stream = streamUrl(options.type, {
deviceId: options.deviceId,
remoteId: item.remoteId,
token: options.token,
url: options.url,
userId: options.userId,
});
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: image(
item.images,
options.type,
ImageType.PRIMARY,
url,
item.remoteId
),
releaseDate: item.releaseDate,
releaseYear: item.releaseYear,
remoteCreatedAt: item.remoteCreatedAt,
remoteId: item.remoteId,
// serverFolderId: item.serverFolderId,
serverId: item.serverId,
streamUrl: stream,
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 } = item.server;
const url = serverUrl || item.server.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: image(
item.images,
type,
ImageType.PRIMARY,
url,
item.remoteId
),
backdropImageUrl: image(
item.images,
type,
ImageType.BACKDROP,
url,
item.remoteId
),
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, {
deviceId: user.deviceId,
token,
type,
url,
userId: remoteUserId,
}),
/* eslint-enable sort-keys-fix/sort-keys-fix */
};
}) || []
);
};
// const relatedServerCredentials = (items: ServerCredential[]) => {
// return (
// items.map((item) => {
// return {
// /* eslint-disable sort-keys-fix/sort-keys-fix */
// id: item.id,
// enabled: item.enabled,
// username: item.username,
// credential: item.credential,
// /* eslint-enable sort-keys-fix/sort-keys-fix */
// };
// }) || []
// );
// };
// const serverCredentials = (items: ServerCredential[]) => {
// return (
// items.map((item) => {
// return {
// /* eslint-disable sort-keys-fix/sort-keys-fix */
// id: item.id,
// username: item.username,
// enabled: item.enabled,
// credential: item.credential,
// createdAt: item.createdAt,
// updatedAt: item.updatedAt,
// /* 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,
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 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 */
};
}) || []
);
};
export const toApiModel = {
albums,
servers,
songs,
users,
};
+9
View File
@@ -0,0 +1,9 @@
import { albumHelpers } from './albums.helpers';
import { sharedHelpers } from './shared.helpers';
import { songHelpers } from './songs.helpers';
export const helpers = {
albums: albumHelpers,
shared: sharedHelpers,
songs: songHelpers,
};
+108
View File
@@ -0,0 +1,108 @@
import { AuthUser } from '@/middleware';
import { ApiError } from '@/utils';
import { prisma } from '@lib/prisma';
const checkServerPermissions = (
user: AuthUser,
options: { serverId?: string }
) => {
const { serverId } = options;
if (user.isAdmin || !serverId) {
return;
}
if (serverId && !user.flatServerPermissions.includes(serverId)) {
throw ApiError.forbidden();
}
};
const checkServerFolderPermissions = (
user: AuthUser,
options: { serverFolderId?: string[] | string }
) => {
const { serverFolderId } = options;
if (user.isAdmin || !serverFolderId) {
return;
}
let ids: string[] = [];
if (typeof serverFolderId === 'string') {
ids = [serverFolderId];
} else if (typeof serverFolderId === 'object') {
ids = serverFolderId;
}
for (const id of ids) {
if (!user.flatServerFolderPermissions.includes(id)) {
throw ApiError.forbidden('');
}
}
};
const getAvailableServerFolderIds = async (
user: AuthUser,
options: { serverId: string }
) => {
const { serverId } = options;
if (user.isAdmin) {
const serverFoldersWithAccess = await prisma.serverFolder.findMany({
where: { serverId },
});
const serverFoldersWithAccessIds = serverFoldersWithAccess.map(
(serverFolder) => serverFolder.id
);
return serverFoldersWithAccessIds;
}
const serverFoldersWithAccess = await prisma.serverFolder.findMany({
where: {
OR: [
{
AND: [
{
serverFolderPermissions: {
some: { userId: { equals: user.id } },
},
},
],
},
],
},
});
const serverFoldersWithAccessIds = serverFoldersWithAccess.map(
(serverFolder) => serverFolder.id
);
return serverFoldersWithAccessIds;
};
const serverFolderFilter = (serverFolderIds: string[]) => {
return {
serverFolders: { every: { id: { in: serverFolderIds } } },
};
};
const paginationParams = (options: { skip: any; take: any }) => {
const { skip, take } = options;
return {
skip: Number(skip),
take: Number(take),
};
};
export const sharedHelpers = {
checkServerFolderPermissions,
checkServerPermissions,
getAvailableServerFolderIds,
params: {
pagination: paginationParams,
},
serverFolderFilter,
};
+45
View File
@@ -0,0 +1,45 @@
import { Prisma } from '@prisma/client';
const include = () => {
const props: Prisma.SongInclude = {
album: true,
artists: true,
externals: true,
genres: true,
images: true,
ratings: true,
server: {
include: { serverUrls: true },
},
};
return props;
};
const findMany = () => {
const props: Prisma.SongFindManyArgs = {
include: {
album: true,
artists: true,
externals: true,
genres: true,
images: true,
ratings: true,
server: {
include: { serverUrls: true },
},
},
orderBy: [
// { albumId: Prisma.SortOrder.asc },
{ discNumber: Prisma.SortOrder.asc },
{ trackNumber: Prisma.SortOrder.asc },
],
};
return props;
};
export const songHelpers = {
findMany,
include,
};