feat: awesome index 全功能实现

- M1-M2: 数据层与核心API(DuckDB, entries/tags/comments/search服务)
- M3: SSR页面(EJS模板,接真实数据)
- M4: 管理后台(登录/用户/内容源/AI ingest)
- M5: 采集层(scheduler+rss/html/eryajf适配器)
- M6: MCP只读接口(Streamable HTTP, 4工具)
- M7: Docker部署(Dockerfile+docker-compose)
- 测试: 10个用例全部通过
- 修复: DuckDB连接参数绑定、状态机迁移、标签树操作
This commit is contained in:
cheney
2026-08-28 09:32:48 +08:00
parent c47bb15d8c
commit dec103546e
51 changed files with 7298 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
import * as sourceService from "../services/source.service.js";
import { HttpError } from "./errors.js";
export function requireLogin(req, res, next) {
if (!req.session.userId) {
throw new HttpError(401, "unauthorized", "请先登录");
}
next();
}
export function requireAdmin(req, res, next) {
if (!req.session.userId) {
throw new HttpError(401, "unauthorized", "请先登录");
}
if (req.session.role !== "admin") {
throw new HttpError(403, "forbidden", "需要管理员权限");
}
next();
}
export function loadUser(req, res, next) {
res.locals.user = req.session.userId
? { id: req.session.userId, username: req.session.username, role: req.session.role }
: null;
next();
}
export async function aiSourceFromRequest(req) {
const key = req.get("x-ingest-key") || "";
const source = await sourceService.findByApiKey(key);
if (!source) {
throw new HttpError(403, "invalid_key", "AI 录入密钥无效或未启用");
}
return source;
}
+24
View File
@@ -0,0 +1,24 @@
export class HttpError extends Error {
constructor(status, code, message) {
super(message);
this.status = status;
this.code = code;
}
}
export function notFoundHandler(req, res) {
if (req.path.startsWith("/api/")) {
return res.status(404).json({ ok: false, error: { code: "not_found", message: "接口不存在" } });
}
res.status(404).send("404 Not Found");
}
export function errorHandler(err, req, res, _next) {
const status = err.status || 500;
if (status >= 500) console.error(err);
const message = err.message || "服务器内部错误";
if (req.path.startsWith("/api/") || req.path === "/mcp") {
return res.status(status).json({ ok: false, error: { code: err.code || "error", message } });
}
res.status(status).send(message);
}