diff --git a/collector/utils/files/index.js b/collector/utils/files/index.js index edea280..705f999 100644 --- a/collector/utils/files/index.js +++ b/collector/utils/files/index.js @@ -72,8 +72,8 @@ function trashFile(filepath) { } catch { return; } - - fs.rmSync(filepath); + console.log("=====:::::", filepath); + // fs.rmSync(filepath); return; } diff --git a/server/endpoints/admin.js b/server/endpoints/admin.js index e4e556e..80bda1d 100644 --- a/server/endpoints/admin.js +++ b/server/endpoints/admin.js @@ -79,6 +79,64 @@ function adminEndpoints(app) { } ); + // app.post( + // "/admin/users/new", + // [validatedRequest, strictMultiUserRoleValid([ROLES.admin, ROLES.manager])], + // async (request, response) => { + // try { + // const currUser = await userFromSession(request, response); + // const newUserParams = reqBody(request); + // const roleValidation = validRoleSelection(currUser, newUserParams); + // + // // 验证角色权限 + // if (!roleValidation.valid) { + // response + // .status(200) + // .json({ user: null, error: roleValidation.error }); + // return; + // } + // + // // 使用事务确保原子性 + // const result = await prisma.$transaction(async (prisma) => { + // // 1. 创建用户 + // const newUser = await prisma.users.create({ + // data: newUserParams, + // }); + // + // // 2. 将用户和部门关联到 dept_users 表 + // if (newUserParams.deptId) { + // await prisma.dept_users.create({ + // data: { + // deptId: newUserParams.deptId, + // userId: newUser.id, + // createdAt: new Date(), + // updatedAt: new Date(), + // }, + // }); + // } + // + // // 3. 记录事件日志 + // await EventLogs.logEvent( + // "user_created", + // { + // userName: newUser.username, + // createdBy: currUser.username, + // }, + // currUser.id + // ); + // + // return { user: newUser, error: null }; + // }); + // + // // 返回成功响应 + // response.status(200).json(result); + // } catch (e) { + // console.error(e); + // response.status(500).json({ user: null, error: e.message }); + // } + // } + // ); + app.post( "/admin/user/:id", [validatedRequest, strictMultiUserRoleValid([ROLES.admin, ROLES.manager])], diff --git a/server/endpoints/dept.js b/server/endpoints/dept.js index 2de0e74..29eadc9 100644 --- a/server/endpoints/dept.js +++ b/server/endpoints/dept.js @@ -20,6 +20,38 @@ function deptEndpoints(app) { } } ); + // 获取部门树状结构 + app.get( + "/dept/tree", + [validatedRequest, strictMultiUserRoleValid([ROLES.admin])], + async (_request, response) => { + try { + const deptTree = await Dept.getDeptTree(); + response.status(200).json({ deptTree }); + } catch (e) { + console.error(e); + response.sendStatus(500).end(); + } + } + ); + + // 懒加载子部门列表 + app.get( + "/dept/children", + [validatedRequest, strictMultiUserRoleValid([ROLES.admin])], + async (request, response) => { + try { + const parentId = request.query.parentId + ? parseInt(request.query.parentId) + : null; + const children = await Dept.getChildrenByParentId(parentId); + response.status(200).json({ children }); + } catch (e) { + console.error(e); + response.sendStatus(500).end(); + } + } + ); app.post("/dept/add", [validatedRequest, strictMultiUserRoleValid([ROLES.admin])], diff --git a/server/endpoints/deptDocument.js b/server/endpoints/deptDocument.js new file mode 100644 index 0000000..24a28a7 --- /dev/null +++ b/server/endpoints/deptDocument.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 }; diff --git a/server/endpoints/deptUsers.js b/server/endpoints/deptUsers.js new file mode 100644 index 0000000..2c0408e --- /dev/null +++ b/server/endpoints/deptUsers.js @@ -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 }; diff --git a/server/endpoints/system.js b/server/endpoints/system.js index d785349..b579192 100644 --- a/server/endpoints/system.js +++ b/server/endpoints/system.js @@ -700,6 +700,7 @@ function systemEndpoints(app) { try { const user = await userFromSession(request, response); const uploadedFileName = request.randomFileName; + console.log("Uploaded File::::", uploadedFileName); if (!uploadedFileName) { return response.status(400).json({ message: "File upload failed." }); } diff --git a/server/endpoints/workspaces.js b/server/endpoints/workspaces.js index 6a6df19..e5eda4f 100644 --- a/server/endpoints/workspaces.js +++ b/server/endpoints/workspaces.js @@ -12,7 +12,7 @@ const { Document } = require("../models/documents"); const { DocumentVectors } = require("../models/vectors"); const { WorkspaceChats } = require("../models/workspaceChats"); const { getVectorDbClass } = require("../utils/helpers"); -const { handleFileUpload, handlePfpUpload } = require("../utils/files/multer"); +const { handleFileUpload, handlePfpUpload, handleCommUpload } = require("../utils/files/multer"); const { validatedRequest } = require("../utils/middleware/validatedRequest"); const { Telemetry } = require("../models/telemetry"); const { @@ -34,6 +34,11 @@ const { getTTSProvider } = require("../utils/TextToSpeech"); const { WorkspaceThread } = require("../models/workspaceThread"); const truncate = require("truncate"); const { purgeDocument } = require("../utils/files/purgeDocument"); +const { User } = require("../models/user"); +const { DeptUsers } = require("../models/deptUsers"); +const { DeptDocument } = require("../models/deptDocument"); +const { v4: uuidv4 } = require("uuid"); +const { moveAndRenameFile } = require("../utils/files/index"); function workspaceEndpoints(app) { if (!app) return; @@ -107,6 +112,56 @@ function workspaceEndpoints(app) { } ); + // app.post( + // "/workspace/:slug/upload", + // [ + // validatedRequest, + // flexUserRoleValid([ROLES.admin, ROLES.manager]), + // handleFileUpload, + // ], + // async function (request, response) { + // try { + // const Collector = new CollectorApi(); + // const { originalname } = request.file; + // const processingOnline = await Collector.online(); + // + // if (!processingOnline) { + // response + // .status(500) + // .json({ + // success: false, + // error: `Document processing API is not online. Document ${originalname} will not be processed automatically.`, + // }) + // .end(); + // return; + // } + // + // const { success, reason } = + // await Collector.processDocument(originalname); + // if (!success) { + // response.status(500).json({ success: false, error: reason }).end(); + // return; + // } + // + // Collector.log( + // `Document ${originalname} uploaded processed and successfully. It is now available in documents.` + // ); + // await Telemetry.sendTelemetry("document_uploaded"); + // await EventLogs.logEvent( + // "document_uploaded", + // { + // documentName: originalname, + // }, + // response.locals?.user?.id + // ); + // response.status(200).json({ success: true, error: null }); + // } catch (e) { + // console.error(e.message, e); + // response.sendStatus(500).end(); + // } + // } + // ); + app.post( "/workspace/:slug/upload", [ @@ -116,6 +171,11 @@ function workspaceEndpoints(app) { ], async function (request, response) { try { + const user = await userFromSession(request, response); + const deptUserRecord = await DeptUsers.get({ userId: user.id }); + if (!deptUserRecord.deptUser) { + return response.status(500).json({ success: false, error: "没有发现用户部门" }); + } const Collector = new CollectorApi(); const { originalname } = request.file; const processingOnline = await Collector.online(); @@ -131,12 +191,39 @@ function workspaceEndpoints(app) { return; } - const { success, reason } = + const { success, reason, documents } = await Collector.processDocument(originalname); if (!success) { response.status(500).json({ success: false, error: reason }).end(); return; } + // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + // 假设路径字符串 + const location = documents[0].location; + // 将路径中的反斜杠替换为正斜杠(可选,但通常更通用) + const unixStylePath = location.replace(/\\/g, '/'); + // 找到最后一个目录分隔符的位置 + const lastIndex = unixStylePath.lastIndexOf('/'); + // 提取文件名 + const parsedFileName = unixStylePath.substring(lastIndex + 1); + const fileExtension = path.extname(request.file.path).toLowerCase(); + const sourceFile = path.resolve(__dirname, request.file.destination, request.file.originalname); + const targetDir = + process.env.NODE_ENV === "development" + ? path.resolve(__dirname, `../../server/storage/localFile`) + : path.resolve(process.env.STORAGE_DIR, `../../server/storage/localFile`); + const newFileName = uuidv4() + fileExtension; // 新文件名 + moveAndRenameFile(sourceFile, targetDir, newFileName); + const deptDocData = { + deptId: deptUserRecord.deptUser.deptId, + parsedFileName: parsedFileName, + parsedFilePath: location, + realFileName: originalname, + realFileAlias: newFileName, + realFilePath: targetDir, + }; + await DeptDocument.create(deptDocData); + // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Collector.log( `Document ${originalname} uploaded processed and successfully. It is now available in documents.` diff --git a/server/index.js b/server/index.js index 85647d9..edbb34d 100644 --- a/server/index.js +++ b/server/index.js @@ -28,6 +28,8 @@ const { browserExtensionEndpoints } = require("./endpoints/browserExtension"); const { communityHubEndpoints } = require("./endpoints/communityHub"); const { agentFlowEndpoints } = require("./endpoints/agentFlows"); const { deptEndpoints } = require("./endpoints/dept"); +const { deptDocumentEndpoints } = require("./endpoints/deptDocument"); +const { deptUsersEndpoints } = require("./endpoints/deptUsers"); const app = express(); const apiRouter = express.Router(); const FILE_LIMIT = "3GB"; @@ -53,6 +55,8 @@ systemEndpoints(apiRouter); extensionEndpoints(apiRouter); workspaceEndpoints(apiRouter); deptEndpoints(apiRouter); +deptDocumentEndpoints(apiRouter); +deptUsersEndpoints(apiRouter); workspaceThreadEndpoints(apiRouter); chatEndpoints(apiRouter); adminEndpoints(apiRouter); diff --git a/server/models/dept.js b/server/models/dept.js index dd9cfc3..a820f29 100644 --- a/server/models/dept.js +++ b/server/models/dept.js @@ -233,6 +233,47 @@ const Dept = { throw error; } }, + /** + * 获取部门树状结构 + * @returns {Promise} + */ + getDeptTree:async function () { + try { + // 查询所有部门 + const allDepts = await prisma.dept.findMany(); + + // 构建树状结构 + const buildTree = (parentId = null) => { + return allDepts + .filter((dept) => dept.parentId === parentId) + .map((dept) => ({ + ...dept, + children: buildTree(dept.deptId), // 递归获取子部门 + })); + }; + + return buildTree(); + } catch (error) { + console.error("获取部门树状结构失败:", error); + throw error; + } + }, + /** + * 根据父部门 ID 获取子部门列表 + * @param {number} parentId - 父部门 ID + * @returns {Promise} + */ + getChildrenByParentId:async function (parentId = null) { + try { + const children = await prisma.dept.findMany({ + where: { parentId }, + }); + return children; + } catch (error) { + console.error("获取子部门列表失败:", error); + throw error; + } + }, }; module.exports = { Dept }; diff --git a/server/models/deptDocument.js b/server/models/deptDocument.js new file mode 100644 index 0000000..d16be56 --- /dev/null +++ b/server/models/deptDocument.js @@ -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} + */ + 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} + */ + 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} + */ + 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} + */ + 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 }; diff --git a/server/models/deptUsers.js b/server/models/deptUsers.js new file mode 100644 index 0000000..48eeb38 --- /dev/null +++ b/server/models/deptUsers.js @@ -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} + */ + 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} + */ + 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 }; diff --git a/server/prisma/migrations/20250226090554_init/migration.sql b/server/prisma/migrations/20250226090554_init/migration.sql new file mode 100644 index 0000000..afac12e --- /dev/null +++ b/server/prisma/migrations/20250226090554_init/migration.sql @@ -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 +); diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index 5244a24..30eaecb 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -320,16 +320,17 @@ model temporary_auth_tokens { } model dept { - deptId Int @id @default(autoincrement()) + deptId Int @id @default(autoincrement()) parentId Int? ancestors String? deptName String? orderNum Int? - status Int @default(0) - delFlag Int @default(0) - createdAt DateTime @default(now()) - lastUpdatedAt DateTime @default(now()) + status Int @default(0) + delFlag Int @default(0) + createdAt DateTime @default(now()) + lastUpdatedAt DateTime @default(now()) dept_users dept_users[] + dept_document dept_document[] } model dept_users { @@ -341,3 +342,19 @@ model dept_users { user users @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: NoAction) dept dept @relation(fields: [deptId], references: [deptId], onDelete: Cascade, onUpdate: NoAction) } + +model dept_document { + id Int @id @default(autoincrement()) + deptId Int + dept dept @relation(fields: [deptId], references: [deptId], onDelete: Cascade) + parsedFileName String + parsedFilePath String + realFileName String + realFileAlias String + realFilePath String + isPublic Int? + tags String? + delTag Boolean @default(false) + createdAt DateTime @default(now()) + lastUpdatedAt DateTime @default(now()) +} diff --git a/server/utils/files/index.js b/server/utils/files/index.js index 8e6e299..a28c2b4 100644 --- a/server/utils/files/index.js +++ b/server/utils/files/index.js @@ -32,9 +32,9 @@ async function viewLocalFiles() { type: "folder", items: [], }; - console.log("111111111111111111111111111111111111111111111111111111111111111111111111111111111"); + // console.log("111111111111111111111111111111111111111111111111111111111111111111111111111111111"); for (const file of fs.readdirSync(documentsPath)) { - console.log("file:", file); + // console.log("file:", file); if (path.extname(file) === ".md") continue; const folderPath = path.resolve(documentsPath, file); const isFolder = fs.lstatSync(folderPath).isDirectory(); @@ -50,7 +50,7 @@ async function viewLocalFiles() { if (path.extname(subfile) !== ".json") continue; const filePath = path.join(folderPath, subfile); const rawData = fs.readFileSync(filePath, "utf8"); - console.log("rawData:", rawData); + // console.log("rawData:", rawData); const cachefilename = `${file}/${subfile}`; const { pageContent, ...metadata } = JSON.parse(rawData); subdocs.items.push({ @@ -292,6 +292,32 @@ function purgeEntireVectorCache() { return; } +/** + * 移动文件到目标目录并重命名 + * @param {string} sourceFilePath - 源文件路径 + * @param {string} targetDirectory - 目标目录路径 + * @param {string} newFileName - 新文件名 + */ +function moveAndRenameFile(sourceFilePath, targetDirectory, newFileName) { + // 1. 检查源文件是否存在 + if (!fs.existsSync(sourceFilePath)) { + throw new Error(`源文件不存在: ${sourceFilePath}`); + } + + // 2. 检查目标目录是否存在,如果不存在则创建 + if (!fs.existsSync(targetDirectory)) { + fs.mkdirSync(targetDirectory, { recursive: true }); // recursive: true 确保创建多层目录 + } + + // 3. 构造目标文件的完整路径(使用新文件名) + const targetFilePath = path.join(targetDirectory, newFileName); + + // 4. 移动文件并重命名 + fs.renameSync(sourceFilePath, targetFilePath); + + console.log(`文件已移动到: ${targetFilePath}`); +} + module.exports = { findDocumentInDocuments, cachedVectorInformation, @@ -305,4 +331,5 @@ module.exports = { documentsPath, hasVectorCachedFiles, purgeEntireVectorCache, + moveAndRenameFile, }; diff --git a/server/utils/files/multer.js b/server/utils/files/multer.js index 18c6df6..81f8361 100644 --- a/server/utils/files/multer.js +++ b/server/utils/files/multer.js @@ -10,10 +10,12 @@ const { normalizePath } = require("."); */ const fileUploadStorage = multer.diskStorage({ destination: function (_, __, cb) { + console.log("进来了进来了进来了::::",process.env.NODE_ENV); const uploadOutput = process.env.NODE_ENV === "development" ? path.resolve(__dirname, `../../../collector/hotdir`) : path.resolve(process.env.STORAGE_DIR, `../../collector/hotdir`); + console.log("又来了又来了又来了:::",uploadOutput) cb(null, uploadOutput); }, filename: function (_, file, cb) { @@ -168,9 +170,60 @@ function handlePfpUpload(request, response, next) { }); } +// 文件存储配置 +const commUploadStorage = multer.diskStorage({ + destination: async function (_, __, cb) { + try { + // const uploadOutput = "D:\\code2\\anything-llm-master\\server\\storage\\real"; + console.log("进来了进来了进来了::::",process.env.NODE_ENV); + const uploadOutput = + process.env.NODE_ENV === "development" + ? path.resolve(__dirname, `../../../server/storage/localFile`) + : path.resolve(process.env.STORAGE_DIR, `../../server/storage/localFile`); + console.log("又来了又来了又来了:::",uploadOutput) + + if (process.env.NODE_ENV !== "development" && !process.env.STORAGE_DIR) { + throw new Error("STORAGE_DIR environment variable is required in production."); + } + + fs.mkdirSync(uploadOutput, { recursive: true }); + cb(null, uploadOutput); + } catch (err) { + cb(err); + } + }, + filename: function (req, file, cb) { + const randomFileName = `${v4()}${path.extname(file.originalname)}`; + req.randomFileName = randomFileName; + cb(null, randomFileName); + }, +}); + +// 文件上传中间件 +function handleCommUpload(request, response, next) { + console.log("进来了进来了"); + const upload = multer({ storage: commUploadStorage, limits: { fileSize: 100 * 1024 * 1024 }, }).single("file"); + + upload(request, response, function (err) { + console.log("handleCommUpload", err); + if (err) { + response + .status(500) + .json({ + success: false, + error: `Invalid file upload. ${err.message}`, + }) + .end(); + return; + } + next(); + }); +} + module.exports = { handleFileUpload, handleAPIFileUpload, handleAssetUpload, handlePfpUpload, + handleCommUpload, };