15 changed files with 1043 additions and 12 deletions
-
4collector/utils/files/index.js
-
58server/endpoints/admin.js
-
32server/endpoints/dept.js
-
157server/endpoints/deptDocument.js
-
146server/endpoints/deptUsers.js
-
1server/endpoints/system.js
-
91server/endpoints/workspaces.js
-
4server/index.js
-
41server/models/dept.js
-
225server/models/deptDocument.js
-
167server/models/deptUsers.js
-
16server/prisma/migrations/20250226090554_init/migration.sql
-
27server/prisma/schema.prisma
-
33server/utils/files/index.js
-
53server/utils/files/multer.js
@ -0,0 +1,157 @@ |
|||
const { DeptDocument } = require("../models/deptDocument"); |
|||
const { validatedRequest } = require("../utils/middleware/validatedRequest"); |
|||
const { |
|||
strictMultiUserRoleValid, |
|||
ROLES, |
|||
} = require("../utils/middleware/multiUserProtected"); |
|||
|
|||
function deptDocumentEndpoints(app) { |
|||
if (!app) return; |
|||
|
|||
// 获取部门文档列表
|
|||
app.get( |
|||
"/deptDocument/list", |
|||
[validatedRequest, strictMultiUserRoleValid([ROLES.admin, ROLES.manager])], |
|||
async (_request, response) => { |
|||
try { |
|||
const deptDocuments = await DeptDocument.where(); |
|||
response.status(200).json({ deptDocuments }); |
|||
} catch (e) { |
|||
console.error(e); |
|||
response.sendStatus(500).end(); |
|||
} |
|||
} |
|||
); |
|||
|
|||
// 添加部门文档
|
|||
app.post( |
|||
"/deptDocument/add", |
|||
[validatedRequest, strictMultiUserRoleValid([ROLES.admin, ROLES.manager])], |
|||
async (request, response) => { |
|||
try { |
|||
const documentData = request.body; // 获取请求体中的文档数据
|
|||
|
|||
// 检查文档路径是否唯一
|
|||
const isDocpathUnique = await DeptDocument.checkDocpathUnique( |
|||
documentData.docpath |
|||
); |
|||
if (!isDocpathUnique) { |
|||
return response.status(400).json({ |
|||
success: false, |
|||
message: `文档路径 '${documentData.docpath}' 已存在`, |
|||
}); |
|||
} |
|||
|
|||
// 插入文档数据
|
|||
const { deptDocument, error } = await DeptDocument.create(documentData); |
|||
if (error) { |
|||
return response.status(500).json({ |
|||
success: false, |
|||
message: "添加部门文档失败", |
|||
error: error, |
|||
}); |
|||
} |
|||
|
|||
// 返回成功响应
|
|||
response.status(200).json({ |
|||
success: true, |
|||
data: deptDocument, |
|||
}); |
|||
} catch (error) { |
|||
console.error("添加部门文档失败:", error); |
|||
response.status(500).json({ |
|||
success: false, |
|||
message: "添加部门文档失败,服务器内部错误", |
|||
}); |
|||
} |
|||
} |
|||
); |
|||
|
|||
// 编辑部门文档
|
|||
app.post( |
|||
"/deptDocument/edit", |
|||
[validatedRequest, strictMultiUserRoleValid([ROLES.admin, ROLES.manager])], |
|||
async (request, response) => { |
|||
try { |
|||
const documentData = request.body; // 获取请求体中的文档数据
|
|||
|
|||
// 检查文档是否存在
|
|||
const existingDocument = await DeptDocument.get({ id: documentData.id }); |
|||
if (!existingDocument) { |
|||
return response.status(404).json({ |
|||
success: false, |
|||
message: "文档不存在", |
|||
}); |
|||
} |
|||
|
|||
// 更新文档数据
|
|||
const { success, error, deptDocument } = await DeptDocument.update( |
|||
documentData.id, |
|||
documentData |
|||
); |
|||
if (!success) { |
|||
return response.status(500).json({ |
|||
success: false, |
|||
message: "编辑部门文档失败", |
|||
error: error, |
|||
}); |
|||
} |
|||
|
|||
// 返回成功响应
|
|||
response.status(200).json({ |
|||
success: true, |
|||
data: deptDocument, |
|||
}); |
|||
} catch (error) { |
|||
console.error("编辑部门文档失败:", error); |
|||
response.status(500).json({ |
|||
success: false, |
|||
message: "编辑部门文档失败,服务器内部错误", |
|||
}); |
|||
} |
|||
} |
|||
); |
|||
|
|||
// 删除部门文档
|
|||
app.delete( |
|||
"/deptDocument/:id", |
|||
[validatedRequest, strictMultiUserRoleValid([ROLES.admin, ROLES.manager])], |
|||
async (request, response) => { |
|||
try { |
|||
const documentId = parseInt(request.params.id); // 获取文档 ID
|
|||
|
|||
// 检查文档是否存在
|
|||
const existingDocument = await DeptDocument.get({ id: documentId }); |
|||
if (!existingDocument) { |
|||
return response.status(404).json({ |
|||
success: false, |
|||
message: "文档不存在", |
|||
}); |
|||
} |
|||
|
|||
// 删除文档
|
|||
const success = await DeptDocument.delete({ id: documentId }); |
|||
if (!success) { |
|||
return response.status(500).json({ |
|||
success: false, |
|||
message: "删除部门文档失败", |
|||
}); |
|||
} |
|||
|
|||
// 返回成功响应
|
|||
response.status(200).json({ |
|||
success: true, |
|||
message: "文档删除成功", |
|||
}); |
|||
} catch (error) { |
|||
console.error("删除部门文档失败:", error); |
|||
response.status(500).json({ |
|||
success: false, |
|||
message: "删除部门文档失败,服务器内部错误", |
|||
}); |
|||
} |
|||
} |
|||
); |
|||
} |
|||
|
|||
module.exports = { deptDocumentEndpoints }; |
|||
@ -0,0 +1,146 @@ |
|||
const { DeptUsers } = require("../models/deptUsers"); |
|||
const { validatedRequest } = require("../utils/middleware/validatedRequest"); |
|||
const { |
|||
strictMultiUserRoleValid, |
|||
ROLES, |
|||
} = require("../utils/middleware/multiUserProtected"); |
|||
|
|||
function deptUsersEndpoints(app) { |
|||
if (!app) return; |
|||
|
|||
// 获取部门用户关联列表
|
|||
app.get( |
|||
"/deptUsers/list", |
|||
[validatedRequest, strictMultiUserRoleValid([ROLES.admin])], |
|||
async (_request, response) => { |
|||
try { |
|||
const deptUsers = await DeptUsers.where(); |
|||
response.status(200).json({ deptUsers }); |
|||
} catch (e) { |
|||
console.error(e); |
|||
response.sendStatus(500).end(); |
|||
} |
|||
} |
|||
); |
|||
|
|||
// 添加部门用户关联
|
|||
app.post( |
|||
"/deptUsers/add", |
|||
[validatedRequest, strictMultiUserRoleValid([ROLES.admin])], |
|||
async (request, response) => { |
|||
try { |
|||
const deptUserData = request.body; // 获取请求体中的数据
|
|||
|
|||
// 插入部门用户关联数据
|
|||
const { deptUser, error } = await DeptUsers.create(deptUserData); |
|||
if (error) { |
|||
return response.status(500).json({ |
|||
success: false, |
|||
message: "添加部门用户关联失败", |
|||
error: error, |
|||
}); |
|||
} |
|||
|
|||
// 返回成功响应
|
|||
response.status(200).json({ |
|||
success: true, |
|||
data: deptUser, |
|||
}); |
|||
} catch (error) { |
|||
console.error("添加部门用户关联失败:", error); |
|||
response.status(500).json({ |
|||
success: false, |
|||
message: "添加部门用户关联失败,服务器内部错误", |
|||
}); |
|||
} |
|||
} |
|||
); |
|||
|
|||
// 编辑部门用户关联
|
|||
app.post( |
|||
"/deptUsers/edit", |
|||
[validatedRequest, strictMultiUserRoleValid([ROLES.admin])], |
|||
async (request, response) => { |
|||
try { |
|||
const deptUserData = request.body; // 获取请求体中的数据
|
|||
|
|||
// 检查关联是否存在
|
|||
const existingDeptUser = await DeptUsers.get({ id: deptUserData.id }); |
|||
if (!existingDeptUser) { |
|||
return response.status(404).json({ |
|||
success: false, |
|||
message: "部门用户关联不存在", |
|||
}); |
|||
} |
|||
|
|||
// 更新部门用户关联
|
|||
const { success, error, deptUser } = await DeptUsers.update( |
|||
deptUserData.id, |
|||
deptUserData |
|||
); |
|||
if (!success) { |
|||
return response.status(500).json({ |
|||
success: false, |
|||
message: "编辑部门用户关联失败", |
|||
error: error, |
|||
}); |
|||
} |
|||
|
|||
// 返回成功响应
|
|||
response.status(200).json({ |
|||
success: true, |
|||
data: deptUser, |
|||
}); |
|||
} catch (error) { |
|||
console.error("编辑部门用户关联失败:", error); |
|||
response.status(500).json({ |
|||
success: false, |
|||
message: "编辑部门用户关联失败,服务器内部错误", |
|||
}); |
|||
} |
|||
} |
|||
); |
|||
|
|||
// 删除部门用户关联
|
|||
app.delete( |
|||
"/deptUsers/:id", |
|||
[validatedRequest, strictMultiUserRoleValid([ROLES.admin])], |
|||
async (request, response) => { |
|||
try { |
|||
const id = parseInt(request.params.id); // 获取关联 ID
|
|||
|
|||
// 检查关联是否存在
|
|||
const existingDeptUser = await DeptUsers.get({ id }); |
|||
if (!existingDeptUser) { |
|||
return response.status(404).json({ |
|||
success: false, |
|||
message: "部门用户关联不存在", |
|||
}); |
|||
} |
|||
|
|||
// 删除部门用户关联
|
|||
const success = await DeptUsers.delete({ id }); |
|||
if (!success) { |
|||
return response.status(500).json({ |
|||
success: false, |
|||
message: "删除部门用户关联失败", |
|||
}); |
|||
} |
|||
|
|||
// 返回成功响应
|
|||
response.status(200).json({ |
|||
success: true, |
|||
message: "部门用户关联删除成功", |
|||
}); |
|||
} catch (error) { |
|||
console.error("删除部门用户关联失败:", error); |
|||
response.status(500).json({ |
|||
success: false, |
|||
message: "删除部门用户关联失败,服务器内部错误", |
|||
}); |
|||
} |
|||
} |
|||
); |
|||
} |
|||
|
|||
module.exports = { deptUsersEndpoints }; |
|||
@ -0,0 +1,225 @@ |
|||
const prisma = require("../utils/prisma"); |
|||
|
|||
/** |
|||
* @typedef {Object} DeptDocument |
|||
* @property {number} id |
|||
* @property {number} deptId |
|||
* @property {string} realDocId |
|||
* @property {string} filename |
|||
* @property {string} docpath |
|||
* @property {string} [metadata] |
|||
* @property {string} [tag] |
|||
* @property {string} realFilename |
|||
* @property {boolean} public |
|||
* @property {Date} createdAt |
|||
* @property {Date} lastUpdatedAt |
|||
*/ |
|||
|
|||
const DeptDocument = { |
|||
writable: [ |
|||
"deptId", |
|||
"parsedFileName", |
|||
"parsedFilePath", |
|||
"realFileName", |
|||
"realFileAlias", |
|||
"realFilePath", |
|||
"isPublic", |
|||
"tags", |
|||
"delTag", |
|||
], |
|||
validations: { |
|||
filename: (newValue = "") => { |
|||
if (typeof newValue !== "string" || newValue.length > 255) { |
|||
throw new Error( |
|||
"Filename must be a string and cannot be longer than 255 characters" |
|||
); |
|||
} |
|||
return newValue; |
|||
}, |
|||
docpath: (newValue = "") => { |
|||
if (typeof newValue !== "string" || newValue.length > 255) { |
|||
throw new Error( |
|||
"Document path must be a string and cannot be longer than 255 characters" |
|||
); |
|||
} |
|||
return newValue; |
|||
}, |
|||
public: (newValue = false) => { |
|||
if (typeof newValue !== "boolean") { |
|||
throw new Error("Public must be a boolean"); |
|||
} |
|||
return newValue; |
|||
}, |
|||
}, |
|||
castColumnValue: function (key, value) { |
|||
switch (key) { |
|||
case "public": |
|||
return Boolean(value); |
|||
default: |
|||
return value; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 创建部门文档 |
|||
* @param {Object} data - 部门文档数据 |
|||
* @returns {Promise<{ deptDocument: DeptDocument | null, error: string | null }>} |
|||
*/ |
|||
create: async function (data) { |
|||
try { |
|||
const validatedData = {}; |
|||
for (const key of this.writable) { |
|||
if (data[key] !== undefined) { |
|||
if (this.validations[key]) { |
|||
validatedData[key] = this.validations[key](data[key]); |
|||
} else { |
|||
validatedData[key] = this.castColumnValue(key, data[key]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
const deptDocument = await prisma.dept_document.create({ |
|||
data: { |
|||
...validatedData, |
|||
createdAt: new Date(), |
|||
lastUpdatedAt: new Date(), |
|||
}, |
|||
}); |
|||
|
|||
return { deptDocument, error: null }; |
|||
} catch (error) { |
|||
console.error("FAILED TO CREATE DEPT DOCUMENT.", error.message); |
|||
return { deptDocument: null, error: error.message }; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 更新部门文档 |
|||
* @param {number} id - 文档 ID |
|||
* @param {Object} updates - 更新的字段 |
|||
* @returns {Promise<{ success: boolean, error: string | null, deptDocument: DeptDocument | null }>} |
|||
*/ |
|||
update: async function (id, updates = {}) { |
|||
try { |
|||
if (!id) throw new Error("No document id provided for update"); |
|||
|
|||
const currentDocument = await prisma.dept_document.findUnique({ |
|||
where: { id }, |
|||
}); |
|||
if (!currentDocument) throw new Error("Document not found"); |
|||
|
|||
const validatedUpdates = {}; |
|||
for (const key of this.writable) { |
|||
if (updates[key] !== undefined) { |
|||
if (this.validations[key]) { |
|||
validatedUpdates[key] = this.validations[key](updates[key]); |
|||
} else { |
|||
validatedUpdates[key] = this.castColumnValue(key, updates[key]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
validatedUpdates.lastUpdatedAt = new Date(); |
|||
|
|||
const updatedDocument = await prisma.dept_document.update({ |
|||
where: { id }, |
|||
data: validatedUpdates, |
|||
}); |
|||
|
|||
return { success: true, error: null, deptDocument: updatedDocument }; |
|||
} catch (error) { |
|||
console.error(error.message); |
|||
return { success: false, error: error.message, deptDocument: null }; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 获取部门文档 |
|||
* @param {Object} clause - 查询条件 |
|||
* @returns {Promise<{ deptDocument: DeptDocument | null }>} |
|||
*/ |
|||
get: async function (clause = {}) { |
|||
try { |
|||
const deptDocument = await prisma.dept_document.findFirst({ |
|||
where: clause, |
|||
}); |
|||
return deptDocument ? { deptDocument } : null; |
|||
} catch (error) { |
|||
console.error(error.message); |
|||
return null; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 删除部门文档 |
|||
* @param {Object} clause - 删除条件 |
|||
* @returns {Promise<boolean>} |
|||
*/ |
|||
delete: async function (clause = {}) { |
|||
try { |
|||
const affectedRows = await prisma.dept_document.deleteMany({ |
|||
where: clause, |
|||
}); |
|||
return affectedRows.count > 0; |
|||
} catch (error) { |
|||
console.error(error.message); |
|||
return false; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 查询部门文档列表 |
|||
* @param {Object} clause - 查询条件 |
|||
* @param {number} limit - 限制数量 |
|||
* @returns {Promise<DeptDocument[]>} |
|||
*/ |
|||
where: async function (clause = {}, limit = null) { |
|||
try { |
|||
const deptDocuments = await prisma.dept_document.findMany({ |
|||
where: clause, |
|||
take: limit !== null ? limit : undefined, |
|||
}); |
|||
return deptDocuments; |
|||
} catch (error) { |
|||
console.error(error.message); |
|||
return []; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 检查文档路径是否唯一 |
|||
* @param {string} docpath - 文档路径 |
|||
* @returns {Promise<boolean>} |
|||
*/ |
|||
checkDocpathUnique: async function (docpath) { |
|||
try { |
|||
const existingDocument = await prisma.dept_document.findFirst({ |
|||
where: { docpath }, |
|||
}); |
|||
return !existingDocument; |
|||
} catch (error) { |
|||
console.error("检查文档路径唯一性失败:", error); |
|||
throw error; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 检查文档是否属于指定部门 |
|||
* @param {number} id - 文档 ID |
|||
* @param {number} deptId - 部门 ID |
|||
* @returns {Promise<boolean>} |
|||
*/ |
|||
checkDocumentBelongsToDept: async function (id, deptId) { |
|||
try { |
|||
const document = await prisma.dept_document.findFirst({ |
|||
where: { id, deptId }, |
|||
}); |
|||
return !!document; |
|||
} catch (error) { |
|||
console.error("检查文档所属部门失败:", error); |
|||
throw error; |
|||
} |
|||
}, |
|||
}; |
|||
|
|||
module.exports = { DeptDocument }; |
|||
@ -0,0 +1,167 @@ |
|||
const prisma = require("../utils/prisma"); |
|||
|
|||
/** |
|||
* @typedef {Object} DeptUser |
|||
* @property {number} id |
|||
* @property {number} deptId |
|||
* @property {number} userId |
|||
* @property {Date} createdAt |
|||
* @property {Date} updatedAt |
|||
*/ |
|||
|
|||
const DeptUsers = { |
|||
writable: ["deptId", "userId"], |
|||
validations: { |
|||
deptId: (newValue) => { |
|||
const num = Number(newValue); |
|||
if (isNaN(num)) { |
|||
throw new Error("Dept ID must be a number"); |
|||
} |
|||
return num; |
|||
}, |
|||
userId: (newValue) => { |
|||
const num = Number(newValue); |
|||
if (isNaN(num)) { |
|||
throw new Error("User ID must be a number"); |
|||
} |
|||
return num; |
|||
}, |
|||
}, |
|||
castColumnValue: function (key, value) { |
|||
switch (key) { |
|||
case "deptId": |
|||
case "userId": |
|||
return Number(value); |
|||
default: |
|||
return value; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 创建部门用户关联 |
|||
* @param {Object} data - 部门用户数据 |
|||
* @returns {Promise<{ deptUser: DeptUser | null, error: string | null }>} |
|||
*/ |
|||
create: async function (data) { |
|||
try { |
|||
const validatedData = {}; |
|||
for (const key of this.writable) { |
|||
if (data[key] !== undefined) { |
|||
if (this.validations[key]) { |
|||
validatedData[key] = this.validations[key](data[key]); |
|||
} else { |
|||
validatedData[key] = this.castColumnValue(key, data[key]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
const deptUser = await prisma.dept_users.create({ |
|||
data: { |
|||
...validatedData, |
|||
createdAt: new Date(), |
|||
updatedAt: new Date(), |
|||
}, |
|||
}); |
|||
|
|||
return { deptUser, error: null }; |
|||
} catch (error) { |
|||
console.error("FAILED TO CREATE DEPT USER.", error.message); |
|||
return { deptUser: null, error: error.message }; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 更新部门用户关联 |
|||
* @param {number} id - 关联 ID |
|||
* @param {Object} updates - 更新的字段 |
|||
* @returns {Promise<{ success: boolean, error: string | null, deptUser: DeptUser | null }>} |
|||
*/ |
|||
update: async function (id, updates = {}) { |
|||
try { |
|||
if (!id) throw new Error("No ID provided for update"); |
|||
|
|||
const currentDeptUser = await prisma.dept_users.findUnique({ |
|||
where: { id }, |
|||
}); |
|||
if (!currentDeptUser) throw new Error("Dept user not found"); |
|||
|
|||
const validatedUpdates = {}; |
|||
for (const key of this.writable) { |
|||
if (updates[key] !== undefined) { |
|||
if (this.validations[key]) { |
|||
validatedUpdates[key] = this.validations[key](updates[key]); |
|||
} else { |
|||
validatedUpdates[key] = this.castColumnValue(key, updates[key]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
validatedUpdates.updatedAt = new Date(); |
|||
|
|||
const updatedDeptUser = await prisma.dept_users.update({ |
|||
where: { id }, |
|||
data: validatedUpdates, |
|||
}); |
|||
|
|||
return { success: true, error: null, deptUser: updatedDeptUser }; |
|||
} catch (error) { |
|||
console.error(error.message); |
|||
return { success: false, error: error.message, deptUser: null }; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 获取部门用户关联 |
|||
* @param {Object} clause - 查询条件 |
|||
* @returns {Promise<{ deptUser: DeptUser | null }>} |
|||
*/ |
|||
get: async function (clause = {}) { |
|||
try { |
|||
const deptUser = await prisma.dept_users.findFirst({ |
|||
where: clause, |
|||
}); |
|||
return deptUser ? { deptUser } : null; |
|||
} catch (error) { |
|||
console.error(error.message); |
|||
return null; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 删除部门用户关联 |
|||
* @param {Object} clause - 删除条件 |
|||
* @returns {Promise<boolean>} |
|||
*/ |
|||
delete: async function (clause = {}) { |
|||
try { |
|||
const affectedRows = await prisma.dept_users.deleteMany({ |
|||
where: clause, |
|||
}); |
|||
return affectedRows.count > 0; |
|||
} catch (error) { |
|||
console.error(error.message); |
|||
return false; |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 查询部门用户关联列表 |
|||
* @param {Object} clause - 查询条件 |
|||
* @param {number} limit - 限制数量 |
|||
* @returns {Promise<DeptUser[]>} |
|||
*/ |
|||
where: async function (clause = {}, limit = null) { |
|||
try { |
|||
const deptUsers = await prisma.dept_users.findMany({ |
|||
where: clause, |
|||
take: limit !== null ? limit : undefined, |
|||
}); |
|||
return deptUsers; |
|||
} catch (error) { |
|||
console.error(error.message); |
|||
return []; |
|||
} |
|||
}, |
|||
}; |
|||
|
|||
module.exports = { DeptUsers }; |
|||
@ -0,0 +1,16 @@ |
|||
-- CreateTable |
|||
CREATE TABLE "dept_document" ( |
|||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, |
|||
"deptId" INTEGER NOT NULL, |
|||
"parsedFileName" TEXT NOT NULL, |
|||
"parsedFilePath" TEXT NOT NULL, |
|||
"realFileName" TEXT NOT NULL, |
|||
"realFileAlias" TEXT NOT NULL, |
|||
"realFilePath" TEXT NOT NULL, |
|||
"isPublic" INTEGER, |
|||
"tags" TEXT, |
|||
"delTag" BOOLEAN NOT NULL DEFAULT false, |
|||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, |
|||
"lastUpdatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, |
|||
CONSTRAINT "dept_document_deptId_fkey" FOREIGN KEY ("deptId") REFERENCES "dept" ("deptId") ON DELETE CASCADE ON UPDATE CASCADE |
|||
); |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue