管理台: - 内容管理新增名称搜索(title/slug/url/description),状态与翻页链接保留搜索词 - 菜单改为真实链接,每次点击重新向后台取数渲染分区,不再依赖手动刷新 - 内容管理每页固定 20 条 - pending 条目按钮改为「预览」,active 为「查看」 评论体系: - 评论提交新增必填联系方式(手机号/邮箱),前端不做展示 - 首次评论以「昵称+联系方式」建档,之后需匹配且未被禁用 - 后台评论管理新增联系方式列;新增评论人员管理页,可启用/禁用/改名/删除 - 评论昵称输入框不再预置 admin 相关项目: - 新增 entry_relations 双向关联,详情页侧栏展示并写入机读 JSON - 编辑页可添加/移除关联 内容源自动获取: - 修复 rss 适配器把 feed url 当成条目 url 的根本问题,支持 Atom link/href 与相对链接 - eryajf-weekly 限定正文容器取链接,修正项目说明的提取与「(opens new window)」清理 - pipeline 增加轮内去重与单条失败隔离;scheduler 对非法 cron 明确告警 MCP: - 重写 instructions 为八节说明,新增 usage 工具返回结构化接入文档 - 补全各工具的参数、用途与返回格式说明;baseUrl 取自当次请求 - 首页 MCP 区块与 README 同步补充写入示例与踩坑清单 测试: 新增 comments/relations/admin/ingest-adapters 用例,共 29 个全部通过
57 lines
1.9 KiB
JavaScript
57 lines
1.9 KiB
JavaScript
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { connect, close } from "../src/db/connection.js";
|
|
import { runMigrate } from "../src/db/migrate.js";
|
|
import { buildApp } from "../src/app.js";
|
|
|
|
export async function withApp(fn, { aiIngestKey = "", adminInitPassword = "test123" } = {}) {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "awesome-test-"));
|
|
await connect(path.join(dir, "test.duckdb"));
|
|
await runMigrate({ adminInitPassword, aiIngestKey });
|
|
const app = buildApp({
|
|
sessionSecret: "test-secret",
|
|
duckdbPath: path.join(dir, "test.duckdb"),
|
|
baseUrl: "http://test.local",
|
|
});
|
|
const server = app.listen(0);
|
|
await new Promise((r) => server.once("listening", r));
|
|
const base = `http://127.0.0.1:${server.address().port}`;
|
|
|
|
async function api(pathname, { method = "GET", body, cookie, headers } = {}) {
|
|
const res = await fetch(base + pathname, {
|
|
method,
|
|
headers: {
|
|
...(body ? { "content-type": "application/json" } : {}),
|
|
...(cookie ? { cookie } : {}),
|
|
...(headers || {}),
|
|
},
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
});
|
|
let json = null;
|
|
try {
|
|
json = await res.json();
|
|
} catch {}
|
|
return { status: res.status, json, headers: res.headers };
|
|
}
|
|
|
|
async function loginAdmin() {
|
|
const res = await fetch(base + "/api/admin/login", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({ username: "admin", password: adminInitPassword }),
|
|
});
|
|
const cookies = res.headers.getSetCookie().map((c) => c.split(";")[0]);
|
|
return cookies.join("; ");
|
|
}
|
|
|
|
try {
|
|
await fn({ base, api, loginAdmin });
|
|
} finally {
|
|
server.closeAllConnections?.();
|
|
await new Promise((r) => server.close(r));
|
|
await close();
|
|
fs.rmSync(dir, { recursive: true, force: true, maxRetries: 3 });
|
|
}
|
|
}
|