diff --git a/server/app.tsx b/server/app.tsx index adcfb67..4679a73 100644 --- a/server/app.tsx +++ b/server/app.tsx @@ -14,13 +14,11 @@ import utc from 'dayjs/plugin/utc'; import type { SystemSettingRecord, GlobalConfig } from '../client/share/types.ts'; import { SystemSettingKey, OssType, MapMode } from '../client/share/types.ts'; -import { - createKnowInfoRoutes, - createFileCategoryRoutes, - createFileUploadRoutes, - createThemeRoutes, - createSystemSettingsRoutes, -} from "./routes_sys.ts"; +import { createKnowInfoRoutes } from "./routes_know_info.ts"; +import { createFileCategoryRoutes } from "./routes_file_category.ts"; +import { createFileUploadRoutes } from "./routes_file_upload.ts"; +import { createThemeRoutes } from "./routes_theme.ts"; +import { createSystemSettingsRoutes } from "./routes_system_settings.ts"; import { createMapRoutes, diff --git a/server/routes_file_category.ts b/server/routes_file_category.ts new file mode 100644 index 0000000..35ba900 --- /dev/null +++ b/server/routes_file_category.ts @@ -0,0 +1,147 @@ +import { Hono } from "hono"; +import debug from "debug"; +import type { + FileCategory, +} from "../client/share/types.ts"; + +import { + DeleteStatus, +} from "../client/share/types.ts"; + +import type { Variables, WithAuth } from "./app.tsx"; + +const log = { + api: debug("api:sys"), +}; + +// 创建文件分类路由 +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; + + // 验证必填字段 + 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; + + // 更新文件分类 + 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; +} \ No newline at end of file diff --git a/server/routes_file_upload.ts b/server/routes_file_upload.ts new file mode 100644 index 0000000..a8f7a8f --- /dev/null +++ b/server/routes_file_upload.ts @@ -0,0 +1,258 @@ +import { Hono } from "hono"; +import debug from "debug"; +import type { + FileLibrary, +} from "../client/share/types.ts"; + +import { + DeleteStatus, + EnableStatus, +} from "../client/share/types.ts"; + +import type { Variables, WithAuth } from "./app.tsx"; + +const log = { + api: debug("api:sys"), +}; + +// 创建文件上传路由 +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; + 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; +} \ No newline at end of file diff --git a/server/routes_know_info.ts b/server/routes_know_info.ts new file mode 100644 index 0000000..2e852b0 --- /dev/null +++ b/server/routes_know_info.ts @@ -0,0 +1,228 @@ +import { Hono } from "hono"; +import debug from "debug"; +import type { + KnowInfo, +} from "../client/share/types.ts"; + +import { + EnableStatus, + DeleteStatus, +} 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; + + // 验证必填字段 + 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; + + // 验证必填字段 + 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; +} \ No newline at end of file diff --git a/server/routes_sys.ts b/server/routes_sys.ts deleted file mode 100644 index 0670ed5..0000000 --- a/server/routes_sys.ts +++ /dev/null @@ -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; - - // 验证必填字段 - 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; - - // 验证必填字段 - 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; - - // 验证必填字段 - 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; - - // 更新文件分类 - 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; - 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; - - // 检查用户是否已有主题设置 - 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; -} diff --git a/server/routes_system_settings.ts b/server/routes_system_settings.ts new file mode 100644 index 0000000..ebd1bdb --- /dev/null +++ b/server/routes_system_settings.ts @@ -0,0 +1,296 @@ +import { Hono } from "hono"; +import debug from "debug"; +import type { + SystemSetting, + SystemSettingGroupData, +} from "../client/share/types.ts"; + +import type { Variables, WithAuth } from "./app.tsx"; + +const log = { + api: debug("api:sys"), +}; + +// 创建系统设置路由 +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; +} \ No newline at end of file diff --git a/server/routes_theme.ts b/server/routes_theme.ts new file mode 100644 index 0000000..c4c95eb --- /dev/null +++ b/server/routes_theme.ts @@ -0,0 +1,186 @@ +import { Hono } from "hono"; +import debug from "debug"; +import type { + ThemeSettings, +} from "../client/share/types.ts"; + +import { + ThemeMode, + FontSize, + CompactMode, +} from "../client/share/types.ts"; + +import type { Variables, WithAuth } from "./app.tsx"; + +const log = { + api: debug("api:sys"), +}; + +// 创建主题设置路由 +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; + + // 检查用户是否已有主题设置 + 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; +} \ No newline at end of file