mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 12:30:12 +02:00
Update scanner (server)
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { albumArtistsController } from './album-artists.controller';
|
||||
import { albumsController } from './albums.controller';
|
||||
import { artistsController } from './artists.controller';
|
||||
import { authController } from './auth.controller';
|
||||
import { serversController } from './servers.controller';
|
||||
import { songsController } from './songs.controller';
|
||||
import { usersController } from './users.controller';
|
||||
import { albumArtistsController } from '@controllers/album-artists.controller';
|
||||
import { albumsController } from '@controllers/albums.controller';
|
||||
import { artistsController } from '@controllers/artists.controller';
|
||||
import { authController } from '@controllers/auth.controller';
|
||||
import { serversController } from '@controllers/servers.controller';
|
||||
import { songsController } from '@controllers/songs.controller';
|
||||
import { tasksController } from '@controllers/tasks.controller';
|
||||
import { usersController } from '@controllers/users.controller';
|
||||
|
||||
export const controller = {
|
||||
albumArtists: albumArtistsController,
|
||||
@@ -13,5 +14,6 @@ export const controller = {
|
||||
auth: authController,
|
||||
servers: serversController,
|
||||
songs: songsController,
|
||||
tasks: tasksController,
|
||||
users: usersController,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ServerType } from '@prisma/client';
|
||||
import { Response } from 'express';
|
||||
import { ApiSuccess, getSuccessResponse } from '@/utils';
|
||||
import { ApiError, ApiSuccess, getSuccessResponse } from '@/utils';
|
||||
import { toApiModel } from '@helpers/api-model';
|
||||
import { service } from '@services/index';
|
||||
import { TypedRequest, validation } from '@validations/index';
|
||||
@@ -109,7 +109,7 @@ const refreshServer = async (
|
||||
return res.status(success.statusCode).json(getSuccessResponse(success));
|
||||
};
|
||||
|
||||
const scanServer = async (
|
||||
const fullScanServer = async (
|
||||
req: TypedRequest<typeof validation.servers.scan>,
|
||||
res: Response
|
||||
) => {
|
||||
@@ -118,15 +118,56 @@ const scanServer = async (
|
||||
|
||||
// TODO: Check that server is accessible first with the saved token, otherwise throw error
|
||||
|
||||
const data = await service.servers.fullScan({
|
||||
const scansInProgress = await service.servers.findScanInProgress({
|
||||
serverId,
|
||||
});
|
||||
|
||||
if (scansInProgress.length > 0) {
|
||||
throw ApiError.badRequest('Scan already in progress');
|
||||
}
|
||||
|
||||
const io = req.app.get('socketio');
|
||||
await io.emit('task:started');
|
||||
|
||||
const data = await service.servers.fullScan(req.authUser, {
|
||||
id: serverId,
|
||||
serverFolderId,
|
||||
});
|
||||
|
||||
// return res.status(200).json({ data: null });
|
||||
const success = ApiSuccess.ok({ data });
|
||||
return res.status(success.statusCode).json(getSuccessResponse(success));
|
||||
};
|
||||
|
||||
const quickScanServer = async (
|
||||
req: TypedRequest<typeof validation.servers.scan>,
|
||||
res: Response
|
||||
) => {
|
||||
const { serverId } = req.params;
|
||||
const { serverFolderId } = req.body;
|
||||
|
||||
// TODO: Check that server is accessible first with the saved token, otherwise throw error
|
||||
|
||||
const scansInProgress = await service.servers.findScanInProgress({
|
||||
serverId,
|
||||
});
|
||||
|
||||
if (scansInProgress.length > 0) {
|
||||
throw ApiError.badRequest('Scan already in progress');
|
||||
}
|
||||
|
||||
const io = req.app.get('socketio');
|
||||
await io.emit('task:started');
|
||||
|
||||
// await service.servers.fullScan({
|
||||
// id: serverId,
|
||||
// serverFolderId,
|
||||
// });
|
||||
|
||||
const success = ApiSuccess.ok({ data: null });
|
||||
return res.status(success.statusCode).json(getSuccessResponse(success));
|
||||
};
|
||||
|
||||
const createServerUrl = async (
|
||||
req: TypedRequest<typeof validation.servers.createUrl>,
|
||||
res: Response
|
||||
@@ -228,9 +269,10 @@ export const serversController = {
|
||||
disableServerUrl,
|
||||
enableServerFolder,
|
||||
enableServerUrl,
|
||||
fullScanServer,
|
||||
getServerDetail,
|
||||
getServerList,
|
||||
quickScanServer,
|
||||
refreshServer,
|
||||
scanServer,
|
||||
updateServer,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { queue } from '@/queue/queues';
|
||||
import { toApiModel } from '@helpers/api-model';
|
||||
import { prisma } from '@lib/prisma';
|
||||
import { ApiSuccess } from '@utils/api-success';
|
||||
import { getSuccessResponse } from '@utils/get-success-response';
|
||||
import { validation } from '@validations/index';
|
||||
import { TypedRequest } from '@validations/shared.validation';
|
||||
import { SortOrder } from '../types/types';
|
||||
|
||||
const getActiveTasks = async (_req: Request, res: Response) => {
|
||||
const tasks = await prisma.task.findMany({
|
||||
include: {
|
||||
server: true,
|
||||
user: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: SortOrder.ASC,
|
||||
},
|
||||
where: {
|
||||
completed: false,
|
||||
isError: false,
|
||||
},
|
||||
});
|
||||
|
||||
if (queue.scanner.length === 0) {
|
||||
await prisma.task.updateMany({
|
||||
data: { completed: true, isError: true, message: 'Task not found' },
|
||||
where: { completed: false },
|
||||
});
|
||||
}
|
||||
|
||||
const success = ApiSuccess.ok({ data: toApiModel.tasks({ items: tasks }) });
|
||||
return res.status(success.statusCode).json(getSuccessResponse(success));
|
||||
};
|
||||
|
||||
const cancelAllTasks = async (
|
||||
_req: TypedRequest<typeof validation.tasks.cancelAll>,
|
||||
res: Response
|
||||
) => {
|
||||
const runningTasks = await prisma.task.findMany({
|
||||
include: {
|
||||
server: true,
|
||||
user: true,
|
||||
},
|
||||
where: {
|
||||
completed: false,
|
||||
isError: false,
|
||||
},
|
||||
});
|
||||
|
||||
for (const task of runningTasks) {
|
||||
queue.scanner.push({
|
||||
fn: async () => {
|
||||
return {};
|
||||
},
|
||||
id: task.id,
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.task.updateMany({
|
||||
data: {
|
||||
completed: true,
|
||||
message: 'Task was cancelled by user',
|
||||
},
|
||||
where: { completed: false },
|
||||
});
|
||||
|
||||
const success = ApiSuccess.noContent({ data: null });
|
||||
return res.status(success.statusCode).json(getSuccessResponse(success));
|
||||
};
|
||||
|
||||
const cancelTaskById = async (
|
||||
req: TypedRequest<typeof validation.tasks.cancel>,
|
||||
res: Response
|
||||
) => {
|
||||
const { taskId } = req.params;
|
||||
|
||||
const task = await prisma.task.update({
|
||||
data: {
|
||||
completed: true,
|
||||
message: 'Task was cancelled by user',
|
||||
},
|
||||
include: {
|
||||
server: true,
|
||||
user: true,
|
||||
},
|
||||
where: { id: taskId },
|
||||
});
|
||||
|
||||
queue.scanner.push({
|
||||
fn: async () => {
|
||||
return {};
|
||||
},
|
||||
id: taskId,
|
||||
});
|
||||
|
||||
const success = ApiSuccess.ok({
|
||||
data: toApiModel.tasks({ items: [task] })[0],
|
||||
});
|
||||
return res.status(success.statusCode).json(getSuccessResponse(success));
|
||||
};
|
||||
|
||||
export const tasksController = {
|
||||
cancelAllTasks,
|
||||
cancelTaskById,
|
||||
getActiveTasks,
|
||||
};
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
ServerUrl,
|
||||
Song,
|
||||
SongRating,
|
||||
Task,
|
||||
User,
|
||||
UserServerUrl,
|
||||
} from '@prisma/client';
|
||||
@@ -516,6 +517,17 @@ const servers = (
|
||||
);
|
||||
};
|
||||
|
||||
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 {
|
||||
@@ -578,9 +590,46 @@ const users = (
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
Generated
+283
-9
@@ -7,7 +7,7 @@
|
||||
"": {
|
||||
"name": "feishin-server",
|
||||
"version": "1.0.0-alpha1",
|
||||
"license": "ISC",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^4.5.0",
|
||||
"axios": "^0.27.2",
|
||||
@@ -25,6 +25,7 @@
|
||||
"passport": "^0.4.1",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"socket.io": "^4.5.3",
|
||||
"zod": "^3.19.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -1552,6 +1553,11 @@
|
||||
"@sinonjs/commons": "^1.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
|
||||
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz",
|
||||
@@ -1665,6 +1671,11 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cookie": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
|
||||
},
|
||||
"node_modules/@types/cookie-parser": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz",
|
||||
@@ -1677,8 +1688,7 @@
|
||||
"node_modules/@types/cors": {
|
||||
"version": "2.8.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
|
||||
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw=="
|
||||
},
|
||||
"node_modules/@types/eslint": {
|
||||
"version": "8.4.8",
|
||||
@@ -1827,8 +1837,7 @@
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.8.4.tgz",
|
||||
"integrity": "sha512-WdlVphvfR/GJCLEMbNA8lJ0lhFNBj4SW3O+O5/cEGw9oYrv0al9zTwuQsq+myDUXgNx2jgBynoVgZ2MMJ6pbow==",
|
||||
"dev": true
|
||||
"integrity": "sha512-WdlVphvfR/GJCLEMbNA8lJ0lhFNBj4SW3O+O5/cEGw9oYrv0al9zTwuQsq+myDUXgNx2jgBynoVgZ2MMJ6pbow=="
|
||||
},
|
||||
"node_modules/@types/passport": {
|
||||
"version": "1.0.7",
|
||||
@@ -2866,6 +2875,14 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/base64id": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
|
||||
"engines": {
|
||||
"node": "^4.5.0 || >= 5.9"
|
||||
}
|
||||
},
|
||||
"node_modules/bcryptjs": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
|
||||
@@ -3539,6 +3556,55 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz",
|
||||
"integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==",
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.4.1",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/node": ">=10.0.0",
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "2.0.0",
|
||||
"cookie": "~0.4.1",
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.0.3",
|
||||
"ws": "~8.2.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-parser": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz",
|
||||
"integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io/node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io/node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz",
|
||||
@@ -8600,6 +8666,81 @@
|
||||
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io": {
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.3.tgz",
|
||||
"integrity": "sha512-zdpnnKU+H6mOp7nYRXH4GNv1ux6HL6+lHL8g7Ds7Lj8CkdK1jJK/dlwsKDculbyOHifcJ0Pr/yeXnZQ5GeFrcg==",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "~2.0.0",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io": "~6.2.0",
|
||||
"socket.io-adapter": "~2.4.0",
|
||||
"socket.io-parser": "~4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-adapter": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz",
|
||||
"integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg=="
|
||||
},
|
||||
"node_modules/socket.io-parser": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.1.tgz",
|
||||
"integrity": "sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser/node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser/node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/socket.io/node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io/node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
@@ -9531,6 +9672,26 @@
|
||||
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
|
||||
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
@@ -10791,6 +10952,11 @@
|
||||
"@sinonjs/commons": "^1.7.0"
|
||||
}
|
||||
},
|
||||
"@socket.io/component-emitter": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
|
||||
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
|
||||
},
|
||||
"@tsconfig/node10": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz",
|
||||
@@ -10903,6 +11069,11 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/cookie": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
|
||||
},
|
||||
"@types/cookie-parser": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz",
|
||||
@@ -10915,8 +11086,7 @@
|
||||
"@types/cors": {
|
||||
"version": "2.8.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
|
||||
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw=="
|
||||
},
|
||||
"@types/eslint": {
|
||||
"version": "8.4.8",
|
||||
@@ -11065,8 +11235,7 @@
|
||||
"@types/node": {
|
||||
"version": "18.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.8.4.tgz",
|
||||
"integrity": "sha512-WdlVphvfR/GJCLEMbNA8lJ0lhFNBj4SW3O+O5/cEGw9oYrv0al9zTwuQsq+myDUXgNx2jgBynoVgZ2MMJ6pbow==",
|
||||
"dev": true
|
||||
"integrity": "sha512-WdlVphvfR/GJCLEMbNA8lJ0lhFNBj4SW3O+O5/cEGw9oYrv0al9zTwuQsq+myDUXgNx2jgBynoVgZ2MMJ6pbow=="
|
||||
},
|
||||
"@types/passport": {
|
||||
"version": "1.0.7",
|
||||
@@ -11876,6 +12045,11 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"base64id": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
|
||||
},
|
||||
"bcryptjs": {
|
||||
"version": "2.4.3",
|
||||
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
|
||||
@@ -12381,6 +12555,43 @@
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
||||
},
|
||||
"engine.io": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz",
|
||||
"integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==",
|
||||
"requires": {
|
||||
"@types/cookie": "^0.4.1",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/node": ">=10.0.0",
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "2.0.0",
|
||||
"cookie": "~0.4.1",
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.0.3",
|
||||
"ws": "~8.2.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"engine.io-parser": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz",
|
||||
"integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg=="
|
||||
},
|
||||
"enhanced-resolve": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz",
|
||||
@@ -16105,6 +16316,63 @@
|
||||
"is-fullwidth-code-point": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"socket.io": {
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.3.tgz",
|
||||
"integrity": "sha512-zdpnnKU+H6mOp7nYRXH4GNv1ux6HL6+lHL8g7Ds7Lj8CkdK1jJK/dlwsKDculbyOHifcJ0Pr/yeXnZQ5GeFrcg==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "~2.0.0",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io": "~6.2.0",
|
||||
"socket.io-adapter": "~2.4.0",
|
||||
"socket.io-parser": "~4.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"socket.io-adapter": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz",
|
||||
"integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg=="
|
||||
},
|
||||
"socket.io-parser": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.1.tgz",
|
||||
"integrity": "sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==",
|
||||
"requires": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
@@ -16799,6 +17067,12 @@
|
||||
"signal-exit": "^3.0.7"
|
||||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "8.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
|
||||
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
|
||||
"requires": {}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
"passport": "^0.4.1",
|
||||
"passport-jwt": "^4.0.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"socket.io": "^4.5.3",
|
||||
"zod": "^3.19.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `name` on the `Task` table. All the data in the column will be lost.
|
||||
- You are about to drop the column `progress` on the `Task` table. All the data in the column will be lost.
|
||||
- Made the column `isError` on table `Task` required. This step will fail if there are existing NULL values in that column.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Task" DROP COLUMN "name",
|
||||
DROP COLUMN "progress",
|
||||
ADD COLUMN "userId" UUID,
|
||||
ALTER COLUMN "isError" SET NOT NULL;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Task" ADD CONSTRAINT "Task_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
@@ -75,12 +75,12 @@ model User {
|
||||
|
||||
serverFolderPermissions ServerFolderPermission[]
|
||||
serverPermissions ServerPermission[]
|
||||
// serverCredentials ServerCredential[]
|
||||
albumArtistFavorites AlbumArtistFavorite[]
|
||||
artistFavorites ArtistFavorite[]
|
||||
albumFavorites AlbumFavorite[]
|
||||
songFavorites SongFavorite[]
|
||||
userServerUrls UserServerUrl[]
|
||||
tasks Task[]
|
||||
}
|
||||
|
||||
model History {
|
||||
@@ -112,26 +112,10 @@ model Server {
|
||||
serverUrls ServerUrl[]
|
||||
folders Folder[]
|
||||
serverPermissions ServerPermission[]
|
||||
// serverCredentials ServerCredential[]
|
||||
tasks Task[]
|
||||
userServerUrls UserServerUrl[]
|
||||
}
|
||||
|
||||
// model ServerCredential {
|
||||
// id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
// username String
|
||||
// enabled Boolean @default(false)
|
||||
// credential String
|
||||
// createdAt DateTime @default(now())
|
||||
// updatedAt DateTime @updatedAt
|
||||
|
||||
// server Server @relation(fields: [serverId], references: [id], onDelete: Cascade)
|
||||
// serverId String @db.Uuid
|
||||
|
||||
// user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
// userId String @db.Uuid
|
||||
// }
|
||||
|
||||
model Folder {
|
||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
name String
|
||||
@@ -518,15 +502,16 @@ model Song {
|
||||
|
||||
model Task {
|
||||
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||
name String
|
||||
type TaskType
|
||||
message String?
|
||||
progress String?
|
||||
completed Boolean @default(false)
|
||||
isError Boolean? @default(false)
|
||||
isError Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
server Server @relation(fields: [serverId], references: [id], onDelete: Cascade)
|
||||
serverId String @db.Uuid
|
||||
|
||||
user User? @relation(fields: [userId], references: [id])
|
||||
userId String? @db.Uuid
|
||||
}
|
||||
|
||||
+25
-15
@@ -1,24 +1,34 @@
|
||||
/* eslint-disable promise/catch-or-return */
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import { PrismaClient, Prisma } from '@prisma/client';
|
||||
import { randomString } from '../utils';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
const hashedPassword = await bcrypt.hash('admin', 12);
|
||||
const hashedPassword =
|
||||
'$2y$12$icIH42ono1yTBypZ34V/PuDMXIbMD04GtSB6pgYpcwbjjIvujzv2y';
|
||||
|
||||
await prisma.user.upsert({
|
||||
create: {
|
||||
deviceId: `admin_${randomString(10)}`,
|
||||
enabled: true,
|
||||
isAdmin: true,
|
||||
password: hashedPassword,
|
||||
username: 'admin',
|
||||
},
|
||||
update: {},
|
||||
where: { username: 'admin' },
|
||||
});
|
||||
let error;
|
||||
do {
|
||||
try {
|
||||
await prisma.user.upsert({
|
||||
create: {
|
||||
deviceId: `admin_${randomString(10)}`,
|
||||
enabled: true,
|
||||
isAdmin: true,
|
||||
password: hashedPassword,
|
||||
username: 'admin',
|
||||
},
|
||||
update: {},
|
||||
where: { username: 'admin' },
|
||||
});
|
||||
} catch (e) {
|
||||
if (e instanceof Prisma.PrismaClientInitializationError) {
|
||||
error = 'retry';
|
||||
}
|
||||
|
||||
error = undefined;
|
||||
}
|
||||
} while (error === 'retry');
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
@@ -478,6 +478,11 @@ const scanAll = async (
|
||||
await scanAlbums(server, serverFolder, task);
|
||||
await scanSongs(server, serverFolder, task);
|
||||
await checkDeleted(server, serverFolder, task);
|
||||
|
||||
await prisma.serverFolder.update({
|
||||
data: { lastScannedAt: new Date() },
|
||||
where: { id: serverFolder.id },
|
||||
});
|
||||
}
|
||||
|
||||
return { task };
|
||||
|
||||
@@ -30,10 +30,11 @@ const authenticate = async (options: {
|
||||
};
|
||||
|
||||
const getGenres = async (server: Server, params?: NDGenreListParams) => {
|
||||
const [ndToken] = server.token.split('||');
|
||||
const { data } = await api.get<NDGenreListResponse>(
|
||||
`${server.url}/api/genre`,
|
||||
{
|
||||
headers: { 'x-nd-authorization': `Bearer ${server.token}` },
|
||||
headers: { 'x-nd-authorization': `Bearer ${ndToken}` },
|
||||
params,
|
||||
}
|
||||
);
|
||||
@@ -42,10 +43,11 @@ const getGenres = async (server: Server, params?: NDGenreListParams) => {
|
||||
};
|
||||
|
||||
const getArtists = async (server: Server, params?: NDGenreListParams) => {
|
||||
const [ndToken] = server.token.split('||');
|
||||
const { data } = await api.get<NDArtistListResponse>(
|
||||
`${server.url}/api/artist`,
|
||||
{
|
||||
headers: { 'x-nd-authorization': `Bearer ${server.token}` },
|
||||
headers: { 'x-nd-authorization': `Bearer ${ndToken}` },
|
||||
params,
|
||||
}
|
||||
);
|
||||
@@ -54,10 +56,11 @@ const getArtists = async (server: Server, params?: NDGenreListParams) => {
|
||||
};
|
||||
|
||||
const getAlbums = async (server: Server, params?: NDAlbumListParams) => {
|
||||
const [ndToken] = server.token.split('||');
|
||||
const { data } = await api.get<NDAlbumListResponse>(
|
||||
`${server.url}/api/album`,
|
||||
{
|
||||
headers: { 'x-nd-authorization': `Bearer ${server.token}` },
|
||||
headers: { 'x-nd-authorization': `Bearer ${ndToken}` },
|
||||
params,
|
||||
}
|
||||
);
|
||||
@@ -66,8 +69,9 @@ const getAlbums = async (server: Server, params?: NDAlbumListParams) => {
|
||||
};
|
||||
|
||||
const getSongs = async (server: Server, params?: NDSongListParams) => {
|
||||
const [ndToken] = server.token.split('||');
|
||||
const { data } = await api.get<NDSongListResponse>(`${server.url}/api/song`, {
|
||||
headers: { 'x-nd-authorization': `Bearer ${server.token}` },
|
||||
headers: { 'x-nd-authorization': `Bearer ${ndToken}` },
|
||||
params,
|
||||
});
|
||||
|
||||
|
||||
@@ -37,8 +37,14 @@ export const scanGenres = async (server: Server, task: Task) => {
|
||||
|
||||
export const scanAlbumArtists = async (
|
||||
server: Server,
|
||||
serverFolder: ServerFolder
|
||||
serverFolder: ServerFolder,
|
||||
task: Task
|
||||
) => {
|
||||
await prisma.task.update({
|
||||
data: { message: 'Scanning artists' },
|
||||
where: { id: task.id },
|
||||
});
|
||||
|
||||
const artists = await navidromeApi.getArtists(server);
|
||||
|
||||
const externalsCreateMany = artists
|
||||
@@ -101,8 +107,14 @@ export const scanAlbumArtists = async (
|
||||
|
||||
export const scanAlbums = async (
|
||||
server: Server,
|
||||
serverFolder: ServerFolder
|
||||
serverFolder: ServerFolder,
|
||||
task: Task
|
||||
) => {
|
||||
await prisma.task.update({
|
||||
data: { message: 'Scanning artists' },
|
||||
where: { id: task.id },
|
||||
});
|
||||
|
||||
let start = 0;
|
||||
let count = 5000;
|
||||
do {
|
||||
@@ -153,26 +165,38 @@ export const scanAlbums = async (
|
||||
}
|
||||
}
|
||||
|
||||
const artistsConnect = validArtistIds.map((id) => ({
|
||||
uniqueArtistId: {
|
||||
remoteId: id,
|
||||
serverId: server.id,
|
||||
},
|
||||
}));
|
||||
// const artistsConnect = validArtistIds.map((id) => ({
|
||||
// uniqueArtistId: {
|
||||
// remoteId: id,
|
||||
// serverId: server.id,
|
||||
// },
|
||||
// }));
|
||||
|
||||
const albumArtistConnect = album.artistId
|
||||
const aaConnect = [];
|
||||
const albumArtistConnect = album.albumArtistId
|
||||
? {
|
||||
uniqueAlbumArtistId: {
|
||||
remoteId: album.artistId,
|
||||
remoteId: album.albumArtistId,
|
||||
serverId: server.id,
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
|
||||
aaConnect.push(
|
||||
...validArtistIds.map((id) => ({
|
||||
uniqueAlbumArtistId: {
|
||||
remoteId: id,
|
||||
serverId: server.id,
|
||||
},
|
||||
}))
|
||||
);
|
||||
|
||||
albumArtistConnect && aaConnect.push(albumArtistConnect);
|
||||
|
||||
await prisma.album.upsert({
|
||||
create: {
|
||||
albumArtists: { connect: albumArtistConnect },
|
||||
artists: { connect: artistsConnect },
|
||||
albumArtists: { connect: aaConnect },
|
||||
// artists: { connect: artistsConnect },
|
||||
deleted: false,
|
||||
genres: { connect: genresConnect },
|
||||
images: { connect: imagesConnect },
|
||||
@@ -188,8 +212,8 @@ export const scanAlbums = async (
|
||||
sortName: album.name,
|
||||
},
|
||||
update: {
|
||||
albumArtists: { connect: albumArtistConnect },
|
||||
artists: { connect: artistsConnect },
|
||||
albumArtists: { connect: aaConnect },
|
||||
// artists: { connect: artistsConnect },
|
||||
deleted: false,
|
||||
genres: { connect: genresConnect },
|
||||
images: { connect: imagesConnect },
|
||||
@@ -218,7 +242,16 @@ export const scanAlbums = async (
|
||||
} while (count === CHUNK_SIZE);
|
||||
};
|
||||
|
||||
const scanSongs = async (server: Server, serverFolder: ServerFolder) => {
|
||||
const scanSongs = async (
|
||||
server: Server,
|
||||
serverFolder: ServerFolder,
|
||||
task: Task
|
||||
) => {
|
||||
await prisma.task.update({
|
||||
data: { message: 'Scanning artists' },
|
||||
where: { id: task.id },
|
||||
});
|
||||
|
||||
let start = 0;
|
||||
let count = 5000;
|
||||
do {
|
||||
@@ -313,7 +346,7 @@ const scanSongs = async (server: Server, serverFolder: ServerFolder) => {
|
||||
}
|
||||
|
||||
for (const folder of createdFolders) {
|
||||
if (folder.parentId) break;
|
||||
if (folder?.parentId || !folder) break;
|
||||
|
||||
const pathSplit = folder.path.split('/');
|
||||
const parentPath = pathSplit.slice(0, pathSplit.length - 1).join('/');
|
||||
@@ -359,9 +392,14 @@ const scanAll = async (
|
||||
|
||||
for (const serverFolder of serverFolders) {
|
||||
await scanGenres(server, task);
|
||||
await scanAlbumArtists(server, serverFolder);
|
||||
await scanAlbums(server, serverFolder);
|
||||
await scanSongs(server, serverFolder);
|
||||
await scanAlbumArtists(server, serverFolder, task);
|
||||
await scanAlbums(server, serverFolder, task);
|
||||
await scanSongs(server, serverFolder, task);
|
||||
|
||||
await prisma.serverFolder.update({
|
||||
data: { lastScannedAt: new Date() },
|
||||
where: { id: serverFolder.id },
|
||||
});
|
||||
}
|
||||
|
||||
return { task };
|
||||
|
||||
@@ -8,7 +8,7 @@ interface QueueTask {
|
||||
task: Task;
|
||||
}
|
||||
|
||||
export const scannerQueue: Queue = new Queue(
|
||||
export const scannerQueue: Queue | any = new Queue(
|
||||
async (task: QueueTask, cb: any) => {
|
||||
const result = await task.fn();
|
||||
return cb(null, result);
|
||||
@@ -18,26 +18,20 @@ export const scannerQueue: Queue = new Queue(
|
||||
cancelIfRunning: true,
|
||||
concurrent: 1,
|
||||
filo: false,
|
||||
maxRetries: 5,
|
||||
maxTimeout: 600000,
|
||||
retryDelay: 2000,
|
||||
}
|
||||
);
|
||||
|
||||
scannerQueue.on('task_finish', async (taskId) => {
|
||||
scannerQueue.on('task_finish', async (taskId: string) => {
|
||||
await prisma.task.update({
|
||||
data: {
|
||||
completed: true,
|
||||
isError: false,
|
||||
progress: null,
|
||||
},
|
||||
where: { id: taskId },
|
||||
});
|
||||
});
|
||||
|
||||
scannerQueue.on('task_failed', async (taskId, errorMessage) => {
|
||||
const dbTaskId = taskId.split('(')[1].split(')')[0];
|
||||
|
||||
scannerQueue.on('task_failed', async (taskId: string, errorMessage: string) => {
|
||||
console.log('errorMessage', errorMessage);
|
||||
await prisma.task.update({
|
||||
data: {
|
||||
@@ -45,13 +39,13 @@ scannerQueue.on('task_failed', async (taskId, errorMessage) => {
|
||||
isError: true,
|
||||
message: errorMessage,
|
||||
},
|
||||
where: { id: dbTaskId },
|
||||
where: { id: taskId },
|
||||
});
|
||||
});
|
||||
|
||||
scannerQueue.on('drain', async () => {
|
||||
await prisma.task.updateMany({
|
||||
data: { completed: true, progress: null },
|
||||
data: { completed: true },
|
||||
where: { completed: false },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,8 +26,14 @@ export const scanGenres = async (server: Server, task: Task) => {
|
||||
|
||||
export const scanAlbumArtists = async (
|
||||
server: Server,
|
||||
serverFolder: ServerFolder
|
||||
serverFolder: ServerFolder,
|
||||
task: Task
|
||||
) => {
|
||||
await prisma.task.update({
|
||||
data: { message: 'Scanning artists' },
|
||||
where: { id: task.id },
|
||||
});
|
||||
|
||||
const artists = await subsonicApi.getArtists(server, serverFolder.remoteId);
|
||||
|
||||
for (const artist of artists) {
|
||||
@@ -58,8 +64,14 @@ export const scanAlbumArtists = async (
|
||||
|
||||
export const scanAlbums = async (
|
||||
server: Server,
|
||||
serverFolder: ServerFolder
|
||||
serverFolder: ServerFolder,
|
||||
task: Task
|
||||
) => {
|
||||
await prisma.task.update({
|
||||
data: { message: 'Scanning albums' },
|
||||
where: { id: task.id },
|
||||
});
|
||||
|
||||
const albums = await subsonicApi.getAlbums(server, {
|
||||
musicFolderId: serverFolder.id,
|
||||
offset: 0,
|
||||
@@ -241,8 +253,14 @@ const throttledAlbumFetch = throttle(
|
||||
|
||||
export const scanAlbumDetail = async (
|
||||
server: Server,
|
||||
serverFolder: ServerFolder
|
||||
serverFolder: ServerFolder,
|
||||
task: Task
|
||||
) => {
|
||||
await prisma.task.update({
|
||||
data: { message: 'Scanning songs' },
|
||||
where: { id: task.id },
|
||||
});
|
||||
|
||||
const promises = [];
|
||||
const dbAlbums = await prisma.album.findMany({
|
||||
where: {
|
||||
@@ -271,9 +289,14 @@ const scanAll = async (
|
||||
|
||||
for (const serverFolder of serverFolders) {
|
||||
await scanGenres(server, task);
|
||||
await scanAlbumArtists(server, serverFolder);
|
||||
await scanAlbums(server, serverFolder);
|
||||
await scanAlbumDetail(server, serverFolder);
|
||||
await scanAlbumArtists(server, serverFolder, task);
|
||||
await scanAlbums(server, serverFolder, task);
|
||||
await scanAlbumDetail(server, serverFolder, task);
|
||||
|
||||
await prisma.serverFolder.update({
|
||||
data: { lastScannedAt: new Date() },
|
||||
where: { id: serverFolder.id },
|
||||
});
|
||||
}
|
||||
|
||||
return { task };
|
||||
|
||||
@@ -48,7 +48,15 @@ router
|
||||
.post(
|
||||
validateRequest(validation.servers.scan),
|
||||
authenticateAdmin,
|
||||
controller.servers.scanServer
|
||||
controller.servers.quickScanServer
|
||||
);
|
||||
|
||||
router
|
||||
.route('/:serverId/full-scan')
|
||||
.post(
|
||||
validateRequest(validation.servers.scan),
|
||||
authenticateAdmin,
|
||||
controller.servers.fullScanServer
|
||||
);
|
||||
|
||||
router
|
||||
|
||||
@@ -1,11 +1,39 @@
|
||||
import express, { Router } from 'express';
|
||||
import { controller } from '@controllers/index';
|
||||
import { prisma } from '@lib/prisma';
|
||||
import { authenticateAdmin } from '@middleware/authenticate-admin';
|
||||
import { ApiError } from '@utils/api-error';
|
||||
import { validation } from '@validations/index';
|
||||
import { validateRequest } from '@validations/shared.validation';
|
||||
|
||||
export const router: Router = express.Router({ mergeParams: true });
|
||||
|
||||
router.post('/scan', async (_req, res) => {
|
||||
return res.status(200);
|
||||
router
|
||||
.route('/')
|
||||
.get(validateRequest(validation.tasks.list), controller.tasks.getActiveTasks);
|
||||
|
||||
router
|
||||
.route('/cancel')
|
||||
.post(
|
||||
authenticateAdmin,
|
||||
validateRequest(validation.tasks.cancelAll),
|
||||
controller.tasks.cancelAllTasks
|
||||
);
|
||||
|
||||
router.param('taskId', async (_req, _res, next, taskId) => {
|
||||
const task = await prisma.task.findUnique({ where: { id: taskId } });
|
||||
|
||||
if (!task) {
|
||||
throw ApiError.notFound('Task not found');
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
router.post('/', async (_req, res) => {
|
||||
return res.status(200).json({});
|
||||
});
|
||||
router
|
||||
.route('/:taskId/cancel')
|
||||
.post(
|
||||
authenticateAdmin,
|
||||
validateRequest(validation.tasks.cancel),
|
||||
controller.tasks.cancelTaskById
|
||||
);
|
||||
|
||||
+17
-1
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable import/order */
|
||||
import path from 'path';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import cors from 'cors';
|
||||
@@ -6,6 +7,9 @@ import passport from 'passport';
|
||||
import 'express-async-errors';
|
||||
import { errorHandler } from '@/middleware';
|
||||
import { routes } from '@routes/index';
|
||||
import { sockets } from '@sockets/index';
|
||||
import * as http from 'http';
|
||||
import * as socketio from 'socket.io';
|
||||
|
||||
require('./lib/passport');
|
||||
|
||||
@@ -37,4 +41,16 @@ app.get('/', (_req, res) => {
|
||||
app.use(routes);
|
||||
app.use(errorHandler);
|
||||
|
||||
app.listen(9321, () => console.log(`Listening on port ${PORT}`));
|
||||
const server = http.createServer(app);
|
||||
const io = new socketio.Server(server, {
|
||||
cors: {
|
||||
credentials: false,
|
||||
methods: ['GET', 'POST'],
|
||||
origin: [`http://localhost:4343`, `${process.env.APP_BASE_URL}`],
|
||||
},
|
||||
});
|
||||
|
||||
app.set('socketio', io);
|
||||
io.on('connection', (socket) => sockets(socket));
|
||||
|
||||
server.listen(9321, () => console.log(`Listening on port ${PORT}`));
|
||||
|
||||
@@ -364,7 +364,22 @@ const refresh = async (options: { id: string }) => {
|
||||
return server;
|
||||
};
|
||||
|
||||
const fullScan = async (options: { id: string; serverFolderId?: string[] }) => {
|
||||
const findScanInProgress = async (options: { serverId: string }) => {
|
||||
const tasks = await prisma.task.findMany({
|
||||
where: {
|
||||
OR: [{ type: TaskType.FULL_SCAN }, { type: TaskType.QUICK_SCAN }],
|
||||
completed: false,
|
||||
serverId: options.serverId,
|
||||
},
|
||||
});
|
||||
|
||||
return tasks;
|
||||
};
|
||||
|
||||
const fullScan = async (
|
||||
user: AuthUser,
|
||||
options: { id: string; serverFolderId?: string[] }
|
||||
) => {
|
||||
const { id, serverFolderId } = options;
|
||||
|
||||
// Only allow scan of enabled folders
|
||||
@@ -392,10 +407,9 @@ const fullScan = async (options: { id: string; serverFolderId?: string[] }) => {
|
||||
|
||||
const task = await prisma.task.create({
|
||||
data: {
|
||||
completed: false,
|
||||
name: 'Full scan',
|
||||
server: { connect: { id: server.id } },
|
||||
type: TaskType.FULL_SCAN,
|
||||
user: { connect: { id: user.id } },
|
||||
},
|
||||
});
|
||||
|
||||
@@ -411,7 +425,7 @@ const fullScan = async (options: { id: string; serverFolderId?: string[] }) => {
|
||||
await navidrome.scanner.scanAll(server, serverFolders, task);
|
||||
}
|
||||
|
||||
return {};
|
||||
return task;
|
||||
};
|
||||
|
||||
const findServerUrlById = async (options: { id: string }) => {
|
||||
@@ -422,69 +436,6 @@ const findServerUrlById = async (options: { id: string }) => {
|
||||
return serverUrl;
|
||||
};
|
||||
|
||||
// const findCredentialById = async (options: { id: string }) => {
|
||||
// const credential = await prisma.serverCredential.findUnique({
|
||||
// where: { id: options.id },
|
||||
// });
|
||||
|
||||
// if (!credential) {
|
||||
// throw ApiError.notFound('Credential not found.');
|
||||
// }
|
||||
|
||||
// return credential;
|
||||
// };
|
||||
|
||||
// const createCredential = async (options: {
|
||||
// credential: string;
|
||||
// serverId: string;
|
||||
// userId: string;
|
||||
// username: string;
|
||||
// }) => {
|
||||
// const { credential, serverId, userId, username } = options;
|
||||
|
||||
// const serverCredential = await prisma.serverCredential.create({
|
||||
// data: {
|
||||
// credential,
|
||||
// serverId,
|
||||
// userId,
|
||||
// username,
|
||||
// },
|
||||
// });
|
||||
|
||||
// return serverCredential;
|
||||
// };
|
||||
|
||||
// const deleteCredentialById = async (options: { id: string }) => {
|
||||
// await prisma.serverCredential.delete({
|
||||
// where: { id: options.id },
|
||||
// });
|
||||
// };
|
||||
|
||||
// const enableCredentialById = async (options: { id: string }) => {
|
||||
// const serverCredential = await prisma.serverCredential.update({
|
||||
// data: { enabled: true },
|
||||
// where: { id: options.id },
|
||||
// });
|
||||
|
||||
// const { id, userId, serverId } = serverCredential;
|
||||
|
||||
// await prisma.serverCredential.updateMany({
|
||||
// data: { enabled: false },
|
||||
// where: { AND: [{ serverId, userId }, { NOT: { id } }] },
|
||||
// });
|
||||
|
||||
// return serverCredential;
|
||||
// };
|
||||
|
||||
// const disableCredentialById = async (options: { id: string }) => {
|
||||
// const serverCredential = await prisma.serverCredential.update({
|
||||
// data: { enabled: false },
|
||||
// where: { id: options.id },
|
||||
// });
|
||||
|
||||
// return serverCredential;
|
||||
// };
|
||||
|
||||
const createUrl = async (options: { serverId: string; url: string }) => {
|
||||
const { serverId, url } = options;
|
||||
|
||||
@@ -589,6 +540,7 @@ export const serversService = {
|
||||
findById,
|
||||
findFolderById,
|
||||
findMany,
|
||||
findScanInProgress,
|
||||
findServerUrlById,
|
||||
findUrlById,
|
||||
fullScan,
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Socket } from 'socket.io';
|
||||
|
||||
export const sockets = (socket: Socket) => {
|
||||
socket.broadcast.emit('user:connected', {
|
||||
userID: socket.id,
|
||||
username: socket.handshake.query.username,
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
socket.broadcast.emit('user:disconnected', {
|
||||
userID: socket.id,
|
||||
username: socket.handshake.query.username,
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -1,12 +1,13 @@
|
||||
import { albumArtistsValidation } from './album-artists.validation';
|
||||
import { albumsValidation } from './albums.validation';
|
||||
import { artistsValidation } from './artists.validation';
|
||||
import { authValidation } from './auth.validation';
|
||||
import { serversValidation } from './servers.validation';
|
||||
import { songsValidation } from './songs.validation';
|
||||
import { usersValidation } from './users.validation';
|
||||
import { albumArtistsValidation } from '@validations/album-artists.validation';
|
||||
import { albumsValidation } from '@validations/albums.validation';
|
||||
import { artistsValidation } from '@validations/artists.validation';
|
||||
import { authValidation } from '@validations/auth.validation';
|
||||
import { serversValidation } from '@validations/servers.validation';
|
||||
import { songsValidation } from '@validations/songs.validation';
|
||||
import { tasksValidation } from '@validations/tasks.validation';
|
||||
import { usersValidation } from '@validations/users.validation';
|
||||
|
||||
export { validateRequest, TypedRequest } from './shared.validation';
|
||||
export { validateRequest, TypedRequest } from '@validations/shared.validation';
|
||||
|
||||
export const validation = {
|
||||
albumArtists: albumArtistsValidation,
|
||||
@@ -15,5 +16,6 @@ export const validation = {
|
||||
auth: authValidation,
|
||||
servers: serversValidation,
|
||||
songs: songsValidation,
|
||||
tasks: tasksValidation,
|
||||
users: usersValidation,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { z } from 'zod';
|
||||
import { idValidation } from '@validations/shared.validation';
|
||||
|
||||
const list = {
|
||||
body: z.object({}),
|
||||
params: z.object({}),
|
||||
query: z.object({}),
|
||||
};
|
||||
|
||||
const cancelAll = {
|
||||
body: z.object({}),
|
||||
params: z.object({}),
|
||||
query: z.object({}),
|
||||
};
|
||||
|
||||
const cancel = {
|
||||
body: z.object({}),
|
||||
params: z.object({
|
||||
...idValidation('taskId'),
|
||||
}),
|
||||
query: z.object({}),
|
||||
};
|
||||
|
||||
export const tasksValidation = {
|
||||
cancel,
|
||||
cancelAll,
|
||||
list,
|
||||
};
|
||||
Reference in New Issue
Block a user