routes_sys.ts 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251
  1. import { Hono } from "hono";
  2. import debug from "debug";
  3. import type {
  4. FileLibrary,
  5. FileCategory,
  6. KnowInfo,
  7. ThemeSettings,
  8. SystemSetting,
  9. SystemSettingGroupData,
  10. } from "../client/share/types.ts";
  11. import {
  12. EnableStatus,
  13. DeleteStatus,
  14. ThemeMode,
  15. FontSize,
  16. CompactMode,
  17. } from "../client/share/types.ts";
  18. import type { Variables, WithAuth } from "./app.tsx";
  19. const log = {
  20. api: debug("api:sys"),
  21. };
  22. // 创建知识库管理路由
  23. export function createKnowInfoRoutes(withAuth: WithAuth) {
  24. const knowInfoRoutes = new Hono<{ Variables: Variables }>();
  25. // 获取知识库文章列表
  26. knowInfoRoutes.get("/", withAuth, async (c) => {
  27. try {
  28. const apiClient = c.get('apiClient');
  29. // 获取分页参数
  30. const page = Number(c.req.query("page")) || 1;
  31. const limit = Number(c.req.query("limit")) || 10;
  32. const offset = (page - 1) * limit;
  33. // 获取筛选参数
  34. const title = c.req.query("title");
  35. const category = c.req.query("category");
  36. const tags = c.req.query("tags");
  37. // 构建查询
  38. let query = apiClient.database
  39. .table("know_info")
  40. .where("is_deleted", 0)
  41. .orderBy("id", "desc");
  42. // 应用筛选条件
  43. if (title) {
  44. query = query.where("title", "like", `%${title}%`);
  45. }
  46. if (category) {
  47. query = query.where("category", category);
  48. }
  49. if (tags) {
  50. query = query.where("tags", "like", `%${tags}%`);
  51. }
  52. // 克隆查询以获取总数
  53. const countQuery = query.clone();
  54. // 执行分页查询
  55. const articles = await query.limit(limit).offset(offset);
  56. // 获取总数
  57. const count = await countQuery.count();
  58. return c.json({
  59. data: articles,
  60. pagination: {
  61. total: Number(count),
  62. current: page,
  63. pageSize: limit,
  64. totalPages: Math.ceil(Number(count) / limit),
  65. },
  66. });
  67. } catch (error) {
  68. log.api("获取知识库文章列表失败:", error);
  69. return c.json({ error: "获取知识库文章列表失败" }, 500);
  70. }
  71. });
  72. // 获取单个知识库文章
  73. knowInfoRoutes.get("/:id", withAuth, async (c) => {
  74. try {
  75. const id = Number(c.req.param("id"));
  76. if (!id || isNaN(id)) {
  77. return c.json({ error: "无效的文章ID" }, 400);
  78. }
  79. const apiClient = c.get('apiClient');
  80. const [article] = await apiClient.database
  81. .table("know_info")
  82. .where({ id, is_deleted: 0 });
  83. if (!article) {
  84. return c.json({ error: "文章不存在" }, 404);
  85. }
  86. return c.json({message: '获取知识库文章详情成功', data: article});
  87. } catch (error) {
  88. log.api("获取知识库文章详情失败:", error);
  89. return c.json({ error: "获取知识库文章详情失败" }, 500);
  90. }
  91. });
  92. // 创建知识库文章
  93. knowInfoRoutes.post("/", withAuth, async (c) => {
  94. try {
  95. const articleData = (await c.req.json()) as Partial<KnowInfo>;
  96. // 验证必填字段
  97. if (!articleData.title) {
  98. return c.json({ error: "文章标题不能为空" }, 400);
  99. }
  100. // 如果作者为空,则使用当前用户的用户名
  101. if (!articleData.author) {
  102. const user = c.get("user");
  103. articleData.author = user ? user.username : "unknown";
  104. }
  105. const apiClient = c.get('apiClient');
  106. const [id] = await apiClient.database
  107. .table("know_info")
  108. .insert(articleData);
  109. // 获取创建的文章
  110. const [createdArticle] = await apiClient.database
  111. .table("know_info")
  112. .where("id", id);
  113. return c.json({
  114. message: "知识库文章创建成功",
  115. data: createdArticle,
  116. });
  117. } catch (error) {
  118. log.api("创建知识库文章失败:", error);
  119. return c.json({ error: "创建知识库文章失败" }, 500);
  120. }
  121. });
  122. // 更新知识库文章
  123. knowInfoRoutes.put("/:id", withAuth, async (c) => {
  124. try {
  125. const id = Number(c.req.param("id"));
  126. if (!id || isNaN(id)) {
  127. return c.json({ error: "无效的文章ID" }, 400);
  128. }
  129. const articleData = (await c.req.json()) as Partial<KnowInfo>;
  130. // 验证必填字段
  131. if (!articleData.title) {
  132. return c.json({ error: "文章标题不能为空" }, 400);
  133. }
  134. const apiClient = c.get('apiClient');
  135. // 检查文章是否存在
  136. const [existingArticle] = await apiClient.database
  137. .table("know_info")
  138. .where({ id, is_deleted: 0 });
  139. if (!existingArticle) {
  140. return c.json({ error: "文章不存在" }, 404);
  141. }
  142. // 更新文章
  143. await apiClient.database
  144. .table("know_info")
  145. .where("id", id)
  146. .update({
  147. ...articleData,
  148. updated_at: apiClient.database.fn.now(),
  149. });
  150. // 获取更新后的文章
  151. const [updatedArticle] = await apiClient.database
  152. .table("know_info")
  153. .where("id", id);
  154. return c.json({
  155. message: "知识库文章更新成功",
  156. data: updatedArticle,
  157. });
  158. } catch (error) {
  159. log.api("更新知识库文章失败:", error);
  160. return c.json({ error: "更新知识库文章失败" }, 500);
  161. }
  162. });
  163. // 删除知识库文章(软删除)
  164. knowInfoRoutes.delete("/:id", withAuth, async (c) => {
  165. try {
  166. const id = Number(c.req.param("id"));
  167. if (!id || isNaN(id)) {
  168. return c.json({ error: "无效的文章ID" }, 400);
  169. }
  170. const apiClient = c.get('apiClient');
  171. // 检查文章是否存在
  172. const [existingArticle] = await apiClient.database
  173. .table("know_info")
  174. .where({ id, is_deleted: 0 });
  175. if (!existingArticle) {
  176. return c.json({ error: "文章不存在" }, 404);
  177. }
  178. // 软删除文章
  179. await apiClient.database.table("know_info").where("id", id).update({
  180. is_deleted: 1,
  181. updated_at: apiClient.database.fn.now(),
  182. });
  183. return c.json({
  184. message: "知识库文章删除成功",
  185. });
  186. } catch (error) {
  187. log.api("删除知识库文章失败:", error);
  188. return c.json({ error: "删除知识库文章失败" }, 500);
  189. }
  190. });
  191. return knowInfoRoutes;
  192. }
  193. // 创建文件分类路由
  194. export function createFileCategoryRoutes(withAuth: WithAuth) {
  195. const fileCategoryRoutes = new Hono<{ Variables: Variables }>();
  196. // 获取文件分类列表
  197. fileCategoryRoutes.get("/", withAuth, async (c) => {
  198. try {
  199. const apiClient = c.get('apiClient');
  200. const page = Number(c.req.query("page")) || 1;
  201. const pageSize = Number(c.req.query("pageSize")) || 10;
  202. const offset = (page - 1) * pageSize;
  203. const search = c.req.query("search") || "";
  204. let query = apiClient.database.table("file_category").orderBy("id", "desc");
  205. if (search) {
  206. query = query.where("name", "like", `%${search}%`);
  207. }
  208. const total = await query.clone().count();
  209. const categories = await query
  210. .select("id", "name", "code", "description")
  211. .limit(pageSize)
  212. .offset(offset);
  213. return c.json({
  214. data: categories,
  215. total: Number(total),
  216. page,
  217. pageSize,
  218. });
  219. } catch (error) {
  220. log.api("获取文件分类列表失败:", error);
  221. return c.json({ error: "获取文件分类列表失败" }, 500);
  222. }
  223. });
  224. // 创建文件分类
  225. fileCategoryRoutes.post("/", withAuth, async (c) => {
  226. try {
  227. const apiClient = c.get('apiClient');
  228. const data = (await c.req.json()) as Partial<FileCategory>;
  229. // 验证必填字段
  230. if (!data.name) {
  231. return c.json({ error: "分类名称不能为空" }, 400);
  232. }
  233. // 插入文件分类
  234. const [id] = await apiClient.database.table("file_category").insert({
  235. ...data,
  236. created_at: apiClient.database.fn.now(),
  237. updated_at: apiClient.database.fn.now(),
  238. });
  239. return c.json({
  240. message: "文件分类创建成功",
  241. data: {
  242. id,
  243. ...data,
  244. },
  245. });
  246. } catch (error) {
  247. log.api("创建文件分类失败:", error);
  248. return c.json({ error: "创建文件分类失败" }, 500);
  249. }
  250. });
  251. // 更新文件分类
  252. fileCategoryRoutes.put("/:id", withAuth, async (c) => {
  253. try {
  254. const apiClient = c.get('apiClient');
  255. const id = Number(c.req.param("id"));
  256. if (!id || isNaN(id)) {
  257. return c.json({ error: "无效的分类ID" }, 400);
  258. }
  259. const data = (await c.req.json()) as Partial<FileCategory>;
  260. // 更新文件分类
  261. await apiClient.database
  262. .table("file_category")
  263. .where("id", id)
  264. .update({
  265. ...data,
  266. updated_at: apiClient.database.fn.now(),
  267. });
  268. return c.json({
  269. message: "文件分类更新成功",
  270. data: {
  271. id,
  272. ...data,
  273. },
  274. });
  275. } catch (error) {
  276. log.api("更新文件分类失败:", error);
  277. return c.json({ error: "更新文件分类失败" }, 500);
  278. }
  279. });
  280. // 删除文件分类
  281. fileCategoryRoutes.delete("/:id", withAuth, async (c) => {
  282. try {
  283. const apiClient = c.get('apiClient');
  284. const id = Number(c.req.param("id"));
  285. if (!id || isNaN(id)) {
  286. return c.json({ error: "无效的分类ID" }, 400);
  287. }
  288. await apiClient.database.table("file_category").where("id", id).update({
  289. is_deleted: DeleteStatus.DELETED,
  290. updated_at: apiClient.database.fn.now(),
  291. });
  292. return c.json({
  293. message: "文件分类删除成功",
  294. });
  295. } catch (error) {
  296. log.api("删除文件分类失败:", error);
  297. return c.json({ error: "删除文件分类失败" }, 500);
  298. }
  299. });
  300. return fileCategoryRoutes;
  301. }
  302. // 创建文件上传路由
  303. export function createFileUploadRoutes(withAuth: WithAuth) {
  304. const fileUploadRoutes = new Hono<{ Variables: Variables }>();
  305. // 获取 MinIO 上传策略
  306. fileUploadRoutes.get("/policy", withAuth, async (c) => {
  307. try {
  308. const prefix = c.req.query("prefix") || "uploads/";
  309. const filename = c.req.query("filename");
  310. const maxSize = Number(c.req.query("maxSize")) || 10 * 1024 * 1024; // 默认10MB
  311. if (!filename) {
  312. return c.json({ error: "文件名不能为空" }, 400);
  313. }
  314. const apiClient = c.get('apiClient');
  315. const policy = await apiClient.storage.getUploadPolicy(
  316. prefix,
  317. filename,
  318. maxSize
  319. );
  320. return c.json({
  321. message: "获取上传策略成功",
  322. data: policy,
  323. });
  324. } catch (error) {
  325. log.api("获取上传策略失败:", error);
  326. return c.json({ error: "获取上传策略失败" }, 500);
  327. }
  328. });
  329. // 保存文件信息到文件库
  330. fileUploadRoutes.post("/save", withAuth, async (c) => {
  331. try {
  332. const fileData = (await c.req.json()) as Partial<FileLibrary>;
  333. const user = c.get("user");
  334. // 验证必填字段
  335. if (!fileData.file_name || !fileData.file_path || !fileData.file_type) {
  336. return c.json({ error: "文件名、路径和类型不能为空" }, 400);
  337. }
  338. const apiClient = c.get('apiClient');
  339. // 设置上传者信息
  340. if (user) {
  341. fileData.uploader_id = user.id;
  342. fileData.uploader_name = user.nickname || user.username;
  343. }
  344. // 插入文件库记录
  345. const [id] = await apiClient.database.table("file_library").insert({
  346. ...fileData,
  347. download_count: 0,
  348. is_disabled: EnableStatus.ENABLED,
  349. is_deleted: DeleteStatus.NOT_DELETED,
  350. created_at: apiClient.database.fn.now(),
  351. updated_at: apiClient.database.fn.now(),
  352. });
  353. // 获取插入的数据
  354. const [insertedFile] = await apiClient.database
  355. .table("file_library")
  356. .where("id", id);
  357. return c.json({
  358. message: "文件信息保存成功",
  359. data: insertedFile,
  360. });
  361. } catch (error) {
  362. log.api("保存文件信息失败:", error);
  363. return c.json({ error: "保存文件信息失败" }, 500);
  364. }
  365. });
  366. // 获取文件列表
  367. fileUploadRoutes.get("/list", withAuth, async (c) => {
  368. try {
  369. const page = Number(c.req.query("page")) || 1;
  370. const pageSize = Number(c.req.query("pageSize")) || 10;
  371. const category_id = c.req.query("category_id");
  372. const fileType = c.req.query("fileType");
  373. const keyword = c.req.query("keyword");
  374. const apiClient = c.get('apiClient');
  375. let query = apiClient.database
  376. .table("file_library")
  377. .where("is_deleted", DeleteStatus.NOT_DELETED)
  378. .orderBy("created_at", "desc");
  379. // 应用过滤条件
  380. if (category_id) {
  381. query = query.where("category_id", category_id);
  382. }
  383. if (fileType) {
  384. query = query.where("file_type", fileType);
  385. }
  386. if (keyword) {
  387. query = query.where((builder) => {
  388. builder
  389. .where("file_name", "like", `%${keyword}%`)
  390. .orWhere("description", "like", `%${keyword}%`)
  391. .orWhere("tags", "like", `%${keyword}%`);
  392. });
  393. }
  394. // 获取总数
  395. const total = await query.clone().count();
  396. // 分页查询
  397. const files = await query.limit(pageSize).offset((page - 1) * pageSize);
  398. return c.json({
  399. message: "获取文件列表成功",
  400. data: {
  401. list: files,
  402. pagination: {
  403. current: page,
  404. pageSize,
  405. total: Number(total),
  406. },
  407. },
  408. });
  409. } catch (error) {
  410. log.api("获取文件列表失败:", error);
  411. return c.json({ error: "获取文件列表失败" }, 500);
  412. }
  413. });
  414. // 获取单个文件信息
  415. fileUploadRoutes.get("/:id", withAuth, async (c) => {
  416. try {
  417. const id = Number(c.req.param("id"));
  418. if (!id || isNaN(id)) {
  419. return c.json({ error: "无效的文件ID" }, 400);
  420. }
  421. const apiClient = c.get('apiClient');
  422. const file = await apiClient.database
  423. .table("file_library")
  424. .where("id", id)
  425. .where("is_deleted", DeleteStatus.NOT_DELETED)
  426. .first();
  427. if (!file) {
  428. return c.json({ error: "文件不存在" }, 404);
  429. }
  430. return c.json({
  431. message: "获取文件信息成功",
  432. data: file,
  433. });
  434. } catch (error) {
  435. log.api("获取文件信息失败:", error);
  436. return c.json({ error: "获取文件信息失败" }, 500);
  437. }
  438. });
  439. // 增加文件下载计数
  440. fileUploadRoutes.post("/:id/download", withAuth, async (c) => {
  441. try {
  442. const id = Number(c.req.param("id"));
  443. if (!id || isNaN(id)) {
  444. return c.json({ error: "无效的文件ID" }, 400);
  445. }
  446. const apiClient = c.get('apiClient');
  447. // 查询文件是否存在
  448. const file = await apiClient.database
  449. .table("file_library")
  450. .where("id", id)
  451. .where("is_deleted", DeleteStatus.NOT_DELETED)
  452. .first();
  453. if (!file) {
  454. return c.json({ error: "文件不存在" }, 404);
  455. }
  456. // 增加下载计数
  457. await apiClient.database
  458. .table("file_library")
  459. .where("id", id)
  460. .update({
  461. download_count: apiClient.database.raw("download_count + 1"),
  462. updated_at: apiClient.database.fn.now(),
  463. });
  464. return c.json({
  465. message: "更新下载计数成功",
  466. });
  467. } catch (error) {
  468. log.api("更新下载计数失败:", error);
  469. return c.json({ error: "更新下载计数失败" }, 500);
  470. }
  471. });
  472. // 删除文件
  473. fileUploadRoutes.delete("/:id", withAuth, async (c) => {
  474. try {
  475. const id = Number(c.req.param("id"));
  476. if (!id || isNaN(id)) {
  477. return c.json({ error: "无效的文件ID" }, 400);
  478. }
  479. const apiClient = c.get('apiClient');
  480. // 查询文件是否存在
  481. const file = await apiClient.database
  482. .table("file_library")
  483. .where("id", id)
  484. .where("is_deleted", DeleteStatus.NOT_DELETED)
  485. .first();
  486. if (!file) {
  487. return c.json({ error: "文件不存在" }, 404);
  488. }
  489. // 软删除文件
  490. await apiClient.database.table("file_library").where("id", id).update({
  491. is_deleted: DeleteStatus.DELETED,
  492. updated_at: apiClient.database.fn.now(),
  493. });
  494. return c.json({
  495. message: "文件删除成功",
  496. });
  497. } catch (error) {
  498. log.api("删除文件失败:", error);
  499. return c.json({ error: "删除文件失败" }, 500);
  500. }
  501. });
  502. return fileUploadRoutes;
  503. }
  504. // 创建主题设置路由
  505. export function createThemeRoutes(withAuth: WithAuth) {
  506. const themeRoutes = new Hono<{ Variables: Variables }>();
  507. // 获取当前主题设置
  508. themeRoutes.get("/", withAuth, async (c) => {
  509. try {
  510. const apiClient = c.get('apiClient');
  511. const user = c.get('user');
  512. if (!user) {
  513. return c.json({ error: "未授权访问" }, 401);
  514. }
  515. // 获取用户的主题设置
  516. let themeSettings = await apiClient.database
  517. .table("theme_settings")
  518. .where("user_id", user.id)
  519. .first();
  520. // 如果用户没有主题设置,则创建默认设置
  521. if (!themeSettings) {
  522. const defaultSettings = {
  523. theme_mode: ThemeMode.LIGHT,
  524. primary_color: '#1890ff',
  525. font_size: FontSize.MEDIUM,
  526. is_compact: CompactMode.NORMAL
  527. };
  528. const [id] = await apiClient.database.table("theme_settings").insert({
  529. user_id: user.id,
  530. settings: defaultSettings,
  531. created_at: apiClient.database.fn.now(),
  532. updated_at: apiClient.database.fn.now(),
  533. });
  534. themeSettings = await apiClient.database
  535. .table("theme_settings")
  536. .where("id", id)
  537. .first();
  538. }
  539. return c.json({
  540. message: "获取主题设置成功",
  541. data: themeSettings?.settings,
  542. });
  543. } catch (error) {
  544. log.api("获取主题设置失败:", error);
  545. return c.json({ error: "获取主题设置失败" }, 500);
  546. }
  547. });
  548. // 更新主题设置
  549. themeRoutes.put("/", withAuth, async (c) => {
  550. try {
  551. const user = c.get('user');
  552. const apiClient = c.get('apiClient');
  553. if (!user) {
  554. return c.json({ error: "未授权访问" }, 401);
  555. }
  556. const themeData = (await c.req.json()) as Partial<ThemeSettings>;
  557. // 检查用户是否已有主题设置
  558. const existingTheme = await apiClient.database
  559. .table("theme_settings")
  560. .where("user_id", user.id)
  561. .first();
  562. if (existingTheme) {
  563. // 更新现有设置
  564. const currentSettings = existingTheme.settings || {};
  565. const updatedSettings = {
  566. ...currentSettings,
  567. ...themeData
  568. };
  569. await apiClient.database
  570. .table("theme_settings")
  571. .where("user_id", user.id)
  572. .update({
  573. settings: JSON.stringify(updatedSettings),
  574. updated_at: apiClient.database.fn.now(),
  575. });
  576. } else {
  577. // 创建新设置
  578. const defaultSettings = {
  579. theme_mode: ThemeMode.LIGHT,
  580. primary_color: '#1890ff',
  581. font_size: FontSize.MEDIUM,
  582. is_compact: CompactMode.NORMAL
  583. };
  584. const updatedSettings = {
  585. ...defaultSettings,
  586. ...themeData
  587. };
  588. await apiClient.database.table("theme_settings").insert({
  589. user_id: user.id,
  590. settings: updatedSettings,
  591. created_at: apiClient.database.fn.now(),
  592. updated_at: apiClient.database.fn.now(),
  593. });
  594. }
  595. // 获取更新后的主题设置
  596. const updatedTheme = await apiClient.database
  597. .table("theme_settings")
  598. .where("user_id", user.id)
  599. .first();
  600. return c.json({
  601. message: "主题设置更新成功",
  602. data: updatedTheme,
  603. });
  604. } catch (error) {
  605. log.api("更新主题设置失败:", error);
  606. return c.json({ error: "更新主题设置失败" }, 500);
  607. }
  608. });
  609. // 重置主题设置为默认值
  610. themeRoutes.post("/reset", withAuth, async (c) => {
  611. try {
  612. const user = c.get('user');
  613. const apiClient = c.get('apiClient');
  614. if (!user) {
  615. return c.json({ error: "未授权访问" }, 401);
  616. }
  617. // 默认主题设置
  618. const defaultSettings = {
  619. theme_mode: ThemeMode.LIGHT,
  620. primary_color: '#1890ff',
  621. font_size: FontSize.MEDIUM,
  622. is_compact: CompactMode.NORMAL
  623. };
  624. // 更新用户的主题设置
  625. await apiClient.database
  626. .table("theme_settings")
  627. .where("user_id", user.id)
  628. .update({
  629. settings: JSON.stringify(defaultSettings),
  630. updated_at: apiClient.database.fn.now(),
  631. });
  632. // 获取更新后的主题设置
  633. const updatedTheme = await apiClient.database
  634. .table("theme_settings")
  635. .where("user_id", user.id)
  636. .first();
  637. return c.json({
  638. message: "主题设置已重置为默认值",
  639. data: updatedTheme,
  640. });
  641. } catch (error) {
  642. log.api("重置主题设置失败:", error);
  643. return c.json({ error: "重置主题设置失败" }, 500);
  644. }
  645. });
  646. return themeRoutes;
  647. }
  648. // 创建系统设置路由
  649. export function createSystemSettingsRoutes(withAuth: WithAuth) {
  650. const settingsRoutes = new Hono<{ Variables: Variables }>();
  651. // 获取所有系统设置(按分组)
  652. settingsRoutes.get('/', withAuth, async (c) => {
  653. try {
  654. const apiClient = c.get('apiClient');
  655. const settings = await apiClient.database
  656. .table('system_settings')
  657. .select('*');
  658. // 按分组整理数据
  659. const groupedSettings = settings.reduce((acc: SystemSettingGroupData[], setting) => {
  660. const groupIndex = acc.findIndex((g: SystemSettingGroupData) => g.name === setting.group);
  661. if (groupIndex === -1) {
  662. acc.push({
  663. name: setting.group,
  664. description: `${setting.group}组设置`,
  665. settings: [{
  666. id: setting.id,
  667. key: setting.key,
  668. value: setting.value,
  669. description: setting.description,
  670. group: setting.group
  671. }]
  672. });
  673. } else {
  674. acc[groupIndex].settings.push({
  675. id: setting.id,
  676. key: setting.key,
  677. value: setting.value,
  678. description: setting.description,
  679. group: setting.group
  680. });
  681. }
  682. return acc;
  683. }, []);
  684. return c.json({
  685. message: '获取系统设置成功',
  686. data: groupedSettings
  687. });
  688. } catch (error) {
  689. log.api('获取系统设置失败:', error);
  690. return c.json({ error: '获取系统设置失败' }, 500);
  691. }
  692. });
  693. // 获取指定分组的系统设置
  694. settingsRoutes.get('/group/:group', withAuth, async (c) => {
  695. try {
  696. const apiClient = c.get('apiClient');
  697. const group = c.req.param('group');
  698. const settings = await apiClient.database
  699. .table('system_settings')
  700. .where('group', group)
  701. .select('*');
  702. return c.json({
  703. message: '获取分组设置成功',
  704. data: settings
  705. });
  706. } catch (error) {
  707. log.api('获取分组设置失败:', error);
  708. return c.json({ error: '获取分组设置失败' }, 500);
  709. }
  710. });
  711. // 更新系统设置
  712. settingsRoutes.put('/:key', withAuth, async (c) => {
  713. try {
  714. const apiClient = c.get('apiClient');
  715. const key = c.req.param('key');
  716. const settingData = await c.req.json();
  717. // 验证设置是否存在
  718. const existingSetting = await apiClient.database
  719. .table('system_settings')
  720. .where('key', key)
  721. .first();
  722. if (!existingSetting) {
  723. return c.json({ error: '设置项不存在' }, 404);
  724. }
  725. // 更新设置
  726. await apiClient.database
  727. .table('system_settings')
  728. .where('key', key)
  729. .update({
  730. value: settingData.value,
  731. updated_at: apiClient.database.fn.now()
  732. });
  733. // 获取更新后的设置
  734. const updatedSetting = await apiClient.database
  735. .table('system_settings')
  736. .where('key', key)
  737. .first();
  738. return c.json({
  739. message: '系统设置已更新',
  740. data: updatedSetting
  741. });
  742. } catch (error) {
  743. log.api('更新系统设置失败:', error);
  744. return c.json({ error: '更新系统设置失败' }, 500);
  745. }
  746. });
  747. // 批量更新系统设置
  748. settingsRoutes.put('/', withAuth, async (c) => {
  749. try {
  750. const apiClient = c.get('apiClient');
  751. const settingsData = await c.req.json();
  752. // 验证数据格式
  753. if (!Array.isArray(settingsData)) {
  754. return c.json({ error: '无效的请求数据格式,应为数组' }, 400);
  755. }
  756. const trxProvider = apiClient.database.transactionProvider();
  757. const trx = await trxProvider();
  758. for (const setting of settingsData) {
  759. if (!setting.key) continue;
  760. // 验证设置是否存在
  761. const existingSetting = await trx.table('system_settings')
  762. .where('key', setting.key)
  763. .first();
  764. if (!existingSetting) {
  765. throw new Error(`设置项 ${setting.key} 不存在`);
  766. }
  767. // 更新设置
  768. await trx.table('system_settings')
  769. .where('key', setting.key)
  770. .update({
  771. value: setting.value,
  772. updated_at: trx.fn.now()
  773. });
  774. }
  775. await trx.commit();
  776. // 获取所有更新后的设置
  777. const updatedSettings = await apiClient.database
  778. .table('system_settings')
  779. .whereIn('key', settingsData.map(s => s.key))
  780. .select('*');
  781. return c.json({
  782. message: '系统设置已批量更新',
  783. data: updatedSettings
  784. });
  785. } catch (error) {
  786. log.api('批量更新系统设置失败:', error);
  787. return c.json({ error: '批量更新系统设置失败' }, 500);
  788. }
  789. });
  790. // 重置系统设置
  791. settingsRoutes.post('/reset', withAuth, async (c) => {
  792. try {
  793. const apiClient = c.get('apiClient');
  794. // 重置为迁移文件中定义的初始值
  795. const trxProvider = apiClient.database.transactionProvider();
  796. const trx = await trxProvider();
  797. // 清空现有设置
  798. await trx.table('system_settings').delete();
  799. // 插入默认设置
  800. await trx.table('system_settings').insert([
  801. // 基础设置组
  802. {
  803. key: 'SITE_NAME',
  804. value: '应用管理系统',
  805. description: '站点名称',
  806. group: 'basic',
  807. created_at: apiClient.database.fn.now(),
  808. updated_at: apiClient.database.fn.now()
  809. },
  810. {
  811. key: 'SITE_DESCRIPTION',
  812. value: '一个强大的应用管理系统',
  813. description: '站点描述',
  814. group: 'basic',
  815. created_at: apiClient.database.fn.now(),
  816. updated_at: apiClient.database.fn.now()
  817. },
  818. {
  819. key: 'SITE_KEYWORDS',
  820. value: '应用管理,系统管理,后台管理',
  821. description: '站点关键词',
  822. group: 'basic',
  823. created_at: apiClient.database.fn.now(),
  824. updated_at: apiClient.database.fn.now()
  825. },
  826. // 功能设置组
  827. {
  828. key: 'ENABLE_REGISTER',
  829. value: 'true',
  830. description: '是否开启注册',
  831. group: 'feature',
  832. created_at: apiClient.database.fn.now(),
  833. updated_at: apiClient.database.fn.now()
  834. },
  835. {
  836. key: 'ENABLE_CAPTCHA',
  837. value: 'true',
  838. description: '是否开启验证码',
  839. group: 'feature',
  840. created_at: apiClient.database.fn.now(),
  841. updated_at: apiClient.database.fn.now()
  842. },
  843. {
  844. key: 'LOGIN_ATTEMPTS',
  845. value: '5',
  846. description: '登录尝试次数',
  847. group: 'feature',
  848. created_at: apiClient.database.fn.now(),
  849. updated_at: apiClient.database.fn.now()
  850. },
  851. // 上传设置组
  852. {
  853. key: 'UPLOAD_MAX_SIZE',
  854. value: '10',
  855. description: '最大上传大小(MB)',
  856. group: 'upload',
  857. created_at: apiClient.database.fn.now(),
  858. updated_at: apiClient.database.fn.now()
  859. },
  860. {
  861. key: 'ALLOWED_FILE_TYPES',
  862. value: 'jpg,jpeg,png,gif,doc,docx,xls,xlsx,pdf',
  863. description: '允许的文件类型',
  864. group: 'upload',
  865. created_at: apiClient.database.fn.now(),
  866. updated_at: apiClient.database.fn.now()
  867. },
  868. // 通知设置组
  869. {
  870. key: 'NOTIFY_ON_LOGIN',
  871. value: 'true',
  872. description: '登录通知',
  873. group: 'notify',
  874. created_at: apiClient.database.fn.now(),
  875. updated_at: apiClient.database.fn.now()
  876. },
  877. {
  878. key: 'NOTIFY_ON_ERROR',
  879. value: 'true',
  880. description: '错误通知',
  881. group: 'notify',
  882. created_at: apiClient.database.fn.now(),
  883. updated_at: apiClient.database.fn.now()
  884. }
  885. ]);
  886. await trx.commit();
  887. const resetSettings = await apiClient.database
  888. .table('system_settings')
  889. .select('*');
  890. return c.json({
  891. message: '系统设置已重置',
  892. data: resetSettings
  893. });
  894. } catch (error) {
  895. log.api('重置系统设置失败:', error);
  896. return c.json({ error: '重置系统设置失败' }, 500);
  897. }
  898. });
  899. return settingsRoutes;
  900. }
  901. import { Organization } from "../client/share/types.ts";
  902. // 创建机构管理路由
  903. export function createOrganizationRoutes(withAuth: WithAuth) {
  904. const organizationRoutes = new Hono<{ Variables: Variables }>();
  905. // 获取机构树形列表
  906. organizationRoutes.get("/tree", withAuth, async (c) => {
  907. try {
  908. const apiClient = c.get('apiClient');
  909. // 获取所有机构数据
  910. const organizations = await apiClient.database
  911. .table("organizations")
  912. .where("is_deleted", DeleteStatus.NOT_DELETED)
  913. .orderBy("id", "asc");
  914. // 构建树形结构
  915. const buildTree = (parentId?: number): Organization[] => {
  916. return organizations
  917. .filter((org: any) => org.parent_id === parentId)
  918. .map((org: any): Organization => ({
  919. id: org.id,
  920. name: org.name,
  921. code: org.code,
  922. parentId: org.parent_id,
  923. description: org.description,
  924. children: buildTree(org.id)
  925. }));
  926. };
  927. const treeData = buildTree();
  928. return c.json({
  929. message: "获取机构树成功",
  930. data: treeData
  931. });
  932. } catch (error) {
  933. log.api("获取机构树失败:", error);
  934. return c.json({ error: "获取机构树失败" }, 500);
  935. }
  936. });
  937. // 创建新机构
  938. organizationRoutes.post("/", withAuth, async (c) => {
  939. try {
  940. const apiClient = c.get('apiClient');
  941. const user = c.get('user');
  942. const orgData = (await c.req.json()) as Partial<Organization>;
  943. // 验证必填字段
  944. if (!orgData.name) {
  945. return c.json({ error: "机构名称不能为空" }, 400);
  946. }
  947. if (!orgData.code) {
  948. return c.json({ error: "机构编码不能为空" }, 400);
  949. }
  950. // 插入新机构
  951. const [id] = await apiClient.database.table("organizations").insert({
  952. ...orgData,
  953. created_by: user?.id,
  954. updated_by: user?.id,
  955. is_deleted: DeleteStatus.NOT_DELETED,
  956. created_at: apiClient.database.fn.now(),
  957. updated_at: apiClient.database.fn.now()
  958. });
  959. // 获取创建的机构
  960. const [createdOrg] = await apiClient.database
  961. .table("organizations")
  962. .where("id", id);
  963. return c.json({
  964. message: "机构创建成功",
  965. data: createdOrg
  966. });
  967. } catch (error) {
  968. log.api("创建机构失败:", error);
  969. return c.json({ error: "创建机构失败" }, 500);
  970. }
  971. });
  972. // 更新机构信息
  973. organizationRoutes.put("/:id", withAuth, async (c) => {
  974. try {
  975. const apiClient = c.get('apiClient');
  976. const user = c.get('user');
  977. const id = Number(c.req.param("id"));
  978. const orgData = (await c.req.json()) as Partial<Organization>;
  979. if (!id || isNaN(id)) {
  980. return c.json({ error: "无效的机构ID" }, 400);
  981. }
  982. // 验证必填字段
  983. if (!orgData.name) {
  984. return c.json({ error: "机构名称不能为空" }, 400);
  985. }
  986. // 检查机构是否存在
  987. const [existingOrg] = await apiClient.database
  988. .table("organizations")
  989. .where({ id, is_deleted: DeleteStatus.NOT_DELETED });
  990. if (!existingOrg) {
  991. return c.json({ error: "机构不存在" }, 404);
  992. }
  993. // 更新机构信息
  994. await apiClient.database
  995. .table("organizations")
  996. .where("id", id)
  997. .update({
  998. ...orgData,
  999. updated_by: user?.id,
  1000. updated_at: apiClient.database.fn.now()
  1001. });
  1002. // 获取更新后的机构
  1003. const [updatedOrg] = await apiClient.database
  1004. .table("organizations")
  1005. .where("id", id);
  1006. return c.json({
  1007. message: "机构更新成功",
  1008. data: updatedOrg
  1009. });
  1010. } catch (error) {
  1011. log.api("更新机构失败:", error);
  1012. return c.json({ error: "更新机构失败" }, 500);
  1013. }
  1014. });
  1015. // 删除机构(软删除)
  1016. organizationRoutes.delete("/:id", withAuth, async (c) => {
  1017. try {
  1018. const apiClient = c.get('apiClient');
  1019. const user = c.get('user');
  1020. const id = Number(c.req.param("id"));
  1021. if (!id || isNaN(id)) {
  1022. return c.json({ error: "无效的机构ID" }, 400);
  1023. }
  1024. // 检查机构是否存在
  1025. const [existingOrg] = await apiClient.database
  1026. .table("organizations")
  1027. .where({ id, is_deleted: DeleteStatus.NOT_DELETED });
  1028. if (!existingOrg) {
  1029. return c.json({ error: "机构不存在" }, 404);
  1030. }
  1031. // 检查是否有子机构
  1032. const [hasChildren] = await apiClient.database
  1033. .table("organizations")
  1034. .where({ parent_id: id, is_deleted: DeleteStatus.NOT_DELETED })
  1035. .limit(1);
  1036. if (hasChildren) {
  1037. return c.json({ error: "该机构下有子机构,不能删除" }, 400);
  1038. }
  1039. // 软删除机构
  1040. await apiClient.database
  1041. .table("organizations")
  1042. .where("id", id)
  1043. .update({
  1044. is_deleted: DeleteStatus.DELETED,
  1045. updated_by: user?.id,
  1046. updated_at: apiClient.database.fn.now()
  1047. });
  1048. return c.json({
  1049. message: "机构删除成功"
  1050. });
  1051. } catch (error) {
  1052. log.api("删除机构失败:", error);
  1053. return c.json({ error: "删除机构失败" }, 500);
  1054. }
  1055. });
  1056. return organizationRoutes;
  1057. }