diff --git a/client/package-lock.json b/client/package-lock.json index 0a3109f9..8e0bdf51 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -12,6 +12,7 @@ "date-and-time": "^2.4.2", "file-saver": "^2.0.5", "filepond": "^4.30.4", + "filepond-plugin-file-validate-type": "^1.2.9", "js-cookie": "^3.0.1", "jszip": "^3.10.1", "react": "^18.2.0", @@ -966,6 +967,15 @@ "integrity": "sha512-FCwsMvG9iiEs6uobdDrTaKsCgsqys0NuLgPPD8n37AYVYBiiDkrPkk9MSIU5rT2FahYcL1bScYI9huIPtlzqyA==", "license": "MIT" }, + "node_modules/filepond-plugin-file-validate-type": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/filepond-plugin-file-validate-type/-/filepond-plugin-file-validate-type-1.2.9.tgz", + "integrity": "sha512-Tzv07aNdZvjUXDRA3XL16QMEvh6llDrXlcZ6W0eTHQ+taHaVg/JKJTFs/AViO+6ZcpPCcQStbhYEL2HoS+vldw==", + "license": "MIT", + "peerDependencies": { + "filepond": ">=1.x <5.x" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2506,6 +2516,12 @@ "resolved": "https://registry.npmjs.org/filepond/-/filepond-4.30.4.tgz", "integrity": "sha512-FCwsMvG9iiEs6uobdDrTaKsCgsqys0NuLgPPD8n37AYVYBiiDkrPkk9MSIU5rT2FahYcL1bScYI9huIPtlzqyA==" }, + "filepond-plugin-file-validate-type": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/filepond-plugin-file-validate-type/-/filepond-plugin-file-validate-type-1.2.9.tgz", + "integrity": "sha512-Tzv07aNdZvjUXDRA3XL16QMEvh6llDrXlcZ6W0eTHQ+taHaVg/JKJTFs/AViO+6ZcpPCcQStbhYEL2HoS+vldw==", + "requires": {} + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", diff --git a/client/package.json b/client/package.json index 6c4f5a20..2f0abce2 100644 --- a/client/package.json +++ b/client/package.json @@ -13,6 +13,7 @@ "date-and-time": "^2.4.2", "file-saver": "^2.0.5", "filepond": "^4.30.4", + "filepond-plugin-file-validate-type": "^1.2.9", "js-cookie": "^3.0.1", "jszip": "^3.10.1", "react": "^18.2.0", diff --git a/client/src/screens/browse/components/year-info/year-options.jsx b/client/src/screens/browse/components/year-info/year-options.jsx index f40996cd..ad7fff86 100644 --- a/client/src/screens/browse/components/year-info/year-options.jsx +++ b/client/src/screens/browse/components/year-info/year-options.jsx @@ -1,6 +1,6 @@ const currentDate = new Date(); const currentMonth = currentDate.getMonth() + 1; -const acadYear = (currentMonth >= 1 && currentMonth <= 5) ? currentDate.getFullYear() - 1 : currentDate.getFullYear(); +const acadYear = currentDate.getFullYear(); const Yroptions = ({ course, diff --git a/client/src/screens/contributions/index.jsx b/client/src/screens/contributions/index.jsx index c38969aa..4a51cc7f 100644 --- a/client/src/screens/contributions/index.jsx +++ b/client/src/screens/contributions/index.jsx @@ -2,6 +2,7 @@ import Wrapper from "./components/wrapper"; import SectionC from "./components/sectionC"; import axios from "axios"; import { FilePond, registerPlugin } from "react-filepond"; +import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type'; import "filepond/dist/filepond.min.css"; import { useEffect, useRef, useState } from "react"; import "./styles.scss"; @@ -18,6 +19,9 @@ import { RefreshCurrentFolder, ChangeCurrentYearData, } from "../../actions/filebrowser_actions"; + +registerPlugin(FilePondPluginFileValidateType); + const Contributions = () => { const uploadedBy = useSelector((state) => state.user.user._id); const userName = useSelector((state) => state.user.user.name); @@ -27,6 +31,15 @@ const Contributions = () => { const code = currentFolder?.course; const [contributionId, setContributionId] = useState(""); const dispatch = useDispatch(); + const allowed_file_types = [ + 'application/pdf', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'video/mp4', + 'video/x-matroska', // mkv + 'image/png', + 'image/jpeg' + ] + useEffect(() => { setContributionId(uuidv4()); }, []); @@ -35,26 +48,25 @@ const Contributions = () => { const [isUploading, setIsUploading] = useState(false); // const [contributionId, setContributionId] = useState(""); - + const [files, setFiles] = useState([]); let pond = useRef(); const handleUpdateFiles = (fileItems) => { + setFiles(fileItems); if (fileItems.length > 0) setSubmitEnabled(true); else setSubmitEnabled(false); }; async function handleSubmit() { - if (isUploading) return; + if (isUploading || files.length === 0) return; const collection = document.getElementsByClassName("contri"); const contributionSection = collection[0]; - // console.log(toggle); - // console.log(isAnoynmous); try { setIsUploading(true); setSubmitEnabled(false); - // console.log(resp); + let resp = await CreateNewContribution({ parentFolder: currentFolder._id, courseCode: currentFolder.course, @@ -63,22 +75,37 @@ const Contributions = () => { contributionId, uploadedBy, }); - // console.log(resp); - await pond.current.processFiles(); + + const formData = new FormData(); + files.forEach((fileItem, index) => { + formData.append(`files`, fileItem.file); + }); + + pond.current.processFiles(); + + await fetch(`${server}/api/contribution/upload`, { + method: 'POST', + headers: { + "contribution-id": contributionId, + username: userName, + }, + body: formData + }); + pond.current.removeFiles(); contributionSection.classList.remove("show"); toast.success("Files uploaded successfully!"); setContributionId(uuidv4()); setSubmitEnabled(true); + } catch (error) { setSubmitEnabled(true); contributionSection.classList.remove("show"); - toast.error("Upload failed. Please try again!"); - // console.log(error); } finally { setIsUploading(false); } + //refresh the course in session storage to include the new file. try { let loadingCourseToastId = toast.loading("Loading course data..."); @@ -114,21 +141,20 @@ const Contributions = () => { allowMultiple={true} onupdatefiles={handleUpdateFiles} maxFiles={40} - server={{ - url: `${server}/api/contribution/upload`, - process: { - headers: { - "contribution-id": contributionId, - username: userName, - }, - }, - }} instantUpload={false} - allowProcess={false} + acceptedFileTypes={allowed_file_types} + fileValidateTypeLabelExpectedTypes="Expects PDF and PowerPoint files" + allowProcess={true} allowRevert={false} ref={(ref) => { pond.current = ref; }} + server={{ + process: (fieldName, file, metadata, load) => { + load(); + }, + revert: null, + }} />
diff --git a/client/yarn.lock b/client/yarn.lock index a4ff28eb..1907d043 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -540,7 +540,12 @@ file-saver@^2.0.5: resolved "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz" integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== -filepond@^4.30.4, "filepond@>=3.7.x < 5.x": +filepond-plugin-file-validate-type@^1.2.9: + version "1.2.9" + resolved "https://registry.npmjs.org/filepond-plugin-file-validate-type/-/filepond-plugin-file-validate-type-1.2.9.tgz" + integrity sha512-Tzv07aNdZvjUXDRA3XL16QMEvh6llDrXlcZ6W0eTHQ+taHaVg/JKJTFs/AViO+6ZcpPCcQStbhYEL2HoS+vldw== + +filepond@^4.30.4, "filepond@>=1.x <5.x", "filepond@>=3.7.x < 5.x": version "4.30.4" resolved "https://registry.npmjs.org/filepond/-/filepond-4.30.4.tgz" integrity sha512-FCwsMvG9iiEs6uobdDrTaKsCgsqys0NuLgPPD8n37AYVYBiiDkrPkk9MSIU5rT2FahYcL1bScYI9huIPtlzqyA== diff --git a/server/external/mobile/055228a2aadf749161d2abc0d4860165 b/server/external/mobile/055228a2aadf749161d2abc0d4860165 deleted file mode 100644 index 940137c0..00000000 Binary files a/server/external/mobile/055228a2aadf749161d2abc0d4860165 and /dev/null differ diff --git a/server/external/mobile/2a1d8ff81af7ed2cf0d4f5239f146b11 b/server/external/mobile/2a1d8ff81af7ed2cf0d4f5239f146b11 deleted file mode 100644 index 940137c0..00000000 Binary files a/server/external/mobile/2a1d8ff81af7ed2cf0d4f5239f146b11 and /dev/null differ diff --git a/server/external/mobile/5b35bd641d976b5cb1e4facd9798e068 b/server/external/mobile/5b35bd641d976b5cb1e4facd9798e068 deleted file mode 100644 index 940137c0..00000000 Binary files a/server/external/mobile/5b35bd641d976b5cb1e4facd9798e068 and /dev/null differ diff --git a/server/external/mobile/aaf8c0bd472c9c22ae4a42d0e60d6704 b/server/external/mobile/aaf8c0bd472c9c22ae4a42d0e60d6704 deleted file mode 100644 index 940137c0..00000000 Binary files a/server/external/mobile/aaf8c0bd472c9c22ae4a42d0e60d6704 and /dev/null differ diff --git a/server/external/mobile/d81f2d3942919762e6eee446b00dd2ce b/server/external/mobile/d81f2d3942919762e6eee446b00dd2ce deleted file mode 100644 index 940137c0..00000000 Binary files a/server/external/mobile/d81f2d3942919762e6eee446b00dd2ce and /dev/null differ diff --git a/server/modules/contribution/contribution.controller.js b/server/modules/contribution/contribution.controller.js index 3151b8bd..a5034689 100644 --- a/server/modules/contribution/contribution.controller.js +++ b/server/modules/contribution/contribution.controller.js @@ -51,44 +51,62 @@ async function GetAllContributions(req, res, next) { async function HandleFileUpload(req, res, next) { console.log("Handling File Upload"); const contributionId = req.headers["contribution-id"]; - const files = req.files; // Changed from req.file to req.files for array handling + const username = req.headers.username; + const files = req.files; - // Check if files were uploaded if (!files || files.length === 0) { return res.status(400).json({ error: "No files were uploaded" }); } - // Handle multiple files const uploadedFiles = []; - for (const file of files) { - // Files names - let initialPath = file.path; - let newFilename = file.filename; - let originalFilename = file.originalname; - - let wordArr = originalFilename.split("."); - let fileExtension = wordArr[wordArr.length - 1]; - let finalFileName = ""; - - for (let i = 0; i < wordArr.length - 1; i++) { - finalFileName += wordArr[i]; + try { + for (const file of files) { + // Files names + let initialPath = file.path; + let newFilename = file.filename; + let originalFilename = file.originalname; + + let wordArr = originalFilename.split("."); + let fileExtension = wordArr[wordArr.length - 1]; + let finalFileName = ""; + + for (let i = 0; i < wordArr.length - 1; i++) { + finalFileName += wordArr[i]; + } + finalFileName += "~" + username; + finalFileName += "." + fileExtension; + + const finalPath = initialPath.slice(0, initialPath.indexOf(newFilename)); + + await fs.promises.rename(finalPath + newFilename, finalPath + finalFileName); + const fileId = await UploadFile(contributionId, finalPath, finalFileName); + + if (fileId) { + await HandleFileToDB(contributionId, fileId); + uploadedFiles.push({ + fileId, + originalName: originalFilename, + finalName: finalFileName + }); + } + + await fs.promises.unlink(finalPath + finalFileName); } - finalFileName += "~" + req.headers.username; - finalFileName += "." + fileExtension; - const finalPath = initialPath.slice(0, initialPath.indexOf(newFilename)); + console.log(`Successfully uploaded ${uploadedFiles.length} files`); + return res.json({ + files: uploadedFiles, + count: uploadedFiles.length + }); - await fs.promises.rename(finalPath + newFilename, finalPath + finalFileName); - const fileId = await UploadFile(contributionId, finalPath, finalFileName); - if (fileId) { - await HandleFileToDB(contributionId, fileId); - uploadedFiles.push({ fileId, originalName: originalFilename }); - } - await fs.promises.unlink(finalPath + finalFileName); + } catch (error) { + console.error("File upload error:", error); + return res.status(500).json({ + error: "File upload failed", + details: error.message + }); } - - return res.json({ files: uploadedFiles, count: uploadedFiles.length }); } async function CreateNewContribution(req, res, next) { diff --git a/server/modules/contribution/contribution.routes.js b/server/modules/contribution/contribution.routes.js index 36725237..4fe97b20 100644 --- a/server/modules/contribution/contribution.routes.js +++ b/server/modules/contribution/contribution.routes.js @@ -10,7 +10,7 @@ router.get("/", isAuthenticated, ContributionController.GetMyContributions); router.get("/all", ContributionController.GetAllContributions); router.delete("/:contributionId", ContributionController.DeleteContribution); router.post("/", catchAsync(ContributionController.CreateNewContribution)); -router.post("/upload", upload.array("file"), catchAsync(ContributionController.HandleFileUpload)); +router.post("/upload", upload.array("files"), catchAsync(ContributionController.HandleFileUpload)); router.post( "/upload/mobile", isAuthenticated, diff --git a/server/onedrive-device-code.token b/server/onedrive-device-code.token index 2b0c7266..54e65146 100644 --- a/server/onedrive-device-code.token +++ b/server/onedrive-device-code.token @@ -1 +1 @@ -SAQABIQEAAABVrSpeuWamRam2jAF1XRQEWGdKsPq8yryqwLbeStuwomr7_ecQpZI0SkTNkLul23HPrGog08llLH4HjavMgc7vHrYEowvnWD3KOJaGcH6aVC2LAH1STKBlfczrzJEPaELD27PsyOboCOUey0JLjYAkrVc5qSuJRvIFOBXHTO1HA9IT4AZeca7utAiXhPO56Pk2rXs45wLr6dAhpt0OUgHpIAA \ No newline at end of file +SAQABIQEAAABVrSpeuWamRam2jAF1XRQEWGdKsPq8yryqwLbeStuwomr7_ecQpZI0SkTNkLul23HPrGog08llLH4HjavMgc7vHrYEowvnWD3KOJaGcH6aVC2LAH1STKBlfczrzJEPaELD27PsyOboCOUey0JLjYAkrVc5qSuJRvIFOBXHTO1HA9IT4AZeca7utAiXhPO56Pk2rXs45wLr6dAhpt0OUgHpIAA diff --git a/server/yarn.lock b/server/yarn.lock index 586fa22f..4f24dbbb 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -2040,6 +2040,11 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + function-bind@^1.1.1, function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"