219 lines
8.5 KiB
JavaScript
219 lines
8.5 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.docsRouter = void 0;
|
|
const express_1 = require("express");
|
|
const auth_middleware_1 = require("../../middleware/auth.middleware");
|
|
const rbac_middleware_1 = require("../../middleware/rbac.middleware");
|
|
const env_1 = require("../../config/env");
|
|
const logger_1 = require("../../utils/logger");
|
|
const health_check_1 = require("../../utils/health-check");
|
|
const metrics_1 = require("../../utils/metrics");
|
|
const docs_files_service_1 = require("./docs-files.service");
|
|
const mkdocs_config_service_1 = require("./mkdocs-config.service");
|
|
const router = (0, express_1.Router)();
|
|
router.use(auth_middleware_1.authenticate);
|
|
router.use(rbac_middleware_1.requireNonTemp);
|
|
// Removed duplicated isServiceOnline - now using shared utility from utils/health-check.ts
|
|
// GET /api/docs/status — check MkDocs and Code Server availability
|
|
router.get('/status', async (_req, res, next) => {
|
|
try {
|
|
const [mkdocsOnline, codeServerOnline, siteServerOnline] = await Promise.all([
|
|
(0, health_check_1.isServiceOnline)(env_1.env.MKDOCS_PREVIEW_URL),
|
|
(0, health_check_1.isServiceOnline)(env_1.env.CODE_SERVER_URL),
|
|
(0, health_check_1.isServiceOnline)(env_1.env.MKDOCS_SITE_SERVER_URL),
|
|
]);
|
|
res.json({
|
|
mkdocs: { online: mkdocsOnline, url: env_1.env.MKDOCS_PREVIEW_URL },
|
|
codeServer: { online: codeServerOnline, url: env_1.env.CODE_SERVER_URL },
|
|
siteServer: { online: siteServerOnline, url: env_1.env.MKDOCS_SITE_SERVER_URL },
|
|
});
|
|
}
|
|
catch (err) {
|
|
logger_1.logger.error('Failed to check docs status', err);
|
|
next(err);
|
|
}
|
|
});
|
|
// GET /api/docs/config — return public-facing port numbers for iframe URLs
|
|
router.get('/config', async (_req, res, _next) => {
|
|
res.json({
|
|
codeServerPort: env_1.env.CODE_SERVER_PORT,
|
|
mkdocsPort: env_1.env.MKDOCS_PORT,
|
|
mkdocsSitePort: env_1.env.MKDOCS_SITE_SERVER_PORT,
|
|
});
|
|
});
|
|
// --- MkDocs Config Endpoints ---
|
|
// GET /api/docs/mkdocs-config — read raw mkdocs.yml content
|
|
router.get('/mkdocs-config', async (_req, res, next) => {
|
|
try {
|
|
const content = await mkdocs_config_service_1.mkdocsConfigService.readConfig();
|
|
res.json({ content });
|
|
}
|
|
catch (err) {
|
|
logger_1.logger.error('Failed to read mkdocs config', err);
|
|
next(err);
|
|
}
|
|
});
|
|
// PUT /api/docs/mkdocs-config — validate + write mkdocs.yml (SUPER_ADMIN only)
|
|
router.put('/mkdocs-config', (0, rbac_middleware_1.requireRole)('SUPER_ADMIN'), async (req, res, next) => {
|
|
try {
|
|
const { content } = req.body;
|
|
if (typeof content !== 'string') {
|
|
res.status(400).json({ error: { message: 'Content string required', code: 'VALIDATION_ERROR' } });
|
|
return;
|
|
}
|
|
await mkdocs_config_service_1.mkdocsConfigService.writeConfig(content);
|
|
res.json({ success: true });
|
|
}
|
|
catch (err) {
|
|
if (err.message?.startsWith('Invalid YAML')) {
|
|
res.status(400).json({ error: { message: err.message, code: 'VALIDATION_ERROR' } });
|
|
return;
|
|
}
|
|
logger_1.logger.error('Failed to write mkdocs config', err);
|
|
next(err);
|
|
}
|
|
});
|
|
// POST /api/docs/build — trigger mkdocs build in container (SUPER_ADMIN only)
|
|
router.post('/build', (0, rbac_middleware_1.requireRole)('SUPER_ADMIN'), async (_req, res, next) => {
|
|
try {
|
|
const result = await mkdocs_config_service_1.mkdocsConfigService.triggerBuild();
|
|
res.json(result);
|
|
}
|
|
catch (err) {
|
|
logger_1.logger.error('MkDocs build failed', err);
|
|
next(err);
|
|
}
|
|
});
|
|
// --- File Management Endpoints ---
|
|
// GET /api/docs/files — list file tree
|
|
router.get('/files', async (_req, res, next) => {
|
|
try {
|
|
metrics_1.cm_docs_operations.inc({ operation: 'list' });
|
|
const tree = await docs_files_service_1.docsFilesService.listTree();
|
|
res.json(tree);
|
|
}
|
|
catch (err) {
|
|
logger_1.logger.error('Failed to list docs files', err);
|
|
next(err);
|
|
}
|
|
});
|
|
// POST /api/docs/files/rename — rename/move file
|
|
router.post('/files/rename', async (req, res, next) => {
|
|
try {
|
|
metrics_1.cm_docs_operations.inc({ operation: 'rename' });
|
|
const { from, to } = req.body;
|
|
if (!from || !to) {
|
|
res.status(400).json({ error: { message: 'Both "from" and "to" paths are required', code: 'VALIDATION_ERROR' } });
|
|
return;
|
|
}
|
|
await docs_files_service_1.docsFilesService.renameFile(from, to);
|
|
res.json({ success: true });
|
|
}
|
|
catch (err) {
|
|
handleFileError(err, res, next);
|
|
}
|
|
});
|
|
// GET /api/docs/files/* — read file content
|
|
router.get('/files/*', async (req, res, next) => {
|
|
try {
|
|
metrics_1.cm_docs_operations.inc({ operation: 'read' });
|
|
const filePath = extractWildcardPath(req);
|
|
if (!filePath) {
|
|
res.status(400).json({ error: { message: 'File path required', code: 'VALIDATION_ERROR' } });
|
|
return;
|
|
}
|
|
const content = await docs_files_service_1.docsFilesService.readFileContent(filePath);
|
|
res.json({ path: filePath, content });
|
|
}
|
|
catch (err) {
|
|
handleFileError(err, res, next);
|
|
}
|
|
});
|
|
// PUT /api/docs/files/* — write/update file content
|
|
router.put('/files/*', async (req, res, next) => {
|
|
try {
|
|
metrics_1.cm_docs_operations.inc({ operation: 'write' });
|
|
const filePath = extractWildcardPath(req);
|
|
if (!filePath) {
|
|
res.status(400).json({ error: { message: 'File path required', code: 'VALIDATION_ERROR' } });
|
|
return;
|
|
}
|
|
const { content } = req.body;
|
|
if (typeof content !== 'string') {
|
|
res.status(400).json({ error: { message: 'Content string required', code: 'VALIDATION_ERROR' } });
|
|
return;
|
|
}
|
|
await docs_files_service_1.docsFilesService.writeFileContent(filePath, content);
|
|
res.json({ success: true, path: filePath });
|
|
}
|
|
catch (err) {
|
|
handleFileError(err, res, next);
|
|
}
|
|
});
|
|
// POST /api/docs/files/* — create new file or folder
|
|
router.post('/files/*', async (req, res, next) => {
|
|
try {
|
|
metrics_1.cm_docs_operations.inc({ operation: 'create' });
|
|
const filePath = extractWildcardPath(req);
|
|
if (!filePath) {
|
|
res.status(400).json({ error: { message: 'File path required', code: 'VALIDATION_ERROR' } });
|
|
return;
|
|
}
|
|
const { content, isDirectory } = req.body;
|
|
await docs_files_service_1.docsFilesService.createFile(filePath, content, isDirectory);
|
|
res.status(201).json({ success: true, path: filePath });
|
|
}
|
|
catch (err) {
|
|
handleFileError(err, res, next);
|
|
}
|
|
});
|
|
// DELETE /api/docs/files/* — delete file or empty folder
|
|
router.delete('/files/*', async (req, res, next) => {
|
|
try {
|
|
metrics_1.cm_docs_operations.inc({ operation: 'delete' });
|
|
const filePath = extractWildcardPath(req);
|
|
if (!filePath) {
|
|
res.status(400).json({ error: { message: 'File path required', code: 'VALIDATION_ERROR' } });
|
|
return;
|
|
}
|
|
await docs_files_service_1.docsFilesService.deleteFile(filePath);
|
|
res.json({ success: true });
|
|
}
|
|
catch (err) {
|
|
handleFileError(err, res, next);
|
|
}
|
|
});
|
|
/**
|
|
* Extract the wildcard path from Express 5 req.params.
|
|
* Express 5 uses params[0] for * routes.
|
|
*/
|
|
function extractWildcardPath(req) {
|
|
// Express 5: req.params is array-like for * routes
|
|
const params = req.params;
|
|
const wildcardParam = params[0] || params['0'];
|
|
if (Array.isArray(wildcardParam))
|
|
return wildcardParam.join('/');
|
|
return wildcardParam || '';
|
|
}
|
|
function handleFileError(err, res, next) {
|
|
if (err instanceof docs_files_service_1.PathTraversalError) {
|
|
res.status(403).json({ error: { message: 'Path traversal not allowed', code: 'FORBIDDEN' } });
|
|
return;
|
|
}
|
|
if (err instanceof docs_files_service_1.FileNotFoundError) {
|
|
res.status(404).json({ error: { message: err.message, code: 'NOT_FOUND' } });
|
|
return;
|
|
}
|
|
if (err.message?.startsWith('Already exists')) {
|
|
res.status(409).json({ error: { message: err.message, code: 'CONFLICT' } });
|
|
return;
|
|
}
|
|
if (err.message === 'Directory is not empty') {
|
|
res.status(400).json({ error: { message: 'Directory is not empty', code: 'VALIDATION_ERROR' } });
|
|
return;
|
|
}
|
|
logger_1.logger.error('Docs file operation failed', err);
|
|
next(err);
|
|
}
|
|
exports.docsRouter = router;
|
|
//# sourceMappingURL=docs.routes.js.map
|