Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

264 lines
10 KiB
JavaScript

/**
* ═══════════════════════════════════════════════════════════
* 智能小说系统路由 · /api/novel
* ═══════════════════════════════════════════════════════════
*
* ZY-PROJ-004 · ZY-SVR-006
* 守护: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*
* 端点:
* — 小说项目 —
* GET / — 列出小说
* POST / — 创建小说
* GET /:novelId — 小说详情
* PUT /:novelId — 更新小说
* DELETE /:novelId — 删除小说
*
* — 章节 —
* GET /:novelId/chapters — 章节列表
* POST /:novelId/chapters — 添加章节
* GET /:novelId/chapters/:chapterId — 章节详情
* PUT /:novelId/chapters/:chapterId — 更新章节
* DELETE /:novelId/chapters/:chapterId — 删除章节
*
* — 人物卡 —
* GET /:novelId/characters — 人物列表
* POST /:novelId/characters — 添加人物
* PUT /:novelId/characters/:charId — 更新人物
*
* — 大纲 —
* GET /:novelId/outline — 获取大纲树
* POST /:novelId/outline — 添加大纲节点
* PUT /:novelId/outline/:nodeId — 更新大纲节点
*
* — AI辅助 —
* POST /:novelId/ai/continue/:chapterId — 续写
* POST /:novelId/ai/rewrite/:chapterId — 改写
* POST /:novelId/ai/suggest — 情节建议
*
* — 统计 —
* GET /stats — 系统统计
*
* ═══════════════════════════════════════════════════════════
*/
'use strict';
const express = require('express');
const router = express.Router();
const engine = require('../services/novel-engine');
// ══════════════════════════════════════════
// 小说 CRUD
// ══════════════════════════════════════════
/* ─── GET /stats (放在 /:novelId 前面) ─── */
router.get('/stats', (req, res) => {
res.json({ error: false, data: engine.getStats() });
});
/* ─── GET / — 列表 ─── */
router.get('/', (req, res) => {
const { author, status, limit } = req.query;
const novels = engine.listNovels({
author, status,
limit: limit ? parseInt(limit) : undefined
});
// 返回摘要(不含完整章节内容)
const summaries = novels.map(n => ({
novel_id: n.novel_id,
title: n.title,
author: n.author,
genre: n.genre,
synopsis: n.synopsis,
status: n.status,
chapter_count: n.chapters.length,
character_count: n.characters.length,
word_count: n.word_count,
created_at: n.created_at,
updated_at: n.updated_at
}));
res.json({ error: false, data: summaries, total: summaries.length });
});
/* ─── POST / — 创建 ─── */
router.post('/', (req, res) => {
try {
const { title, author, genre, synopsis, target_words } = req.body;
if (!title) return res.status(400).json({ error: true, code: 'MISSING_TITLE', message: 'title 为必填项' });
const novel = engine.createNovel({ title, author, genre, synopsis, target_words });
res.json({ error: false, data: novel });
} catch (err) {
res.status(400).json({ error: true, code: 'CREATE_FAILED', message: err.message });
}
});
/* ─── GET /:novelId — 详情 ─── */
router.get('/:novelId', (req, res) => {
const novel = engine.getNovel(req.params.novelId);
if (!novel) return res.status(404).json({ error: true, code: 'NOT_FOUND', message: '小说不存在' });
// 返回完整数据(含章节摘要+人物+大纲)
const data = {
...novel,
chapters: novel.chapters.map(c => ({
chapter_id: c.chapter_id,
title: c.title,
order: c.order,
word_count: c.word_count,
updated_at: c.updated_at
}))
};
res.json({ error: false, data });
});
/* ─── PUT /:novelId — 更新 ─── */
router.put('/:novelId', (req, res) => {
try {
const novel = engine.updateNovel(req.params.novelId, req.body);
res.json({ error: false, data: novel });
} catch (err) {
res.status(400).json({ error: true, code: 'UPDATE_FAILED', message: err.message });
}
});
/* ─── DELETE /:novelId — 删除 ─── */
router.delete('/:novelId', (req, res) => {
try {
const result = engine.deleteNovel(req.params.novelId);
res.json({ error: false, data: result });
} catch (err) {
res.status(400).json({ error: true, code: 'DELETE_FAILED', message: err.message });
}
});
// ══════════════════════════════════════════
// 章节
// ══════════════════════════════════════════
router.get('/:novelId/chapters', (req, res) => {
const chapters = engine.listChapters(req.params.novelId);
res.json({ error: false, data: chapters, total: chapters.length });
});
router.post('/:novelId/chapters', (req, res) => {
try {
const { title, content, order } = req.body;
const chapter = engine.addChapter(req.params.novelId, { title, content, order });
res.json({ error: false, data: chapter });
} catch (err) {
res.status(400).json({ error: true, code: 'ADD_CHAPTER_FAILED', message: err.message });
}
});
router.get('/:novelId/chapters/:chapterId', (req, res) => {
const chapter = engine.getChapter(req.params.novelId, req.params.chapterId);
if (!chapter) return res.status(404).json({ error: true, code: 'NOT_FOUND', message: '章节不存在' });
res.json({ error: false, data: chapter });
});
router.put('/:novelId/chapters/:chapterId', (req, res) => {
try {
const chapter = engine.updateChapter(req.params.novelId, req.params.chapterId, req.body);
res.json({ error: false, data: chapter });
} catch (err) {
res.status(400).json({ error: true, code: 'UPDATE_CHAPTER_FAILED', message: err.message });
}
});
router.delete('/:novelId/chapters/:chapterId', (req, res) => {
try {
const result = engine.deleteChapter(req.params.novelId, req.params.chapterId);
res.json({ error: false, data: result });
} catch (err) {
res.status(400).json({ error: true, code: 'DELETE_CHAPTER_FAILED', message: err.message });
}
});
// ══════════════════════════════════════════
// 人物卡
// ══════════════════════════════════════════
router.get('/:novelId/characters', (req, res) => {
const characters = engine.listCharacters(req.params.novelId);
res.json({ error: false, data: characters, total: characters.length });
});
router.post('/:novelId/characters', (req, res) => {
try {
const character = engine.addCharacter(req.params.novelId, req.body);
res.json({ error: false, data: character });
} catch (err) {
res.status(400).json({ error: true, code: 'ADD_CHARACTER_FAILED', message: err.message });
}
});
router.put('/:novelId/characters/:charId', (req, res) => {
try {
const character = engine.updateCharacter(req.params.novelId, req.params.charId, req.body);
res.json({ error: false, data: character });
} catch (err) {
res.status(400).json({ error: true, code: 'UPDATE_CHARACTER_FAILED', message: err.message });
}
});
// ══════════════════════════════════════════
// 大纲
// ══════════════════════════════════════════
router.get('/:novelId/outline', (req, res) => {
const outline = engine.getOutline(req.params.novelId);
res.json({ error: false, data: outline });
});
router.post('/:novelId/outline', (req, res) => {
try {
const node = engine.addOutlineNode(req.params.novelId, req.body);
res.json({ error: false, data: node });
} catch (err) {
res.status(400).json({ error: true, code: 'ADD_OUTLINE_FAILED', message: err.message });
}
});
router.put('/:novelId/outline/:nodeId', (req, res) => {
try {
const node = engine.updateOutlineNode(req.params.novelId, req.params.nodeId, req.body);
res.json({ error: false, data: node });
} catch (err) {
res.status(400).json({ error: true, code: 'UPDATE_OUTLINE_FAILED', message: err.message });
}
});
// ══════════════════════════════════════════
// AI 辅助
// ══════════════════════════════════════════
router.post('/:novelId/ai/continue/:chapterId', (req, res) => {
try {
const result = engine.aiContinue(req.params.novelId, req.params.chapterId, req.body);
res.json({ error: false, data: result });
} catch (err) {
res.status(400).json({ error: true, code: 'AI_CONTINUE_FAILED', message: err.message });
}
});
router.post('/:novelId/ai/rewrite/:chapterId', (req, res) => {
try {
const result = engine.aiRewrite(req.params.novelId, req.params.chapterId, req.body);
res.json({ error: false, data: result });
} catch (err) {
res.status(400).json({ error: true, code: 'AI_REWRITE_FAILED', message: err.message });
}
});
router.post('/:novelId/ai/suggest', (req, res) => {
try {
const result = engine.aiSuggest(req.params.novelId, req.body);
res.json({ error: false, data: result });
} catch (err) {
res.status(400).json({ error: true, code: 'AI_SUGGEST_FAILED', message: err.message });
}
});
module.exports = router;