Initial commit mit MkDocs-Dokumentation
This commit is contained in:
154
backend/src/routes/historyRoutes.js
Normal file
154
backend/src/routes/historyRoutes.js
Normal file
@@ -0,0 +1,154 @@
|
||||
const express = require('express');
|
||||
const asyncHandler = require('../middleware/asyncHandler');
|
||||
const historyService = require('../services/historyService');
|
||||
const pipelineService = require('../services/pipelineService');
|
||||
const logger = require('../services/logger').child('HISTORY_ROUTE');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req, res) => {
|
||||
logger.info('get:jobs', {
|
||||
reqId: req.reqId,
|
||||
status: req.query.status,
|
||||
search: req.query.search
|
||||
});
|
||||
|
||||
const jobs = await historyService.getJobs({
|
||||
status: req.query.status,
|
||||
search: req.query.search
|
||||
});
|
||||
|
||||
res.json({ jobs });
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/database',
|
||||
asyncHandler(async (req, res) => {
|
||||
logger.info('get:database', {
|
||||
reqId: req.reqId,
|
||||
status: req.query.status,
|
||||
search: req.query.search
|
||||
});
|
||||
|
||||
const rows = await historyService.getDatabaseRows({
|
||||
status: req.query.status,
|
||||
search: req.query.search
|
||||
});
|
||||
|
||||
res.json({ rows });
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/orphan-raw',
|
||||
asyncHandler(async (req, res) => {
|
||||
logger.info('get:orphan-raw', { reqId: req.reqId });
|
||||
const result = await historyService.getOrphanRawFolders();
|
||||
res.json(result);
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/orphan-raw/import',
|
||||
asyncHandler(async (req, res) => {
|
||||
const rawPath = String(req.body?.rawPath || '').trim();
|
||||
logger.info('post:orphan-raw:import', { reqId: req.reqId, rawPath });
|
||||
const job = await historyService.importOrphanRawFolder(rawPath);
|
||||
const uiReset = await pipelineService.resetFrontendState('history_orphan_import');
|
||||
res.json({ job, uiReset });
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/:id/omdb/assign',
|
||||
asyncHandler(async (req, res) => {
|
||||
const id = Number(req.params.id);
|
||||
const payload = req.body || {};
|
||||
logger.info('post:job:omdb:assign', {
|
||||
reqId: req.reqId,
|
||||
id,
|
||||
imdbId: payload?.imdbId || null,
|
||||
hasTitle: Boolean(payload?.title),
|
||||
hasYear: Boolean(payload?.year)
|
||||
});
|
||||
|
||||
const job = await historyService.assignOmdbMetadata(id, payload);
|
||||
res.json({ job });
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/:id/delete-files',
|
||||
asyncHandler(async (req, res) => {
|
||||
const id = Number(req.params.id);
|
||||
const target = String(req.body?.target || 'both');
|
||||
|
||||
logger.warn('post:delete-files', {
|
||||
reqId: req.reqId,
|
||||
id,
|
||||
target
|
||||
});
|
||||
|
||||
const result = await historyService.deleteJobFiles(id, target);
|
||||
res.json(result);
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/:id/delete',
|
||||
asyncHandler(async (req, res) => {
|
||||
const id = Number(req.params.id);
|
||||
const target = String(req.body?.target || 'none');
|
||||
|
||||
logger.warn('post:delete-job', {
|
||||
reqId: req.reqId,
|
||||
id,
|
||||
target
|
||||
});
|
||||
|
||||
const result = await historyService.deleteJob(id, target);
|
||||
const uiReset = await pipelineService.resetFrontendState('history_delete');
|
||||
res.json({ ...result, uiReset });
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:id',
|
||||
asyncHandler(async (req, res) => {
|
||||
const id = Number(req.params.id);
|
||||
const includeLiveLog = ['1', 'true', 'yes'].includes(String(req.query.includeLiveLog || '').toLowerCase());
|
||||
const includeLogs = ['1', 'true', 'yes'].includes(String(req.query.includeLogs || '').toLowerCase());
|
||||
const includeAllLogs = ['1', 'true', 'yes'].includes(String(req.query.includeAllLogs || '').toLowerCase());
|
||||
const parsedTail = Number(req.query.logTailLines);
|
||||
const logTailLines = Number.isFinite(parsedTail) && parsedTail > 0
|
||||
? Math.trunc(parsedTail)
|
||||
: null;
|
||||
|
||||
logger.info('get:job-detail', {
|
||||
reqId: req.reqId,
|
||||
id,
|
||||
includeLiveLog,
|
||||
includeLogs,
|
||||
includeAllLogs,
|
||||
logTailLines
|
||||
});
|
||||
const job = await historyService.getJobWithLogs(id, {
|
||||
includeLiveLog,
|
||||
includeLogs,
|
||||
includeAllLogs,
|
||||
logTailLines
|
||||
});
|
||||
if (!job) {
|
||||
const error = new Error('Job nicht gefunden.');
|
||||
error.statusCode = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
res.json({ job });
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
160
backend/src/routes/pipelineRoutes.js
Normal file
160
backend/src/routes/pipelineRoutes.js
Normal file
@@ -0,0 +1,160 @@
|
||||
const express = require('express');
|
||||
const asyncHandler = require('../middleware/asyncHandler');
|
||||
const pipelineService = require('../services/pipelineService');
|
||||
const diskDetectionService = require('../services/diskDetectionService');
|
||||
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() });
|
||||
})
|
||||
);
|
||||
|
||||
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.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;
|
||||
logger.info('post:confirm-encode', {
|
||||
reqId: req.reqId,
|
||||
jobId,
|
||||
selectedEncodeTitleId,
|
||||
selectedTrackSelectionProvided: Boolean(selectedTrackSelection)
|
||||
});
|
||||
const job = await pipelineService.confirmEncodeReview(jobId, {
|
||||
selectedEncodeTitleId,
|
||||
selectedTrackSelection
|
||||
});
|
||||
res.json({ job });
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/cancel',
|
||||
asyncHandler(async (req, res) => {
|
||||
logger.warn('post:cancel', { reqId: req.reqId });
|
||||
await pipelineService.cancel();
|
||||
res.json({ ok: true });
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/retry/:jobId',
|
||||
asyncHandler(async (req, res) => {
|
||||
const jobId = Number(req.params.jobId);
|
||||
logger.info('post:retry', { reqId: req.reqId, jobId });
|
||||
await pipelineService.retry(jobId);
|
||||
res.json({ ok: true });
|
||||
})
|
||||
);
|
||||
|
||||
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-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 });
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
128
backend/src/routes/settingsRoutes.js
Normal file
128
backend/src/routes/settingsRoutes.js
Normal file
@@ -0,0 +1,128 @@
|
||||
const express = require('express');
|
||||
const asyncHandler = require('../middleware/asyncHandler');
|
||||
const settingsService = require('../services/settingsService');
|
||||
const notificationService = require('../services/notificationService');
|
||||
const pipelineService = require('../services/pipelineService');
|
||||
const wsService = require('../services/websocketService');
|
||||
const logger = require('../services/logger').child('SETTINGS_ROUTE');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
function isSensitiveSettingKey(key) {
|
||||
const normalized = String(key || '').trim().toLowerCase();
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
return /(token|password|secret|api_key|registration_key|pushover_user)/i.test(normalized);
|
||||
}
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req, res) => {
|
||||
logger.debug('get:settings', { reqId: req.reqId });
|
||||
const categories = await settingsService.getCategorizedSettings();
|
||||
res.json({ categories });
|
||||
})
|
||||
);
|
||||
|
||||
router.put(
|
||||
'/:key',
|
||||
asyncHandler(async (req, res) => {
|
||||
const { key } = req.params;
|
||||
const { value } = req.body;
|
||||
|
||||
logger.info('put:setting', {
|
||||
reqId: req.reqId,
|
||||
key,
|
||||
value: isSensitiveSettingKey(key) ? '[redacted]' : value
|
||||
});
|
||||
const updated = await settingsService.setSettingValue(key, value);
|
||||
let reviewRefresh = null;
|
||||
try {
|
||||
reviewRefresh = await pipelineService.refreshEncodeReviewAfterSettingsSave([key]);
|
||||
if (reviewRefresh?.triggered) {
|
||||
logger.info('put:setting:review-refresh-started', {
|
||||
reqId: req.reqId,
|
||||
key,
|
||||
jobId: reviewRefresh.jobId
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn('put:setting:review-refresh-failed', {
|
||||
reqId: req.reqId,
|
||||
key,
|
||||
error: {
|
||||
name: error?.name,
|
||||
message: error?.message
|
||||
}
|
||||
});
|
||||
reviewRefresh = {
|
||||
triggered: false,
|
||||
reason: 'refresh_error',
|
||||
message: error?.message || 'unknown'
|
||||
};
|
||||
}
|
||||
wsService.broadcast('SETTINGS_UPDATED', updated);
|
||||
|
||||
res.json({ setting: updated, reviewRefresh });
|
||||
})
|
||||
);
|
||||
|
||||
router.put(
|
||||
'/',
|
||||
asyncHandler(async (req, res) => {
|
||||
const { settings } = req.body || {};
|
||||
if (!settings || typeof settings !== 'object' || Array.isArray(settings)) {
|
||||
const error = new Error('settings fehlt oder ist ungültig.');
|
||||
error.statusCode = 400;
|
||||
throw error;
|
||||
}
|
||||
|
||||
logger.info('put:settings:bulk', { reqId: req.reqId, count: Object.keys(settings).length });
|
||||
const changes = await settingsService.setSettingsBulk(settings);
|
||||
let reviewRefresh = null;
|
||||
try {
|
||||
reviewRefresh = await pipelineService.refreshEncodeReviewAfterSettingsSave(changes.map((item) => item.key));
|
||||
if (reviewRefresh?.triggered) {
|
||||
logger.info('put:settings:bulk:review-refresh-started', {
|
||||
reqId: req.reqId,
|
||||
jobId: reviewRefresh.jobId,
|
||||
relevantKeys: reviewRefresh.relevantKeys
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn('put:settings:bulk:review-refresh-failed', {
|
||||
reqId: req.reqId,
|
||||
error: {
|
||||
name: error?.name,
|
||||
message: error?.message
|
||||
}
|
||||
});
|
||||
reviewRefresh = {
|
||||
triggered: false,
|
||||
reason: 'refresh_error',
|
||||
message: error?.message || 'unknown'
|
||||
};
|
||||
}
|
||||
wsService.broadcast('SETTINGS_BULK_UPDATED', { count: changes.length, keys: changes.map((item) => item.key) });
|
||||
|
||||
res.json({ changes, reviewRefresh });
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/pushover/test',
|
||||
asyncHandler(async (req, res) => {
|
||||
const title = req.body?.title;
|
||||
const message = req.body?.message;
|
||||
logger.info('post:pushover:test', {
|
||||
reqId: req.reqId,
|
||||
hasTitle: Boolean(title),
|
||||
hasMessage: Boolean(message)
|
||||
});
|
||||
const result = await notificationService.sendTest({ title, message });
|
||||
res.json({ result });
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user