1064 lines
29 KiB
TypeScript
1064 lines
29 KiB
TypeScript
import { Hono } from "hono";
|
|
import debug from "debug";
|
|
import type {
|
|
FileLibrary,
|
|
FileCategory,
|
|
KnowInfo,
|
|
ThemeSettings,
|
|
SystemSetting,
|
|
SystemSettingGroupData,
|
|
} from "../client/share/types.ts";
|
|
|
|
import {
|
|
EnableStatus,
|
|
DeleteStatus,
|
|
ThemeMode,
|
|
FontSize,
|
|
CompactMode,
|
|
} from "../client/share/types.ts";
|
|
|
|
import type { Variables, WithAuth } from "./app.tsx";
|
|
|
|
const log = {
|
|
api: debug("api:sys"),
|
|
};
|
|
|
|
// 创建知识库管理路由
|
|
export function createKnowInfoRoutes(withAuth: WithAuth) {
|
|
const knowInfoRoutes = new Hono<{ Variables: Variables }>();
|
|
|
|
// 获取知识库文章列表
|
|
knowInfoRoutes.get("/", withAuth, async (c) => {
|
|
try {
|
|
const apiClient = c.get('apiClient');
|
|
|
|
// 获取分页参数
|
|
const page = Number(c.req.query("page")) || 1;
|
|
const limit = Number(c.req.query("limit")) || 10;
|
|
const offset = (page - 1) * limit;
|
|
|
|
// 获取筛选参数
|
|
const title = c.req.query("title");
|
|
const category = c.req.query("category");
|
|
const tags = c.req.query("tags");
|
|
|
|
// 构建查询
|
|
let query = apiClient.database
|
|
.table("know_info")
|
|
.where("is_deleted", 0)
|
|
.orderBy("id", "desc");
|
|
|
|
// 应用筛选条件
|
|
if (title) {
|
|
query = query.where("title", "like", `%${title}%`);
|
|
}
|
|
|
|
if (category) {
|
|
query = query.where("category", category);
|
|
}
|
|
|
|
if (tags) {
|
|
query = query.where("tags", "like", `%${tags}%`);
|
|
}
|
|
|
|
// 克隆查询以获取总数
|
|
const countQuery = query.clone();
|
|
|
|
// 执行分页查询
|
|
const articles = await query.limit(limit).offset(offset);
|
|
|
|
// 获取总数
|
|
const count = await countQuery.count();
|
|
|
|
return c.json({
|
|
data: articles,
|
|
pagination: {
|
|
total: Number(count),
|
|
current: page,
|
|
pageSize: limit,
|
|
totalPages: Math.ceil(Number(count) / limit),
|
|
},
|
|
});
|
|
} catch (error) {
|
|
log.api("获取知识库文章列表失败:", error);
|
|
return c.json({ error: "获取知识库文章列表失败" }, 500);
|
|
}
|
|
});
|
|
|
|
// 获取单个知识库文章
|
|
knowInfoRoutes.get("/:id", withAuth, async (c) => {
|
|
try {
|
|
const id = Number(c.req.param("id"));
|
|
|
|
if (!id || isNaN(id)) {
|
|
return c.json({ error: "无效的文章ID" }, 400);
|
|
}
|
|
|
|
const apiClient = c.get('apiClient');
|
|
const [article] = await apiClient.database
|
|
.table("know_info")
|
|
.where({ id, is_deleted: 0 });
|
|
|
|
if (!article) {
|
|
return c.json({ error: "文章不存在" }, 404);
|
|
}
|
|
|
|
return c.json({message: '获取知识库文章详情成功', data: article});
|
|
} catch (error) {
|
|
log.api("获取知识库文章详情失败:", error);
|
|
return c.json({ error: "获取知识库文章详情失败" }, 500);
|
|
}
|
|
});
|
|
|
|
// 创建知识库文章
|
|
knowInfoRoutes.post("/", withAuth, async (c) => {
|
|
try {
|
|
const articleData = (await c.req.json()) as Partial<KnowInfo>;
|
|
|
|
// 验证必填字段
|
|
if (!articleData.title) {
|
|
return c.json({ error: "文章标题不能为空" }, 400);
|
|
}
|
|
|
|
// 如果作者为空,则使用当前用户的用户名
|
|
if (!articleData.author) {
|
|
const user = c.get("user");
|
|
articleData.author = user ? user.username : "unknown";
|
|
}
|
|
|
|
const apiClient = c.get('apiClient');
|
|
const [id] = await apiClient.database
|
|
.table("know_info")
|
|
.insert(articleData);
|
|
|
|
// 获取创建的文章
|
|
const [createdArticle] = await apiClient.database
|
|
.table("know_info")
|
|
.where("id", id);
|
|
|
|
return c.json({
|
|
message: "知识库文章创建成功",
|
|
data: createdArticle,
|
|
});
|
|
} catch (error) {
|
|
log.api("创建知识库文章失败:", error);
|
|
return c.json({ error: "创建知识库文章失败" }, 500);
|
|
}
|
|
});
|
|
|
|
// 更新知识库文章
|
|
knowInfoRoutes.put("/:id", withAuth, async (c) => {
|
|
try {
|
|
const id = Number(c.req.param("id"));
|
|
|
|
if (!id || isNaN(id)) {
|
|
return c.json({ error: "无效的文章ID" }, 400);
|
|
}
|
|
|
|
const articleData = (await c.req.json()) as Partial<KnowInfo>;
|
|
|
|
// 验证必填字段
|
|
if (!articleData.title) {
|
|
return c.json({ error: "文章标题不能为空" }, 400);
|
|
}
|
|
|
|
const apiClient = c.get('apiClient');
|
|
|
|
// 检查文章是否存在
|
|
const [existingArticle] = await apiClient.database
|
|
.table("know_info")
|
|
.where({ id, is_deleted: 0 });
|
|
|
|
if (!existingArticle) {
|
|
return c.json({ error: "文章不存在" }, 404);
|
|
}
|
|
|
|
// 更新文章
|
|
await apiClient.database
|
|
.table("know_info")
|
|
.where("id", id)
|
|
.update({
|
|
...articleData,
|
|
updated_at: apiClient.database.fn.now(),
|
|
});
|
|
|
|
// 获取更新后的文章
|
|
const [updatedArticle] = await apiClient.database
|
|
.table("know_info")
|
|
.where("id", id);
|
|
|
|
return c.json({
|
|
message: "知识库文章更新成功",
|
|
data: updatedArticle,
|
|
});
|
|
} catch (error) {
|
|
log.api("更新知识库文章失败:", error);
|
|
return c.json({ error: "更新知识库文章失败" }, 500);
|
|
}
|
|
});
|
|
|
|
// 删除知识库文章(软删除)
|
|
knowInfoRoutes.delete("/:id", withAuth, async (c) => {
|
|
try {
|
|
const id = Number(c.req.param("id"));
|
|
|
|
if (!id || isNaN(id)) {
|
|
return c.json({ error: "无效的文章ID" }, 400);
|
|
}
|
|
|
|
const apiClient = c.get('apiClient');
|
|
|
|
// 检查文章是否存在
|
|
const [existingArticle] = await apiClient.database
|
|
.table("know_info")
|
|
.where({ id, is_deleted: 0 });
|
|
|
|
if (!existingArticle) {
|
|
return c.json({ error: "文章不存在" }, 404);
|
|
}
|
|
|
|
// 软删除文章
|
|
await apiClient.database.table("know_info").where("id", id).update({
|
|
is_deleted: 1,
|
|
updated_at: apiClient.database.fn.now(),
|
|
});
|
|
|
|
return c.json({
|
|
message: "知识库文章删除成功",
|
|
});
|
|
} catch (error) {
|
|
log.api("删除知识库文章失败:", error);
|
|
return c.json({ error: "删除知识库文章失败" }, 500);
|
|
}
|
|
});
|
|
|
|
return knowInfoRoutes;
|
|
}
|
|
|
|
// 创建文件分类路由
|
|
export function createFileCategoryRoutes(withAuth: WithAuth) {
|
|
const fileCategoryRoutes = new Hono<{ Variables: Variables }>();
|
|
|
|
// 获取文件分类列表
|
|
fileCategoryRoutes.get("/", withAuth, async (c) => {
|
|
try {
|
|
const apiClient = c.get('apiClient');
|
|
|
|
const page = Number(c.req.query("page")) || 1;
|
|
const pageSize = Number(c.req.query("pageSize")) || 10;
|
|
const offset = (page - 1) * pageSize;
|
|
|
|
const search = c.req.query("search") || "";
|
|
|
|
let query = apiClient.database.table("file_category").orderBy("id", "desc");
|
|
|
|
if (search) {
|
|
query = query.where("name", "like", `%${search}%`);
|
|
}
|
|
|
|
const total = await query.clone().count();
|
|
|
|
const categories = await query
|
|
.select("id", "name", "code", "description")
|
|
.limit(pageSize)
|
|
.offset(offset);
|
|
|
|
return c.json({
|
|
data: categories,
|
|
total: Number(total),
|
|
page,
|
|
pageSize,
|
|
});
|
|
} catch (error) {
|
|
log.api("获取文件分类列表失败:", error);
|
|
return c.json({ error: "获取文件分类列表失败" }, 500);
|
|
}
|
|
});
|
|
|
|
// 创建文件分类
|
|
fileCategoryRoutes.post("/", withAuth, async (c) => {
|
|
try {
|
|
const apiClient = c.get('apiClient');
|
|
const data = (await c.req.json()) as Partial<FileCategory>;
|
|
|
|
// 验证必填字段
|
|
if (!data.name) {
|
|
return c.json({ error: "分类名称不能为空" }, 400);
|
|
}
|
|
|
|
// 插入文件分类
|
|
const [id] = await apiClient.database.table("file_category").insert({
|
|
...data,
|
|
created_at: apiClient.database.fn.now(),
|
|
updated_at: apiClient.database.fn.now(),
|
|
});
|
|
|
|
return c.json({
|
|
message: "文件分类创建成功",
|
|
data: {
|
|
id,
|
|
...data,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
log.api("创建文件分类失败:", error);
|
|
return c.json({ error: "创建文件分类失败" }, 500);
|
|
}
|
|
});
|
|
|
|
// 更新文件分类
|
|
fileCategoryRoutes.put("/:id", withAuth, async (c) => {
|
|
try {
|
|
const apiClient = c.get('apiClient');
|
|
const id = Number(c.req.param("id"));
|
|
|
|
if (!id || isNaN(id)) {
|
|
return c.json({ error: "无效的分类ID" }, 400);
|
|
}
|
|
|
|
const data = (await c.req.json()) as Partial<FileCategory>;
|
|
|
|
// 更新文件分类
|
|
await apiClient.database
|
|
.table("file_category")
|
|
.where("id", id)
|
|
.update({
|
|
...data,
|
|
updated_at: apiClient.database.fn.now(),
|
|
});
|
|
|
|
return c.json({
|
|
message: "文件分类更新成功",
|
|
data: {
|
|
id,
|
|
...data,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
log.api("更新文件分类失败:", error);
|
|
return c.json({ error: "更新文件分类失败" }, 500);
|
|
}
|
|
});
|
|
|
|
// 删除文件分类
|
|
fileCategoryRoutes.delete("/:id", withAuth, async (c) => {
|
|
try {
|
|
const apiClient = c.get('apiClient');
|
|
const id = Number(c.req.param("id"));
|
|
|
|
if (!id || isNaN(id)) {
|
|
return c.json({ error: "无效的分类ID" }, 400);
|
|
}
|
|
|
|
await apiClient.database.table("file_category").where("id", id).update({
|
|
is_deleted: DeleteStatus.DELETED,
|
|
updated_at: apiClient.database.fn.now(),
|
|
});
|
|
|
|
return c.json({
|
|
message: "文件分类删除成功",
|
|
});
|
|
} catch (error) {
|
|
log.api("删除文件分类失败:", error);
|
|
return c.json({ error: "删除文件分类失败" }, 500);
|
|
}
|
|
});
|
|
|
|
return fileCategoryRoutes;
|
|
}
|
|
|
|
// 创建文件上传路由
|
|
export function createFileUploadRoutes(withAuth: WithAuth) {
|
|
const fileUploadRoutes = new Hono<{ Variables: Variables }>();
|
|
|
|
// 获取 MinIO 上传策略
|
|
fileUploadRoutes.get("/policy", withAuth, async (c) => {
|
|
try {
|
|
const prefix = c.req.query("prefix") || "uploads/";
|
|
const filename = c.req.query("filename");
|
|
const maxSize = Number(c.req.query("maxSize")) || 10 * 1024 * 1024; // 默认10MB
|
|
|
|
if (!filename) {
|
|
return c.json({ error: "文件名不能为空" }, 400);
|
|
}
|
|
|
|
const apiClient = c.get('apiClient');
|
|
const policy = await apiClient.storage.getUploadPolicy(
|
|
prefix,
|
|
filename,
|
|
maxSize
|
|
);
|
|
|
|
return c.json({
|
|
message: "获取上传策略成功",
|
|
data: policy,
|
|
});
|
|
} catch (error) {
|
|
log.api("获取上传策略失败:", error);
|
|
return c.json({ error: "获取上传策略失败" }, 500);
|
|
}
|
|
});
|
|
|
|
// 保存文件信息到文件库
|
|
fileUploadRoutes.post("/save", withAuth, async (c) => {
|
|
try {
|
|
const fileData = (await c.req.json()) as Partial<FileLibrary>;
|
|
const user = c.get("user");
|
|
|
|
// 验证必填字段
|
|
if (!fileData.file_name || !fileData.file_path || !fileData.file_type) {
|
|
return c.json({ error: "文件名、路径和类型不能为空" }, 400);
|
|
}
|
|
|
|
const apiClient = c.get('apiClient');
|
|
|
|
// 设置上传者信息
|
|
if (user) {
|
|
fileData.uploader_id = user.id;
|
|
fileData.uploader_name = user.nickname || user.username;
|
|
}
|
|
|
|
// 插入文件库记录
|
|
const [id] = await apiClient.database.table("file_library").insert({
|
|
...fileData,
|
|
download_count: 0,
|
|
is_disabled: EnableStatus.ENABLED,
|
|
is_deleted: DeleteStatus.NOT_DELETED,
|
|
created_at: apiClient.database.fn.now(),
|
|
updated_at: apiClient.database.fn.now(),
|
|
});
|
|
|
|
// 获取插入的数据
|
|
const [insertedFile] = await apiClient.database
|
|
.table("file_library")
|
|
.where("id", id);
|
|
|
|
return c.json({
|
|
message: "文件信息保存成功",
|
|
data: insertedFile,
|
|
});
|
|
} catch (error) {
|
|
log.api("保存文件信息失败:", error);
|
|
return c.json({ error: "保存文件信息失败" }, 500);
|
|
}
|
|
});
|
|
|
|
// 获取文件列表
|
|
fileUploadRoutes.get("/list", withAuth, async (c) => {
|
|
try {
|
|
const page = Number(c.req.query("page")) || 1;
|
|
const pageSize = Number(c.req.query("pageSize")) || 10;
|
|
const category_id = c.req.query("category_id");
|
|
const fileType = c.req.query("fileType");
|
|
const keyword = c.req.query("keyword");
|
|
|
|
const apiClient = c.get('apiClient');
|
|
let query = apiClient.database
|
|
.table("file_library")
|
|
.where("is_deleted", DeleteStatus.NOT_DELETED)
|
|
.orderBy("created_at", "desc");
|
|
|
|
// 应用过滤条件
|
|
if (category_id) {
|
|
query = query.where("category_id", category_id);
|
|
}
|
|
|
|
if (fileType) {
|
|
query = query.where("file_type", fileType);
|
|
}
|
|
|
|
if (keyword) {
|
|
query = query.where((builder) => {
|
|
builder
|
|
.where("file_name", "like", `%${keyword}%`)
|
|
.orWhere("description", "like", `%${keyword}%`)
|
|
.orWhere("tags", "like", `%${keyword}%`);
|
|
});
|
|
}
|
|
|
|
// 获取总数
|
|
const total = await query.clone().count();
|
|
|
|
// 分页查询
|
|
const files = await query.limit(pageSize).offset((page - 1) * pageSize);
|
|
|
|
return c.json({
|
|
message: "获取文件列表成功",
|
|
data: {
|
|
list: files,
|
|
pagination: {
|
|
current: page,
|
|
pageSize,
|
|
total: Number(total),
|
|
},
|
|
},
|
|
});
|
|
} catch (error) {
|
|
log.api("获取文件列表失败:", error);
|
|
return c.json({ error: "获取文件列表失败" }, 500);
|
|
}
|
|
});
|
|
|
|
// 获取单个文件信息
|
|
fileUploadRoutes.get("/:id", withAuth, async (c) => {
|
|
try {
|
|
const id = Number(c.req.param("id"));
|
|
|
|
if (!id || isNaN(id)) {
|
|
return c.json({ error: "无效的文件ID" }, 400);
|
|
}
|
|
|
|
const apiClient = c.get('apiClient');
|
|
const file = await apiClient.database
|
|
.table("file_library")
|
|
.where("id", id)
|
|
.where("is_deleted", DeleteStatus.NOT_DELETED)
|
|
.first();
|
|
|
|
if (!file) {
|
|
return c.json({ error: "文件不存在" }, 404);
|
|
}
|
|
|
|
return c.json({
|
|
message: "获取文件信息成功",
|
|
data: file,
|
|
});
|
|
} catch (error) {
|
|
log.api("获取文件信息失败:", error);
|
|
return c.json({ error: "获取文件信息失败" }, 500);
|
|
}
|
|
});
|
|
|
|
// 增加文件下载计数
|
|
fileUploadRoutes.post("/:id/download", withAuth, async (c) => {
|
|
try {
|
|
const id = Number(c.req.param("id"));
|
|
|
|
if (!id || isNaN(id)) {
|
|
return c.json({ error: "无效的文件ID" }, 400);
|
|
}
|
|
|
|
const apiClient = c.get('apiClient');
|
|
|
|
// 查询文件是否存在
|
|
const file = await apiClient.database
|
|
.table("file_library")
|
|
.where("id", id)
|
|
.where("is_deleted", DeleteStatus.NOT_DELETED)
|
|
.first();
|
|
|
|
if (!file) {
|
|
return c.json({ error: "文件不存在" }, 404);
|
|
}
|
|
|
|
// 增加下载计数
|
|
await apiClient.database
|
|
.table("file_library")
|
|
.where("id", id)
|
|
.update({
|
|
download_count: apiClient.database.raw("download_count + 1"),
|
|
updated_at: apiClient.database.fn.now(),
|
|
});
|
|
|
|
return c.json({
|
|
message: "更新下载计数成功",
|
|
});
|
|
} catch (error) {
|
|
log.api("更新下载计数失败:", error);
|
|
return c.json({ error: "更新下载计数失败" }, 500);
|
|
}
|
|
});
|
|
|
|
// 删除文件
|
|
fileUploadRoutes.delete("/:id", withAuth, async (c) => {
|
|
try {
|
|
const id = Number(c.req.param("id"));
|
|
|
|
if (!id || isNaN(id)) {
|
|
return c.json({ error: "无效的文件ID" }, 400);
|
|
}
|
|
|
|
const apiClient = c.get('apiClient');
|
|
|
|
// 查询文件是否存在
|
|
const file = await apiClient.database
|
|
.table("file_library")
|
|
.where("id", id)
|
|
.where("is_deleted", DeleteStatus.NOT_DELETED)
|
|
.first();
|
|
|
|
if (!file) {
|
|
return c.json({ error: "文件不存在" }, 404);
|
|
}
|
|
|
|
// 软删除文件
|
|
await apiClient.database.table("file_library").where("id", id).update({
|
|
is_deleted: DeleteStatus.DELETED,
|
|
updated_at: apiClient.database.fn.now(),
|
|
});
|
|
|
|
return c.json({
|
|
message: "文件删除成功",
|
|
});
|
|
} catch (error) {
|
|
log.api("删除文件失败:", error);
|
|
return c.json({ error: "删除文件失败" }, 500);
|
|
}
|
|
});
|
|
|
|
return fileUploadRoutes;
|
|
}
|
|
|
|
// 创建主题设置路由
|
|
export function createThemeRoutes(withAuth: WithAuth) {
|
|
const themeRoutes = new Hono<{ Variables: Variables }>();
|
|
|
|
// 获取当前主题设置
|
|
themeRoutes.get("/", withAuth, async (c) => {
|
|
try {
|
|
const apiClient = c.get('apiClient');
|
|
const user = c.get('user');
|
|
|
|
if (!user) {
|
|
return c.json({ error: "未授权访问" }, 401);
|
|
}
|
|
|
|
// 获取用户的主题设置
|
|
let themeSettings = await apiClient.database
|
|
.table("theme_settings")
|
|
.where("user_id", user.id)
|
|
.first();
|
|
|
|
// 如果用户没有主题设置,则创建默认设置
|
|
if (!themeSettings) {
|
|
const defaultSettings = {
|
|
theme_mode: ThemeMode.LIGHT,
|
|
primary_color: '#1890ff',
|
|
font_size: FontSize.MEDIUM,
|
|
is_compact: CompactMode.NORMAL
|
|
};
|
|
|
|
const [id] = await apiClient.database.table("theme_settings").insert({
|
|
user_id: user.id,
|
|
settings: defaultSettings,
|
|
created_at: apiClient.database.fn.now(),
|
|
updated_at: apiClient.database.fn.now(),
|
|
});
|
|
|
|
themeSettings = await apiClient.database
|
|
.table("theme_settings")
|
|
.where("id", id)
|
|
.first();
|
|
}
|
|
|
|
return c.json({
|
|
message: "获取主题设置成功",
|
|
data: themeSettings?.settings,
|
|
});
|
|
} catch (error) {
|
|
log.api("获取主题设置失败:", error);
|
|
return c.json({ error: "获取主题设置失败" }, 500);
|
|
}
|
|
});
|
|
|
|
// 更新主题设置
|
|
themeRoutes.put("/", withAuth, async (c) => {
|
|
try {
|
|
const user = c.get('user');
|
|
const apiClient = c.get('apiClient');
|
|
|
|
if (!user) {
|
|
return c.json({ error: "未授权访问" }, 401);
|
|
}
|
|
|
|
const themeData = (await c.req.json()) as Partial<ThemeSettings>;
|
|
|
|
// 检查用户是否已有主题设置
|
|
const existingTheme = await apiClient.database
|
|
.table("theme_settings")
|
|
.where("user_id", user.id)
|
|
.first();
|
|
|
|
if (existingTheme) {
|
|
// 更新现有设置
|
|
const currentSettings = existingTheme.settings || {};
|
|
const updatedSettings = {
|
|
...currentSettings,
|
|
...themeData
|
|
};
|
|
|
|
await apiClient.database
|
|
.table("theme_settings")
|
|
.where("user_id", user.id)
|
|
.update({
|
|
settings: JSON.stringify(updatedSettings),
|
|
updated_at: apiClient.database.fn.now(),
|
|
});
|
|
} else {
|
|
// 创建新设置
|
|
const defaultSettings = {
|
|
theme_mode: ThemeMode.LIGHT,
|
|
primary_color: '#1890ff',
|
|
font_size: FontSize.MEDIUM,
|
|
is_compact: CompactMode.NORMAL
|
|
};
|
|
|
|
const updatedSettings = {
|
|
...defaultSettings,
|
|
...themeData
|
|
};
|
|
|
|
await apiClient.database.table("theme_settings").insert({
|
|
user_id: user.id,
|
|
settings: updatedSettings,
|
|
created_at: apiClient.database.fn.now(),
|
|
updated_at: apiClient.database.fn.now(),
|
|
});
|
|
}
|
|
|
|
// 获取更新后的主题设置
|
|
const updatedTheme = await apiClient.database
|
|
.table("theme_settings")
|
|
.where("user_id", user.id)
|
|
.first();
|
|
|
|
return c.json({
|
|
message: "主题设置更新成功",
|
|
data: updatedTheme,
|
|
});
|
|
} catch (error) {
|
|
log.api("更新主题设置失败:", error);
|
|
return c.json({ error: "更新主题设置失败" }, 500);
|
|
}
|
|
});
|
|
|
|
// 重置主题设置为默认值
|
|
themeRoutes.post("/reset", withAuth, async (c) => {
|
|
try {
|
|
const user = c.get('user');
|
|
const apiClient = c.get('apiClient');
|
|
|
|
if (!user) {
|
|
return c.json({ error: "未授权访问" }, 401);
|
|
}
|
|
|
|
// 默认主题设置
|
|
const defaultSettings = {
|
|
theme_mode: ThemeMode.LIGHT,
|
|
primary_color: '#1890ff',
|
|
font_size: FontSize.MEDIUM,
|
|
is_compact: CompactMode.NORMAL
|
|
};
|
|
|
|
// 更新用户的主题设置
|
|
await apiClient.database
|
|
.table("theme_settings")
|
|
.where("user_id", user.id)
|
|
.update({
|
|
settings: JSON.stringify(defaultSettings),
|
|
updated_at: apiClient.database.fn.now(),
|
|
});
|
|
|
|
// 获取更新后的主题设置
|
|
const updatedTheme = await apiClient.database
|
|
.table("theme_settings")
|
|
.where("user_id", user.id)
|
|
.first();
|
|
|
|
return c.json({
|
|
message: "主题设置已重置为默认值",
|
|
data: updatedTheme,
|
|
});
|
|
} catch (error) {
|
|
log.api("重置主题设置失败:", error);
|
|
return c.json({ error: "重置主题设置失败" }, 500);
|
|
}
|
|
});
|
|
|
|
return themeRoutes;
|
|
}
|
|
|
|
// 创建系统设置路由
|
|
export function createSystemSettingsRoutes(withAuth: WithAuth) {
|
|
const settingsRoutes = new Hono<{ Variables: Variables }>();
|
|
|
|
// 获取所有系统设置(按分组)
|
|
settingsRoutes.get('/', withAuth, async (c) => {
|
|
try {
|
|
const apiClient = c.get('apiClient');
|
|
const settings = await apiClient.database
|
|
.table('system_settings')
|
|
.select('*');
|
|
|
|
// 按分组整理数据
|
|
const groupedSettings = settings.reduce((acc: SystemSettingGroupData[], setting) => {
|
|
const groupIndex = acc.findIndex((g: SystemSettingGroupData) => g.name === setting.group);
|
|
if (groupIndex === -1) {
|
|
acc.push({
|
|
name: setting.group,
|
|
description: `${setting.group}组设置`,
|
|
settings: [{
|
|
id: setting.id,
|
|
key: setting.key,
|
|
value: setting.value,
|
|
description: setting.description,
|
|
group: setting.group
|
|
}]
|
|
});
|
|
} else {
|
|
acc[groupIndex].settings.push({
|
|
id: setting.id,
|
|
key: setting.key,
|
|
value: setting.value,
|
|
description: setting.description,
|
|
group: setting.group
|
|
});
|
|
}
|
|
return acc;
|
|
}, []);
|
|
|
|
return c.json({
|
|
message: '获取系统设置成功',
|
|
data: groupedSettings
|
|
});
|
|
} catch (error) {
|
|
log.api('获取系统设置失败:', error);
|
|
return c.json({ error: '获取系统设置失败' }, 500);
|
|
}
|
|
});
|
|
|
|
// 获取指定分组的系统设置
|
|
settingsRoutes.get('/group/:group', withAuth, async (c) => {
|
|
try {
|
|
const apiClient = c.get('apiClient');
|
|
const group = c.req.param('group');
|
|
|
|
const settings = await apiClient.database
|
|
.table('system_settings')
|
|
.where('group', group)
|
|
.select('*');
|
|
|
|
return c.json({
|
|
message: '获取分组设置成功',
|
|
data: settings
|
|
});
|
|
} catch (error) {
|
|
log.api('获取分组设置失败:', error);
|
|
return c.json({ error: '获取分组设置失败' }, 500);
|
|
}
|
|
});
|
|
|
|
// 更新系统设置
|
|
settingsRoutes.put('/:key', withAuth, async (c) => {
|
|
try {
|
|
const apiClient = c.get('apiClient');
|
|
const key = c.req.param('key');
|
|
const settingData = await c.req.json();
|
|
|
|
// 验证设置是否存在
|
|
const existingSetting = await apiClient.database
|
|
.table('system_settings')
|
|
.where('key', key)
|
|
.first();
|
|
|
|
if (!existingSetting) {
|
|
return c.json({ error: '设置项不存在' }, 404);
|
|
}
|
|
|
|
// 更新设置
|
|
await apiClient.database
|
|
.table('system_settings')
|
|
.where('key', key)
|
|
.update({
|
|
value: settingData.value,
|
|
updated_at: apiClient.database.fn.now()
|
|
});
|
|
|
|
// 获取更新后的设置
|
|
const updatedSetting = await apiClient.database
|
|
.table('system_settings')
|
|
.where('key', key)
|
|
.first();
|
|
|
|
return c.json({
|
|
message: '系统设置已更新',
|
|
data: updatedSetting
|
|
});
|
|
} catch (error) {
|
|
log.api('更新系统设置失败:', error);
|
|
return c.json({ error: '更新系统设置失败' }, 500);
|
|
}
|
|
});
|
|
|
|
// 批量更新系统设置
|
|
settingsRoutes.put('/', withAuth, async (c) => {
|
|
try {
|
|
const apiClient = c.get('apiClient');
|
|
const settingsData = await c.req.json();
|
|
|
|
// 验证数据格式
|
|
if (!Array.isArray(settingsData)) {
|
|
return c.json({ error: '无效的请求数据格式,应为数组' }, 400);
|
|
}
|
|
|
|
const trxProvider = apiClient.database.transactionProvider();
|
|
const trx = await trxProvider();
|
|
|
|
for (const setting of settingsData) {
|
|
if (!setting.key) continue;
|
|
|
|
// 验证设置是否存在
|
|
const existingSetting = await trx.table('system_settings')
|
|
.where('key', setting.key)
|
|
.first();
|
|
|
|
if (!existingSetting) {
|
|
throw new Error(`设置项 ${setting.key} 不存在`);
|
|
}
|
|
|
|
// 更新设置
|
|
await trx.table('system_settings')
|
|
.where('key', setting.key)
|
|
.update({
|
|
value: setting.value,
|
|
updated_at: trx.fn.now()
|
|
});
|
|
}
|
|
|
|
await trx.commit();
|
|
|
|
// 获取所有更新后的设置
|
|
const updatedSettings = await apiClient.database
|
|
.table('system_settings')
|
|
.whereIn('key', settingsData.map(s => s.key))
|
|
.select('*');
|
|
|
|
return c.json({
|
|
message: '系统设置已批量更新',
|
|
data: updatedSettings
|
|
});
|
|
} catch (error) {
|
|
log.api('批量更新系统设置失败:', error);
|
|
return c.json({ error: '批量更新系统设置失败' }, 500);
|
|
}
|
|
});
|
|
|
|
// 重置系统设置
|
|
settingsRoutes.post('/reset', withAuth, async (c) => {
|
|
try {
|
|
const apiClient = c.get('apiClient');
|
|
|
|
// 重置为迁移文件中定义的初始值
|
|
const trxProvider = apiClient.database.transactionProvider();
|
|
const trx = await trxProvider();
|
|
|
|
// 清空现有设置
|
|
await trx.table('system_settings').delete();
|
|
|
|
// 插入默认设置
|
|
await trx.table('system_settings').insert([
|
|
// 基础设置组
|
|
{
|
|
key: 'SITE_NAME',
|
|
value: '应用管理系统',
|
|
description: '站点名称',
|
|
group: 'basic',
|
|
created_at: apiClient.database.fn.now(),
|
|
updated_at: apiClient.database.fn.now()
|
|
},
|
|
{
|
|
key: 'SITE_DESCRIPTION',
|
|
value: '一个强大的应用管理系统',
|
|
description: '站点描述',
|
|
group: 'basic',
|
|
created_at: apiClient.database.fn.now(),
|
|
updated_at: apiClient.database.fn.now()
|
|
},
|
|
{
|
|
key: 'SITE_KEYWORDS',
|
|
value: '应用管理,系统管理,后台管理',
|
|
description: '站点关键词',
|
|
group: 'basic',
|
|
created_at: apiClient.database.fn.now(),
|
|
updated_at: apiClient.database.fn.now()
|
|
},
|
|
// 功能设置组
|
|
{
|
|
key: 'ENABLE_REGISTER',
|
|
value: 'true',
|
|
description: '是否开启注册',
|
|
group: 'feature',
|
|
created_at: apiClient.database.fn.now(),
|
|
updated_at: apiClient.database.fn.now()
|
|
},
|
|
{
|
|
key: 'ENABLE_CAPTCHA',
|
|
value: 'true',
|
|
description: '是否开启验证码',
|
|
group: 'feature',
|
|
created_at: apiClient.database.fn.now(),
|
|
updated_at: apiClient.database.fn.now()
|
|
},
|
|
{
|
|
key: 'LOGIN_ATTEMPTS',
|
|
value: '5',
|
|
description: '登录尝试次数',
|
|
group: 'feature',
|
|
created_at: apiClient.database.fn.now(),
|
|
updated_at: apiClient.database.fn.now()
|
|
},
|
|
// 上传设置组
|
|
{
|
|
key: 'UPLOAD_MAX_SIZE',
|
|
value: '10',
|
|
description: '最大上传大小(MB)',
|
|
group: 'upload',
|
|
created_at: apiClient.database.fn.now(),
|
|
updated_at: apiClient.database.fn.now()
|
|
},
|
|
{
|
|
key: 'ALLOWED_FILE_TYPES',
|
|
value: 'jpg,jpeg,png,gif,doc,docx,xls,xlsx,pdf',
|
|
description: '允许的文件类型',
|
|
group: 'upload',
|
|
created_at: apiClient.database.fn.now(),
|
|
updated_at: apiClient.database.fn.now()
|
|
},
|
|
// 通知设置组
|
|
{
|
|
key: 'NOTIFY_ON_LOGIN',
|
|
value: 'true',
|
|
description: '登录通知',
|
|
group: 'notify',
|
|
created_at: apiClient.database.fn.now(),
|
|
updated_at: apiClient.database.fn.now()
|
|
},
|
|
{
|
|
key: 'NOTIFY_ON_ERROR',
|
|
value: 'true',
|
|
description: '错误通知',
|
|
group: 'notify',
|
|
created_at: apiClient.database.fn.now(),
|
|
updated_at: apiClient.database.fn.now()
|
|
}
|
|
]);
|
|
|
|
await trx.commit();
|
|
|
|
const resetSettings = await apiClient.database
|
|
.table('system_settings')
|
|
.select('*');
|
|
|
|
return c.json({
|
|
message: '系统设置已重置',
|
|
data: resetSettings
|
|
});
|
|
} catch (error) {
|
|
log.api('重置系统设置失败:', error);
|
|
return c.json({ error: '重置系统设置失败' }, 500);
|
|
}
|
|
});
|
|
|
|
return settingsRoutes;
|
|
}
|