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