将routes_sys.ts拆分为5个独立的路由文件
This commit is contained in:
@@ -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,
|
||||
|
||||
147
server/routes_file_category.ts
Normal file
147
server/routes_file_category.ts
Normal file
@@ -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<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;
|
||||
}
|
||||
258
server/routes_file_upload.ts
Normal file
258
server/routes_file_upload.ts
Normal file
@@ -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<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;
|
||||
}
|
||||
228
server/routes_know_info.ts
Normal file
228
server/routes_know_info.ts
Normal file
@@ -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<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;
|
||||
}
|
||||
1063
server/routes_sys.ts
1063
server/routes_sys.ts
File diff suppressed because it is too large
Load Diff
296
server/routes_system_settings.ts
Normal file
296
server/routes_system_settings.ts
Normal file
@@ -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;
|
||||
}
|
||||
186
server/routes_theme.ts
Normal file
186
server/routes_theme.ts
Normal file
@@ -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<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;
|
||||
}
|
||||
Reference in New Issue
Block a user