|
|
@@ -1,1063 +0,0 @@
|
|
|
-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;
|
|
|
-}
|