mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-12 15:22:35 +02:00
Add server-side credential requirement
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import { ServerType } from '@prisma/client';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { ApiSuccess, getSuccessResponse } from '@/utils';
|
import { ApiSuccess, getSuccessResponse } from '@/utils';
|
||||||
import { toApiModel } from '@helpers/api-model';
|
import { toApiModel } from '@helpers/api-model';
|
||||||
@@ -18,7 +19,10 @@ const getServerList = async (
|
|||||||
req: TypedRequest<typeof validation.servers.list>,
|
req: TypedRequest<typeof validation.servers.list>,
|
||||||
res: Response
|
res: Response
|
||||||
) => {
|
) => {
|
||||||
const data = await service.servers.findMany(req.authUser);
|
const { enabled } = req.query;
|
||||||
|
const data = await service.servers.findMany(req.authUser, {
|
||||||
|
enabled: Boolean(enabled),
|
||||||
|
});
|
||||||
const success = ApiSuccess.ok({ data: toApiModel.servers(data) });
|
const success = ApiSuccess.ok({ data: toApiModel.servers(data) });
|
||||||
return res.status(success.statusCode).json(getSuccessResponse(success));
|
return res.status(success.statusCode).json(getSuccessResponse(success));
|
||||||
};
|
};
|
||||||
@@ -55,7 +59,8 @@ const updateServer = async (
|
|||||||
res: Response
|
res: Response
|
||||||
) => {
|
) => {
|
||||||
const { serverId } = req.params;
|
const { serverId } = req.params;
|
||||||
const { username, password, name, legacy, type, url } = req.body;
|
const { username, password, name, legacy, type, url, noCredential } =
|
||||||
|
req.body;
|
||||||
|
|
||||||
if (type && username && password && url) {
|
if (type && username && password && url) {
|
||||||
const remoteServerLoginRes = await service.servers.remoteServerLogin({
|
const remoteServerLoginRes = await service.servers.remoteServerLogin({
|
||||||
@@ -68,14 +73,27 @@ const updateServer = async (
|
|||||||
|
|
||||||
const data = await service.servers.update(
|
const data = await service.servers.update(
|
||||||
{ id: serverId },
|
{ id: serverId },
|
||||||
{ name, ...remoteServerLoginRes }
|
{
|
||||||
|
name,
|
||||||
|
remoteUserId: remoteServerLoginRes.remoteUserId,
|
||||||
|
token:
|
||||||
|
type === ServerType.NAVIDROME
|
||||||
|
? `${remoteServerLoginRes.token}||${remoteServerLoginRes?.altToken}`
|
||||||
|
: remoteServerLoginRes.token,
|
||||||
|
type,
|
||||||
|
url: remoteServerLoginRes.url,
|
||||||
|
username: remoteServerLoginRes.username,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const success = ApiSuccess.ok({ data: toApiModel.servers([data])[0] });
|
const success = ApiSuccess.ok({ data: toApiModel.servers([data])[0] });
|
||||||
return res.status(success.statusCode).json(getSuccessResponse(success));
|
return res.status(success.statusCode).json(getSuccessResponse(success));
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await service.servers.update({ id: serverId }, { name, url });
|
const data = await service.servers.update(
|
||||||
|
{ id: serverId },
|
||||||
|
{ name, noCredential, url }
|
||||||
|
);
|
||||||
const success = ApiSuccess.ok({ data: toApiModel.servers([data])[0] });
|
const success = ApiSuccess.ok({ data: toApiModel.servers([data])[0] });
|
||||||
return res.status(success.statusCode).json(getSuccessResponse(success));
|
return res.status(success.statusCode).json(getSuccessResponse(success));
|
||||||
};
|
};
|
||||||
@@ -98,6 +116,8 @@ const scanServer = async (
|
|||||||
const { serverId } = req.params;
|
const { serverId } = req.params;
|
||||||
const { serverFolderId } = req.body;
|
const { serverFolderId } = req.body;
|
||||||
|
|
||||||
|
// TODO: Check that server is accessible first with the saved token, otherwise throw error
|
||||||
|
|
||||||
const data = await service.servers.fullScan({
|
const data = await service.servers.fullScan({
|
||||||
id: serverId,
|
id: serverId,
|
||||||
serverFolderId,
|
serverFolderId,
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const include = (user: AuthUser, options: { songs?: boolean }) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
server: true,
|
server: true,
|
||||||
serverFolders: true,
|
serverFolders: { where: { enabled: true } },
|
||||||
songs: options?.songs && songHelpers.findMany(user),
|
songs: options?.songs && songHelpers.findMany(user),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+176
-80
@@ -22,28 +22,30 @@ import {
|
|||||||
UserServerUrl,
|
UserServerUrl,
|
||||||
} from '@prisma/client';
|
} from '@prisma/client';
|
||||||
|
|
||||||
const getSubsonicStreamUrl = (
|
const getSubsonicStreamUrl = (options: {
|
||||||
remoteId: string,
|
deviceId: string;
|
||||||
url: string,
|
remoteId: string;
|
||||||
token: string,
|
token?: string;
|
||||||
deviceId: string
|
url: string;
|
||||||
) => {
|
}) => {
|
||||||
|
const { deviceId, remoteId, token, url } = options;
|
||||||
return (
|
return (
|
||||||
`${url}/rest/stream.view` +
|
`${url}/rest/stream.view` +
|
||||||
`?id=${remoteId}` +
|
`?id=${remoteId}` +
|
||||||
`&${token}` +
|
|
||||||
`&v=1.13.0` +
|
`&v=1.13.0` +
|
||||||
`&c=Feishin_${deviceId}`
|
`&c=Feishin_${deviceId}` +
|
||||||
|
`&${token ? `${token}` : ''}`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getJellyfinStreamUrl = (
|
const getJellyfinStreamUrl = (options: {
|
||||||
remoteId: string,
|
deviceId: string;
|
||||||
url: string,
|
remoteId: string;
|
||||||
token: string,
|
token?: string;
|
||||||
userId: string,
|
url: string;
|
||||||
deviceId: string
|
userId: string;
|
||||||
) => {
|
}) => {
|
||||||
|
const { deviceId, remoteId, token, url, userId } = options;
|
||||||
return (
|
return (
|
||||||
`${url}/audio` +
|
`${url}/audio` +
|
||||||
`/${remoteId}/universal` +
|
`/${remoteId}/universal` +
|
||||||
@@ -54,14 +56,15 @@ const getJellyfinStreamUrl = (
|
|||||||
`&transcodingProtocol=hls` +
|
`&transcodingProtocol=hls` +
|
||||||
`&deviceId=Feishin_${deviceId}` +
|
`&deviceId=Feishin_${deviceId}` +
|
||||||
`&playSessionId=${deviceId}` +
|
`&playSessionId=${deviceId}` +
|
||||||
`&api_key=${token}`
|
`&api_key=${token ? `${token}` : ''}`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const streamUrl = (
|
const buildStreamUrl = (
|
||||||
type: ServerType,
|
type: ServerType,
|
||||||
args: {
|
options: {
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
|
noCredential: boolean;
|
||||||
remoteId: string;
|
remoteId: string;
|
||||||
token: string;
|
token: string;
|
||||||
url: string;
|
url: string;
|
||||||
@@ -69,33 +72,69 @@ const streamUrl = (
|
|||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
if (type === ServerType.JELLYFIN) {
|
if (type === ServerType.JELLYFIN) {
|
||||||
return getJellyfinStreamUrl(
|
return getJellyfinStreamUrl({
|
||||||
args.remoteId,
|
deviceId: options.deviceId,
|
||||||
args.url,
|
remoteId: options.remoteId,
|
||||||
args.token,
|
token: options.noCredential ? undefined : options.token,
|
||||||
args.userId || '',
|
url: options.url,
|
||||||
args.deviceId
|
userId: options.userId || '',
|
||||||
);
|
});
|
||||||
}
|
}
|
||||||
return getSubsonicStreamUrl(
|
|
||||||
args.remoteId,
|
if (type === ServerType.SUBSONIC) {
|
||||||
args.url,
|
return getSubsonicStreamUrl({
|
||||||
args.token,
|
deviceId: options.deviceId,
|
||||||
args.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 = (
|
const imageUrl = (
|
||||||
type: ServerType,
|
type: ServerType,
|
||||||
|
imageType: ImageType,
|
||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
imageId: string,
|
imageId: string,
|
||||||
token?: string
|
token?: string
|
||||||
) => {
|
) => {
|
||||||
if (type === ServerType.JELLYFIN) {
|
if (type === ServerType.JELLYFIN) {
|
||||||
|
if (imageType === ImageType.PRIMARY) {
|
||||||
|
return (
|
||||||
|
`${baseUrl}/Items` +
|
||||||
|
`/${imageId}` +
|
||||||
|
`/Images/Primary` +
|
||||||
|
'?fillHeight=250' +
|
||||||
|
`&fillWidth=250` +
|
||||||
|
'&quality=90'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
`${baseUrl}/Items` +
|
`${baseUrl}/Items` +
|
||||||
`/${imageId}` +
|
`/${imageId}` +
|
||||||
`/Images/Primary` +
|
`/Images/Backdrop` +
|
||||||
'?fillHeight=250' +
|
'?fillHeight=250' +
|
||||||
`&fillWidth=250` +
|
`&fillWidth=250` +
|
||||||
'&quality=90'
|
'&quality=90'
|
||||||
@@ -109,7 +148,7 @@ const imageUrl = (
|
|||||||
`&size=250` +
|
`&size=250` +
|
||||||
`&v=1.13.0` +
|
`&v=1.13.0` +
|
||||||
`&c=Feishin` +
|
`&c=Feishin` +
|
||||||
`&${token}`
|
`&${token ? `${token}` : ''}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,23 +258,42 @@ const rating = (
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const image = (
|
const buildImageUrl = (options: {
|
||||||
images: Image[],
|
imageType: ImageType;
|
||||||
type: ServerType,
|
images: Image[];
|
||||||
imageType: ImageType,
|
noCredential?: boolean;
|
||||||
url: string,
|
remoteId: string;
|
||||||
remoteId: string,
|
token?: string;
|
||||||
token?: string
|
type: ServerType;
|
||||||
) => {
|
url: string;
|
||||||
const imageRemoteUrl = images.find((i) => i.type === imageType)?.remoteUrl;
|
}) => {
|
||||||
|
const { imageType, images, remoteId, token, type, url, noCredential } =
|
||||||
|
options;
|
||||||
|
|
||||||
|
const image = images.find((i) => i.type === imageType);
|
||||||
|
|
||||||
|
if (!image) return null;
|
||||||
|
|
||||||
if (!imageRemoteUrl) return null;
|
|
||||||
if (type === ServerType.JELLYFIN) {
|
if (type === ServerType.JELLYFIN) {
|
||||||
return imageUrl(type, url, remoteId);
|
return imageUrl(type, imageType, url, remoteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === ServerType.SUBSONIC || type === ServerType.NAVIDROME) {
|
if (type === ServerType.SUBSONIC) {
|
||||||
return imageUrl(type, url, imageRemoteUrl, token);
|
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;
|
return null;
|
||||||
@@ -244,7 +302,7 @@ const image = (
|
|||||||
type DbSong = Song & DbSongInclude;
|
type DbSong = Song & DbSongInclude;
|
||||||
|
|
||||||
type DbSongInclude = {
|
type DbSongInclude = {
|
||||||
album: Album;
|
album: Album & { images: Image[] };
|
||||||
artists: Artist[];
|
artists: Artist[];
|
||||||
externals: External[];
|
externals: External[];
|
||||||
genres: Genre[];
|
genres: Genre[];
|
||||||
@@ -263,21 +321,45 @@ const songs = (
|
|||||||
type: ServerType;
|
type: ServerType;
|
||||||
url: string;
|
url: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
}
|
},
|
||||||
|
noCredential: boolean
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
items?.map((item) => {
|
items?.map((item) => {
|
||||||
const customUrl = item.server.serverUrls[0].url;
|
const customUrl = item.server.serverUrls[0]?.url;
|
||||||
const baseUrl = customUrl ? customUrl : options.url;
|
const baseUrl = customUrl ? customUrl : options.url;
|
||||||
|
|
||||||
const stream = streamUrl(options.type, {
|
const streamUrl = buildStreamUrl(options.type, {
|
||||||
deviceId: options.deviceId,
|
deviceId: options.deviceId,
|
||||||
|
noCredential,
|
||||||
remoteId: item.remoteId,
|
remoteId: item.remoteId,
|
||||||
token: options.token,
|
token: options.token,
|
||||||
url: baseUrl,
|
url: baseUrl,
|
||||||
userId: options.userId,
|
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 {
|
return {
|
||||||
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
||||||
id: item.id,
|
id: item.id,
|
||||||
@@ -292,20 +374,14 @@ const songs = (
|
|||||||
discNumber: item.discNumber,
|
discNumber: item.discNumber,
|
||||||
duration: item.duration,
|
duration: item.duration,
|
||||||
genres: relatedGenres(item.genres),
|
genres: relatedGenres(item.genres),
|
||||||
imageUrl: image(
|
imageUrl,
|
||||||
item.images,
|
|
||||||
options.type,
|
|
||||||
ImageType.PRIMARY,
|
|
||||||
baseUrl,
|
|
||||||
item.remoteId
|
|
||||||
),
|
|
||||||
releaseDate: item.releaseDate,
|
releaseDate: item.releaseDate,
|
||||||
releaseYear: item.releaseYear,
|
releaseYear: item.releaseYear,
|
||||||
remoteCreatedAt: item.remoteCreatedAt,
|
remoteCreatedAt: item.remoteCreatedAt,
|
||||||
remoteId: item.remoteId,
|
remoteId: item.remoteId,
|
||||||
// serverFolderId: item.serverFolderId,
|
// serverFolderId: item.serverFolderId,
|
||||||
serverId: item.serverId,
|
serverId: item.serverId,
|
||||||
streamUrl: stream,
|
streamUrl,
|
||||||
trackNumber: item.trackNumber,
|
trackNumber: item.trackNumber,
|
||||||
updatedAt: item.updatedAt,
|
updatedAt: item.updatedAt,
|
||||||
/* eslint-enable sort-keys-fix/sort-keys-fix */
|
/* eslint-enable sort-keys-fix/sort-keys-fix */
|
||||||
@@ -338,9 +414,33 @@ const albums = (options: {
|
|||||||
const { items, serverUrl, user } = options;
|
const { items, serverUrl, user } = options;
|
||||||
return (
|
return (
|
||||||
items?.map((item) => {
|
items?.map((item) => {
|
||||||
const { type, token, remoteUserId } = item.server;
|
const { type, token, remoteUserId, noCredential } = item.server;
|
||||||
const url = serverUrl || item.server.url;
|
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 {
|
return {
|
||||||
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
||||||
id: item.id,
|
id: item.id,
|
||||||
@@ -352,20 +452,8 @@ const albums = (options: {
|
|||||||
rating: rating(item.ratings),
|
rating: rating(item.ratings),
|
||||||
songCount: item._count.songs,
|
songCount: item._count.songs,
|
||||||
type,
|
type,
|
||||||
imageUrl: image(
|
imageUrl,
|
||||||
item.images,
|
backdropImageUrl: backdropImageUrl,
|
||||||
type,
|
|
||||||
ImageType.PRIMARY,
|
|
||||||
url,
|
|
||||||
item.remoteId
|
|
||||||
),
|
|
||||||
backdropImageUrl: image(
|
|
||||||
item.images,
|
|
||||||
type,
|
|
||||||
ImageType.BACKDROP,
|
|
||||||
url,
|
|
||||||
item.remoteId
|
|
||||||
),
|
|
||||||
deleted: item.deleted,
|
deleted: item.deleted,
|
||||||
remoteId: item.remoteId,
|
remoteId: item.remoteId,
|
||||||
remoteCreatedAt: item.remoteCreatedAt,
|
remoteCreatedAt: item.remoteCreatedAt,
|
||||||
@@ -379,13 +467,20 @@ const albums = (options: {
|
|||||||
serverFolders: relatedServerFolders(item.serverFolders),
|
serverFolders: relatedServerFolders(item.serverFolders),
|
||||||
songs:
|
songs:
|
||||||
item.songs &&
|
item.songs &&
|
||||||
songs(item.songs, {
|
songs(
|
||||||
deviceId: user.deviceId,
|
item?.songs?.map((s: any) => ({
|
||||||
token,
|
...s,
|
||||||
type,
|
album: { images: item?.images },
|
||||||
url,
|
})),
|
||||||
userId: remoteUserId,
|
{
|
||||||
}),
|
deviceId: user.deviceId,
|
||||||
|
token,
|
||||||
|
type,
|
||||||
|
url,
|
||||||
|
userId: remoteUserId,
|
||||||
|
},
|
||||||
|
noCredential
|
||||||
|
),
|
||||||
/* eslint-enable sort-keys-fix/sort-keys-fix */
|
/* eslint-enable sort-keys-fix/sort-keys-fix */
|
||||||
};
|
};
|
||||||
}) || []
|
}) || []
|
||||||
@@ -440,6 +535,7 @@ const servers = (
|
|||||||
name: item.name,
|
name: item.name,
|
||||||
url: item.url,
|
url: item.url,
|
||||||
type: item.type,
|
type: item.type,
|
||||||
|
noCredential: item.noCredential,
|
||||||
username: item.username,
|
username: item.username,
|
||||||
createdAt: item.createdAt,
|
createdAt: item.createdAt,
|
||||||
updatedAt: item.updatedAt,
|
updatedAt: item.updatedAt,
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ const getAvailableServerFolderIds = async (
|
|||||||
|
|
||||||
if (user.isAdmin) {
|
if (user.isAdmin) {
|
||||||
const serverFoldersWithAccess = await prisma.serverFolder.findMany({
|
const serverFoldersWithAccess = await prisma.serverFolder.findMany({
|
||||||
where: { serverId },
|
where: { enabled: true, serverId },
|
||||||
});
|
});
|
||||||
|
|
||||||
const serverFoldersWithAccessIds = serverFoldersWithAccess.map(
|
const serverFoldersWithAccessIds = serverFoldersWithAccess.map(
|
||||||
@@ -65,6 +65,7 @@ const getAvailableServerFolderIds = async (
|
|||||||
{
|
{
|
||||||
AND: [
|
AND: [
|
||||||
{
|
{
|
||||||
|
enabled: true,
|
||||||
serverFolderPermissions: {
|
serverFolderPermissions: {
|
||||||
some: { userId: { equals: user.id } },
|
some: { userId: { equals: user.id } },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Server" ADD COLUMN "noCredential" BOOLEAN NOT NULL DEFAULT true;
|
||||||
@@ -99,6 +99,7 @@ model Server {
|
|||||||
remoteUserId String
|
remoteUserId String
|
||||||
username String
|
username String
|
||||||
token String
|
token String
|
||||||
|
noCredential Boolean @default(true)
|
||||||
type ServerType
|
type ServerType
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|||||||
@@ -104,12 +104,13 @@ const findById = async (user: AuthUser, options: { id: string }) => {
|
|||||||
return server;
|
return server;
|
||||||
};
|
};
|
||||||
|
|
||||||
const findMany = async (user: AuthUser) => {
|
const findMany = async (user: AuthUser, options?: { enabled?: boolean }) => {
|
||||||
if (user.isAdmin) {
|
if (user.isAdmin) {
|
||||||
return prisma.server.findMany({
|
return prisma.server.findMany({
|
||||||
include: {
|
include: {
|
||||||
serverFolders: {
|
serverFolders: {
|
||||||
orderBy: { name: SortOrder.ASC },
|
orderBy: { name: SortOrder.ASC },
|
||||||
|
where: { enabled: options?.enabled ? true : undefined },
|
||||||
},
|
},
|
||||||
serverPermissions: {
|
serverPermissions: {
|
||||||
orderBy: { createdAt: SortOrder.ASC },
|
orderBy: { createdAt: SortOrder.ASC },
|
||||||
@@ -131,7 +132,12 @@ const findMany = async (user: AuthUser) => {
|
|||||||
include: {
|
include: {
|
||||||
serverFolders: {
|
serverFolders: {
|
||||||
orderBy: { name: SortOrder.ASC },
|
orderBy: { name: SortOrder.ASC },
|
||||||
where: { id: { in: user.flatServerFolderPermissions } },
|
where: {
|
||||||
|
AND: [
|
||||||
|
{ id: { in: user.flatServerFolderPermissions } },
|
||||||
|
{ enabled: options?.enabled ? true : undefined },
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
serverPermissions: {
|
serverPermissions: {
|
||||||
orderBy: { createdAt: SortOrder.ASC },
|
orderBy: { createdAt: SortOrder.ASC },
|
||||||
@@ -178,6 +184,7 @@ const create = async (options: {
|
|||||||
if (!serverFoldersRes) {
|
if (!serverFoldersRes) {
|
||||||
throw ApiError.badRequest('Server is inaccessible.');
|
throw ApiError.badRequest('Server is inaccessible.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverFoldersCreate = serverFoldersRes.map((folder) => {
|
const serverFoldersCreate = serverFoldersRes.map((folder) => {
|
||||||
return {
|
return {
|
||||||
name: folder.name,
|
name: folder.name,
|
||||||
@@ -193,19 +200,6 @@ const create = async (options: {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// for (const serverFolder of serverFolders) {
|
|
||||||
// await prisma.serverFolder.upsert({
|
|
||||||
// create: serverFolder,
|
|
||||||
// update: { name: serverFolder.name },
|
|
||||||
// where: {
|
|
||||||
// uniqueServerFolderId: {
|
|
||||||
// remoteId: serverFolder.remoteId,
|
|
||||||
// serverId: serverFolder.serverId,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,6 +213,8 @@ const create = async (options: {
|
|||||||
throw ApiError.badRequest('Server is inaccessible.');
|
throw ApiError.badRequest('Server is inaccessible.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const navidromeToken = options.token + '||' + options?.altToken;
|
||||||
|
|
||||||
const serverFoldersCreate = serverFoldersRes.map((folder) => {
|
const serverFoldersCreate = serverFoldersRes.map((folder) => {
|
||||||
return {
|
return {
|
||||||
name: folder.name,
|
name: folder.name,
|
||||||
@@ -232,7 +228,7 @@ const create = async (options: {
|
|||||||
remoteUserId: options.remoteUserId,
|
remoteUserId: options.remoteUserId,
|
||||||
serverFolders: { create: serverFoldersCreate },
|
serverFolders: { create: serverFoldersCreate },
|
||||||
serverUrls: { create: { url: options.url } },
|
serverUrls: { create: { url: options.url } },
|
||||||
token: options.token,
|
token: navidromeToken,
|
||||||
type: options.type,
|
type: options.type,
|
||||||
url: options.url,
|
url: options.url,
|
||||||
username: options.username,
|
username: options.username,
|
||||||
@@ -295,8 +291,8 @@ const create = async (options: {
|
|||||||
const update = async (
|
const update = async (
|
||||||
options: { id: string },
|
options: { id: string },
|
||||||
data: {
|
data: {
|
||||||
altToken?: string; // Used for Navidrome only
|
|
||||||
name?: string;
|
name?: string;
|
||||||
|
noCredential?: boolean;
|
||||||
remoteUserId?: string;
|
remoteUserId?: string;
|
||||||
token?: string;
|
token?: string;
|
||||||
type?: ServerType;
|
type?: ServerType;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const detail = {
|
|||||||
const list = {
|
const list = {
|
||||||
body: z.object({}),
|
body: z.object({}),
|
||||||
params: z.object({}),
|
params: z.object({}),
|
||||||
query: z.object({}),
|
query: z.object({ enabled: z.string().optional() }),
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteServer = {
|
const deleteServer = {
|
||||||
@@ -24,6 +24,7 @@ const update = {
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
legacy: z.boolean().optional(),
|
legacy: z.boolean().optional(),
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
|
noCredential: z.boolean().optional(),
|
||||||
password: z.string().optional(),
|
password: z.string().optional(),
|
||||||
type: z.nativeEnum(ServerType),
|
type: z.nativeEnum(ServerType),
|
||||||
url: z.string().optional(),
|
url: z.string().optional(),
|
||||||
@@ -37,6 +38,7 @@ const create = {
|
|||||||
body: z.object({
|
body: z.object({
|
||||||
legacy: z.boolean().optional(),
|
legacy: z.boolean().optional(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
|
noCredential: z.boolean().optional(),
|
||||||
password: z.string(),
|
password: z.string(),
|
||||||
type: z.enum([
|
type: z.enum([
|
||||||
ServerType.JELLYFIN,
|
ServerType.JELLYFIN,
|
||||||
|
|||||||
Reference in New Issue
Block a user