Files
ripster/backend/src/routes/pipelineRoutes.js
2026-03-13 11:07:34 +00:00

331 lines
9.8 KiB
JavaScript

const express = require('express');
const asyncHandler = require('../middleware/asyncHandler');
const pipelineService = require('../services/pipelineService');
const diskDetectionService = require('../services/diskDetectionService');
const hardwareMonitorService = require('../services/hardwareMonitorService');
const logger = require('../services/logger').child('PIPELINE_ROUTE');
const router = express.Router();
router.get(
'/state',
asyncHandler(async (req, res) => {
logger.debug('get:state', { reqId: req.reqId });
res.json({
pipeline: pipelineService.getSnapshot(),
hardwareMonitoring: hardwareMonitorService.getSnapshot()
});
})
);
router.post(
'/analyze',
asyncHandler(async (req, res) => {
logger.info('post:analyze', { reqId: req.reqId });
const result = await pipelineService.analyzeDisc();
res.json({ result });
})
);
router.post(
'/rescan-disc',
asyncHandler(async (req, res) => {
logger.info('post:rescan-disc', { reqId: req.reqId });
const result = await diskDetectionService.rescanAndEmit();
res.json({ result });
})
);
router.get(
'/omdb/search',
asyncHandler(async (req, res) => {
const query = req.query.q || '';
logger.info('get:omdb:search', { reqId: req.reqId, query });
const results = await pipelineService.searchOmdb(String(query));
res.json({ results });
})
);
router.get(
'/cd/musicbrainz/search',
asyncHandler(async (req, res) => {
const query = req.query.q || '';
logger.info('get:cd:musicbrainz:search', { reqId: req.reqId, query });
const results = await pipelineService.searchMusicBrainz(String(query));
res.json({ results });
})
);
router.get(
'/cd/musicbrainz/release/:mbId',
asyncHandler(async (req, res) => {
const mbId = String(req.params.mbId || '').trim();
if (!mbId) {
const error = new Error('mbId fehlt.');
error.statusCode = 400;
throw error;
}
logger.info('get:cd:musicbrainz:release', { reqId: req.reqId, mbId });
const release = await pipelineService.getMusicBrainzReleaseById(mbId);
res.json({ release });
})
);
router.post(
'/cd/select-metadata',
asyncHandler(async (req, res) => {
const { jobId, title, artist, year, mbId, coverUrl, tracks } = req.body;
if (!jobId) {
const error = new Error('jobId fehlt.');
error.statusCode = 400;
throw error;
}
logger.info('post:cd:select-metadata', { reqId: req.reqId, jobId, title, artist, year, mbId });
const job = await pipelineService.selectCdMetadata({
jobId: Number(jobId),
title,
artist,
year,
mbId,
coverUrl,
tracks
});
res.json({ job });
})
);
router.post(
'/cd/start/:jobId',
asyncHandler(async (req, res) => {
const jobId = Number(req.params.jobId);
const ripConfig = req.body || {};
logger.info('post:cd:start', {
reqId: req.reqId,
jobId,
format: ripConfig.format,
selectedPreEncodeScriptIdsCount: Array.isArray(ripConfig?.selectedPreEncodeScriptIds)
? ripConfig.selectedPreEncodeScriptIds.length
: 0,
selectedPostEncodeScriptIdsCount: Array.isArray(ripConfig?.selectedPostEncodeScriptIds)
? ripConfig.selectedPostEncodeScriptIds.length
: 0,
selectedPreEncodeChainIdsCount: Array.isArray(ripConfig?.selectedPreEncodeChainIds)
? ripConfig.selectedPreEncodeChainIds.length
: 0,
selectedPostEncodeChainIdsCount: Array.isArray(ripConfig?.selectedPostEncodeChainIds)
? ripConfig.selectedPostEncodeChainIds.length
: 0
});
const result = await pipelineService.enqueueOrStartCdAction(
jobId,
ripConfig,
() => pipelineService.startCdRip(jobId, ripConfig)
);
res.json({ result });
})
);
router.post(
'/select-metadata',
asyncHandler(async (req, res) => {
const { jobId, title, year, imdbId, poster, fromOmdb, selectedPlaylist } = req.body;
if (!jobId) {
const error = new Error('jobId fehlt.');
error.statusCode = 400;
throw error;
}
logger.info('post:select-metadata', {
reqId: req.reqId,
jobId,
title,
year,
imdbId,
poster,
fromOmdb,
selectedPlaylist
});
const job = await pipelineService.selectMetadata({
jobId: Number(jobId),
title,
year,
imdbId,
poster,
fromOmdb,
selectedPlaylist
});
res.json({ job });
})
);
router.post(
'/start/:jobId',
asyncHandler(async (req, res) => {
const jobId = Number(req.params.jobId);
logger.info('post:start-job', { reqId: req.reqId, jobId });
const result = await pipelineService.startPreparedJob(jobId);
res.json({ result });
})
);
router.post(
'/confirm-encode/:jobId',
asyncHandler(async (req, res) => {
const jobId = Number(req.params.jobId);
const selectedEncodeTitleId = req.body?.selectedEncodeTitleId ?? null;
const selectedTrackSelection = req.body?.selectedTrackSelection ?? null;
const selectedPostEncodeScriptIds = req.body?.selectedPostEncodeScriptIds;
const selectedPreEncodeScriptIds = req.body?.selectedPreEncodeScriptIds;
const selectedPostEncodeChainIds = req.body?.selectedPostEncodeChainIds;
const selectedPreEncodeChainIds = req.body?.selectedPreEncodeChainIds;
const skipPipelineStateUpdate = Boolean(req.body?.skipPipelineStateUpdate);
const selectedUserPresetId = req.body?.selectedUserPresetId ?? null;
logger.info('post:confirm-encode', {
reqId: req.reqId,
jobId,
selectedEncodeTitleId,
selectedTrackSelectionProvided: Boolean(selectedTrackSelection),
skipPipelineStateUpdate,
selectedUserPresetId,
selectedPostEncodeScriptIdsCount: Array.isArray(selectedPostEncodeScriptIds)
? selectedPostEncodeScriptIds.length
: 0,
selectedPreEncodeScriptIdsCount: Array.isArray(selectedPreEncodeScriptIds)
? selectedPreEncodeScriptIds.length
: 0,
selectedPostEncodeChainIdsCount: Array.isArray(selectedPostEncodeChainIds)
? selectedPostEncodeChainIds.length
: 0,
selectedPreEncodeChainIdsCount: Array.isArray(selectedPreEncodeChainIds)
? selectedPreEncodeChainIds.length
: 0
});
const job = await pipelineService.confirmEncodeReview(jobId, {
selectedEncodeTitleId,
selectedTrackSelection,
selectedPostEncodeScriptIds,
selectedPreEncodeScriptIds,
selectedPostEncodeChainIds,
selectedPreEncodeChainIds,
skipPipelineStateUpdate,
selectedUserPresetId
});
res.json({ job });
})
);
router.post(
'/cancel',
asyncHandler(async (req, res) => {
const rawJobId = req.body?.jobId;
const jobId = rawJobId === null || rawJobId === undefined || String(rawJobId).trim() === ''
? null
: Number(rawJobId);
logger.warn('post:cancel', { reqId: req.reqId, jobId });
const result = await pipelineService.cancel(jobId);
res.json({ result });
})
);
router.post(
'/retry/:jobId',
asyncHandler(async (req, res) => {
const jobId = Number(req.params.jobId);
logger.info('post:retry', { reqId: req.reqId, jobId });
const result = await pipelineService.retry(jobId);
res.json({ result });
})
);
router.post(
'/resume-ready/:jobId',
asyncHandler(async (req, res) => {
const jobId = Number(req.params.jobId);
logger.info('post:resume-ready', { reqId: req.reqId, jobId });
const job = await pipelineService.resumeReadyToEncodeJob(jobId);
res.json({ job });
})
);
router.post(
'/reencode/:jobId',
asyncHandler(async (req, res) => {
const jobId = Number(req.params.jobId);
logger.info('post:reencode', { reqId: req.reqId, jobId });
const result = await pipelineService.reencodeFromRaw(jobId);
res.json({ result });
})
);
router.post(
'/restart-review/:jobId',
asyncHandler(async (req, res) => {
const jobId = Number(req.params.jobId);
logger.info('post:restart-review', { reqId: req.reqId, jobId });
const result = await pipelineService.restartReviewFromRaw(jobId);
res.json({ result });
})
);
router.post(
'/restart-encode/:jobId',
asyncHandler(async (req, res) => {
const jobId = Number(req.params.jobId);
logger.info('post:restart-encode', { reqId: req.reqId, jobId });
const result = await pipelineService.restartEncodeWithLastSettings(jobId);
res.json({ result });
})
);
router.get(
'/queue',
asyncHandler(async (req, res) => {
logger.debug('get:queue', { reqId: req.reqId });
const queue = await pipelineService.getQueueSnapshot();
res.json({ queue });
})
);
router.post(
'/queue/reorder',
asyncHandler(async (req, res) => {
// Accept orderedEntryIds (new) or orderedJobIds (legacy fallback for job-only queues).
const orderedEntryIds = Array.isArray(req.body?.orderedEntryIds)
? req.body.orderedEntryIds
: (Array.isArray(req.body?.orderedJobIds) ? req.body.orderedJobIds : []);
logger.info('post:queue:reorder', { reqId: req.reqId, orderedEntryIds });
const queue = await pipelineService.reorderQueue(orderedEntryIds);
res.json({ queue });
})
);
router.post(
'/queue/entry',
asyncHandler(async (req, res) => {
const { type, scriptId, chainId, waitSeconds, insertAfterEntryId } = req.body || {};
logger.info('post:queue:entry', { reqId: req.reqId, type });
const result = await pipelineService.enqueueNonJobEntry(
type,
{ scriptId, chainId, waitSeconds },
insertAfterEntryId ?? null
);
const queue = await pipelineService.getQueueSnapshot();
res.json({ result, queue });
})
);
router.delete(
'/queue/entry/:entryId',
asyncHandler(async (req, res) => {
const entryId = req.params.entryId;
logger.info('delete:queue:entry', { reqId: req.reqId, entryId });
const queue = await pipelineService.removeQueueEntry(entryId);
res.json({ queue });
})
);
module.exports = router;