From b8d37d9f43af2facab4c51146a46229a58cb53d9 Mon Sep 17 00:00:00 2001
From: Timothy Carambat <rambat1010@gmail.com>
Date: Mon, 1 Apr 2024 12:06:47 -0700
Subject: [PATCH] Handle no-mutli-part form data error (#1004)

---
 server/endpoints/api/document/index.js |   6 +-
 server/endpoints/system.js             |  16 +--
 server/endpoints/workspaces.js         |  25 ++--
 server/utils/files/multer.js           | 157 ++++++++++++++++---------
 4 files changed, 124 insertions(+), 80 deletions(-)

diff --git a/server/endpoints/api/document/index.js b/server/endpoints/api/document/index.js
index e3ab050c6..158f4df68 100644
--- a/server/endpoints/api/document/index.js
+++ b/server/endpoints/api/document/index.js
@@ -1,6 +1,6 @@
 const { Telemetry } = require("../../../models/telemetry");
 const { validApiKey } = require("../../../utils/middleware/validApiKey");
-const { setupMulter } = require("../../../utils/files/multer");
+const { handleFileUpload } = require("../../../utils/files/multer");
 const {
   viewLocalFiles,
   findDocumentInDocuments,
@@ -9,7 +9,6 @@ const {
 const { reqBody } = require("../../../utils/http");
 const { EventLogs } = require("../../../models/eventLogs");
 const { CollectorApi } = require("../../../utils/collectorApi");
-const { handleUploads } = setupMulter();
 const fs = require("fs");
 const path = require("path");
 const { Document } = require("../../../models/documents");
@@ -23,8 +22,7 @@ function apiDocumentEndpoints(app) {
 
   app.post(
     "/v1/document/upload",
-    [validApiKey],
-    handleUploads.single("file"),
+    [validApiKey, handleFileUpload],
     async (request, response) => {
       /*
     #swagger.tags = ['Documents']
diff --git a/server/endpoints/system.js b/server/endpoints/system.js
index 2e0eda844..753825b85 100644
--- a/server/endpoints/system.js
+++ b/server/endpoints/system.js
@@ -12,13 +12,11 @@ const {
   multiUserMode,
   queryParams,
 } = require("../utils/http");
-const { setupLogoUploads, setupPfpUploads } = require("../utils/files/multer");
+const { handleAssetUpload, handlePfpUpload } = require("../utils/files/multer");
 const { v4 } = require("uuid");
 const { SystemSettings } = require("../models/systemSettings");
 const { User } = require("../models/user");
 const { validatedRequest } = require("../utils/middleware/validatedRequest");
-const { handleLogoUploads } = setupLogoUploads();
-const { handlePfpUploads } = setupPfpUploads();
 const fs = require("fs");
 const path = require("path");
 const {
@@ -531,8 +529,7 @@ function systemEndpoints(app) {
 
   app.post(
     "/system/upload-pfp",
-    [validatedRequest, flexUserRoleValid([ROLES.all])],
-    handlePfpUploads.single("file"),
+    [validatedRequest, flexUserRoleValid([ROLES.all]), handlePfpUpload],
     async function (request, response) {
       try {
         const user = await userFromSession(request, response);
@@ -605,10 +602,13 @@ function systemEndpoints(app) {
 
   app.post(
     "/system/upload-logo",
-    [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
-    handleLogoUploads.single("logo"),
+    [
+      validatedRequest,
+      flexUserRoleValid([ROLES.admin, ROLES.manager]),
+      handleAssetUpload,
+    ],
     async (request, response) => {
-      if (!request.file || !request.file.originalname) {
+      if (!request?.file || !request?.file.originalname) {
         return response.status(400).json({ message: "No logo file provided." });
       }
 
diff --git a/server/endpoints/workspaces.js b/server/endpoints/workspaces.js
index d3715cd84..da9e2ad94 100644
--- a/server/endpoints/workspaces.js
+++ b/server/endpoints/workspaces.js
@@ -1,10 +1,13 @@
+const path = require("path");
+const fs = require("fs");
 const { reqBody, multiUserMode, userFromSession } = require("../utils/http");
+const { normalizePath } = require("../utils/files");
 const { Workspace } = require("../models/workspace");
 const { Document } = require("../models/documents");
 const { DocumentVectors } = require("../models/vectors");
 const { WorkspaceChats } = require("../models/workspaceChats");
 const { getVectorDbClass } = require("../utils/helpers");
-const { setupMulter } = require("../utils/files/multer");
+const { handleFileUpload, handlePfpUpload } = require("../utils/files/multer");
 const { validatedRequest } = require("../utils/middleware/validatedRequest");
 const { Telemetry } = require("../models/telemetry");
 const {
@@ -18,12 +21,6 @@ const {
 const { validWorkspaceSlug } = require("../utils/middleware/validWorkspace");
 const { convertToChatHistory } = require("../utils/helpers/chat/responses");
 const { CollectorApi } = require("../utils/collectorApi");
-const { handleUploads } = setupMulter();
-const { setupPfpUploads } = require("../utils/files/multer");
-const { normalizePath } = require("../utils/files");
-const { handlePfpUploads } = setupPfpUploads();
-const path = require("path");
-const fs = require("fs");
 const {
   determineWorkspacePfpFilepath,
   fetchPfp,
@@ -102,8 +99,11 @@ function workspaceEndpoints(app) {
 
   app.post(
     "/workspace/:slug/upload",
-    [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
-    handleUploads.single("file"),
+    [
+      validatedRequest,
+      flexUserRoleValid([ROLES.admin, ROLES.manager]),
+      handleFileUpload,
+    ],
     async function (request, response) {
       const Collector = new CollectorApi();
       const { originalname } = request.file;
@@ -479,8 +479,11 @@ function workspaceEndpoints(app) {
 
   app.post(
     "/workspace/:slug/upload-pfp",
-    [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
-    handlePfpUploads.single("file"),
+    [
+      validatedRequest,
+      flexUserRoleValid([ROLES.admin, ROLES.manager]),
+      handlePfpUpload,
+    ],
     async function (request, response) {
       try {
         const { slug } = request.params;
diff --git a/server/utils/files/multer.js b/server/utils/files/multer.js
index 874e7278c..22f1217e3 100644
--- a/server/utils/files/multer.js
+++ b/server/utils/files/multer.js
@@ -3,71 +3,114 @@ const path = require("path");
 const fs = require("fs");
 const { v4 } = require("uuid");
 
-function setupMulter() {
-  // Handle File uploads for auto-uploading.
-  const storage = multer.diskStorage({
-    destination: function (_, __, cb) {
-      const uploadOutput =
-        process.env.NODE_ENV === "development"
-          ? path.resolve(__dirname, `../../../collector/hotdir`)
-          : path.resolve(process.env.STORAGE_DIR, `../../collector/hotdir`);
-      cb(null, uploadOutput);
-    },
-    filename: function (_, file, cb) {
-      file.originalname = Buffer.from(file.originalname, "latin1").toString(
-        "utf8"
-      );
-      cb(null, file.originalname);
-    },
-  });
+// Handle File uploads for auto-uploading.
+const fileUploadStorage = multer.diskStorage({
+  destination: function (_, __, cb) {
+    const uploadOutput =
+      process.env.NODE_ENV === "development"
+        ? path.resolve(__dirname, `../../../collector/hotdir`)
+        : path.resolve(process.env.STORAGE_DIR, `../../collector/hotdir`);
+    cb(null, uploadOutput);
+  },
+  filename: function (_, file, cb) {
+    file.originalname = Buffer.from(file.originalname, "latin1").toString(
+      "utf8"
+    );
+    cb(null, file.originalname);
+  },
+});
 
-  return { handleUploads: multer({ storage }) };
-}
+// Asset storage for logos
+const assetUploadStorage = multer.diskStorage({
+  destination: function (_, __, cb) {
+    const uploadOutput =
+      process.env.NODE_ENV === "development"
+        ? path.resolve(__dirname, `../../storage/assets`)
+        : path.resolve(process.env.STORAGE_DIR, "assets");
+    fs.mkdirSync(uploadOutput, { recursive: true });
+    return cb(null, uploadOutput);
+  },
+  filename: function (_, file, cb) {
+    file.originalname = Buffer.from(file.originalname, "latin1").toString(
+      "utf8"
+    );
+    cb(null, file.originalname);
+  },
+});
 
-function setupLogoUploads() {
-  // Handle Logo uploads.
-  const storage = multer.diskStorage({
-    destination: function (_, __, cb) {
-      const uploadOutput =
-        process.env.NODE_ENV === "development"
-          ? path.resolve(__dirname, `../../storage/assets`)
-          : path.resolve(process.env.STORAGE_DIR, "assets");
-      fs.mkdirSync(uploadOutput, { recursive: true });
-      return cb(null, uploadOutput);
-    },
-    filename: function (_, file, cb) {
-      file.originalname = Buffer.from(file.originalname, "latin1").toString(
-        "utf8"
-      );
-      cb(null, file.originalname);
-    },
-  });
+// Asset sub-storage manager for pfp icons.
+const pfpUploadStorage = multer.diskStorage({
+  destination: function (_, __, cb) {
+    const uploadOutput =
+      process.env.NODE_ENV === "development"
+        ? path.resolve(__dirname, `../../storage/assets/pfp`)
+        : path.resolve(process.env.STORAGE_DIR, "assets/pfp");
+    fs.mkdirSync(uploadOutput, { recursive: true });
+    return cb(null, uploadOutput);
+  },
+  filename: function (req, file, cb) {
+    const randomFileName = `${v4()}${path.extname(file.originalname)}`;
+    req.randomFileName = randomFileName;
+    cb(null, randomFileName);
+  },
+});
 
-  return { handleLogoUploads: multer({ storage }) };
+// Handle Generic file upload as documents
+function handleFileUpload(request, response, next) {
+  const upload = multer({ storage: fileUploadStorage }).single("file");
+  upload(request, response, function (err) {
+    if (err) {
+      response
+        .status(500)
+        .json({
+          success: false,
+          error: `Invalid file upload. ${err.message}`,
+        })
+        .end();
+      return;
+    }
+    next();
+  });
 }
 
-function setupPfpUploads() {
-  const storage = multer.diskStorage({
-    destination: function (_, __, cb) {
-      const uploadOutput =
-        process.env.NODE_ENV === "development"
-          ? path.resolve(__dirname, `../../storage/assets/pfp`)
-          : path.resolve(process.env.STORAGE_DIR, "assets/pfp");
-      fs.mkdirSync(uploadOutput, { recursive: true });
-      return cb(null, uploadOutput);
-    },
-    filename: function (req, file, cb) {
-      const randomFileName = `${v4()}${path.extname(file.originalname)}`;
-      req.randomFileName = randomFileName;
-      cb(null, randomFileName);
-    },
+// Handle logo asset uploads
+function handleAssetUpload(request, response, next) {
+  const upload = multer({ storage: assetUploadStorage }).single("logo");
+  upload(request, response, function (err) {
+    if (err) {
+      response
+        .status(500)
+        .json({
+          success: false,
+          error: `Invalid file upload. ${err.message}`,
+        })
+        .end();
+      return;
+    }
+    next();
   });
+}
 
-  return { handlePfpUploads: multer({ storage }) };
+// Handle PFP file upload as logos
+function handlePfpUpload(request, response, next) {
+  const upload = multer({ storage: pfpUploadStorage }).single("file");
+  upload(request, response, function (err) {
+    if (err) {
+      response
+        .status(500)
+        .json({
+          success: false,
+          error: `Invalid file upload. ${err.message}`,
+        })
+        .end();
+      return;
+    }
+    next();
+  });
 }
 
 module.exports = {
-  setupMulter,
-  setupLogoUploads,
-  setupPfpUploads,
+  handleFileUpload,
+  handleAssetUpload,
+  handlePfpUpload,
 };
-- 
GitLab