diff --git a/CourseHub b/CourseHub
new file mode 160000
index 00000000..d7ccac76
--- /dev/null
+++ b/CourseHub
@@ -0,0 +1 @@
+Subproject commit d7ccac769a7556b0184e77b583f206cdd8805685
diff --git a/client/src/actions/filebrowser_actions.js b/client/src/actions/filebrowser_actions.js
index fa619414..ab445987 100644
--- a/client/src/actions/filebrowser_actions.js
+++ b/client/src/actions/filebrowser_actions.js
@@ -56,3 +56,8 @@ export const RemoveFileFromFolder = (fileId) => ({
type: "REMOVE_FILE_FROM_FOLDER",
payload: fileId,
});
+
+export const RefreshCurrentFolder = () => ({
+ type: "REFRESH_CURRENT_FOLDER",
+ payload: Date.now(),
+});
diff --git a/client/src/api/Folder.js b/client/src/api/Folder.js
new file mode 100644
index 00000000..76de50dd
--- /dev/null
+++ b/client/src/api/Folder.js
@@ -0,0 +1,33 @@
+import axios from "axios";
+import serverRoot from "./server";
+
+// Create axios instance with baseURL and withCredentials
+const API = axios.create({
+ baseURL: `${serverRoot}/api`,
+ withCredentials: true,
+});
+
+// Attach token to every request if available (from localStorage)
+API.interceptors.request.use((req) => {
+ const user = JSON.parse(localStorage.getItem("profile"));
+ if (user) req.headers.Authorization = `Bearer ${user.token}`;
+ return req;
+});
+
+export const createFolder = async ({ name, course, parentFolder,childType }) => {
+ const { data } = await API.post("/folder/create", {
+ name,
+ course,
+ parentFolder,
+ childType
+ });
+ return data;
+};
+
+export const deleteFolder = async ({ folderId, parentFolderId }) => {
+ const { data } = await API.delete(`/folder/delete`, {
+ params: { folderId, parentFolderId },
+ });
+ return data;
+};
+
diff --git a/client/src/reducers/filebrowser_reducer.js b/client/src/reducers/filebrowser_reducer.js
index 2c77084b..f3527399 100644
--- a/client/src/reducers/filebrowser_reducer.js
+++ b/client/src/reducers/filebrowser_reducer.js
@@ -81,6 +81,12 @@ const FileBrowserReducer = (
},
};
+ case "REFRESH_CURRENT_FOLDER":
+ return {
+ ...state,
+ refreshKey: action.payload,
+ };
+
default:
return state;
}
diff --git a/client/src/screens/browse/components/browsefolder/Delete.svg b/client/src/screens/browse/components/browsefolder/Delete.svg
new file mode 100644
index 00000000..036a5745
--- /dev/null
+++ b/client/src/screens/browse/components/browsefolder/Delete.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/src/screens/browse/components/browsefolder/confirmDialog.jsx b/client/src/screens/browse/components/browsefolder/confirmDialog.jsx
new file mode 100644
index 00000000..c75e56d0
--- /dev/null
+++ b/client/src/screens/browse/components/browsefolder/confirmDialog.jsx
@@ -0,0 +1,96 @@
+import React from "react";
+import cross from "./cross.svg";
+
+const styles = {
+ overlay: {
+ position: "fixed",
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ backgroundColor: "rgba(0, 0, 0, 0.4)",
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ zIndex: 1000,
+ },
+ dialog: {
+ backgroundColor: "#fff",
+ padding: "25px 30px",
+ borderRadius: "8px",
+ maxWidth: "400px",
+ width: "90%",
+ boxShadow: "0 4px 20px rgba(0, 0, 0, 0.2)",
+ textAlign: "center",
+ fontFamily: "sans-serif",
+ },
+
+ iconImage: {
+ width: "80px",
+ height: "80px",
+ margin: "1em",
+
+ },
+ heading: {
+ fontSize: "2em",
+ },
+ message: {
+ fontSize: "1em",
+ color: "#374151",
+ margin: "1em",
+ },
+ buttonGroup: {
+ display: "flex",
+ justifyContent: "center",
+ gap: "1em",
+ },
+ deleteBtn: {
+ display: "flex",
+ alignItems: "center",
+ backgroundColor: "#ef4444",
+ color: "#fff",
+ border: "none",
+ padding: "10px 18px",
+ borderRadius: "5px",
+ cursor: "pointer",
+ fontWeight: "bold",
+ fontSize: "1em",
+ },
+ cancelBtn: {
+ backgroundColor: "#9ca3af",
+ color: "#fff",
+ border: "none",
+ padding: "10px 18px",
+ borderRadius: "5px",
+ cursor: "pointer",
+ fontWeight: "bold",
+ fontSize: "1em",
+ },
+};
+
+
+const ConfirmDialog = ({ isOpen, onConfirm, onCancel }) => {
+ if (!isOpen) return null;
+
+ return (
+
+
+

+
Are you sure?
+
+ Do you want to permanently delete this folder? This action cannot be undone.
+
+
+
+
+
+
+
+ );
+};
+
+export { ConfirmDialog };
diff --git a/client/src/screens/browse/components/browsefolder/cross.svg b/client/src/screens/browse/components/browsefolder/cross.svg
new file mode 100644
index 00000000..3f98f5d4
--- /dev/null
+++ b/client/src/screens/browse/components/browsefolder/cross.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/src/screens/browse/components/browsefolder/index.jsx b/client/src/screens/browse/components/browsefolder/index.jsx
index cbf8c76a..643fb85c 100644
--- a/client/src/screens/browse/components/browsefolder/index.jsx
+++ b/client/src/screens/browse/components/browsefolder/index.jsx
@@ -1,15 +1,39 @@
import "./styles.scss";
+import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { ChangeFolder } from "../../../../actions/filebrowser_actions";
+import { deleteFolder } from "../../../../api/Folder";
+import { toast } from "react-toastify";
+import { RefreshCurrentFolder } from "../../../../actions/filebrowser_actions";
+import { ConfirmDialog } from "./confirmDialog";
const BrowseFolder = ({ type = "file", color, path, name, subject, folderData }) => {
const dispatch = useDispatch();
+ const [showConfirm, setShowConfirm] = useState(false);
const onClick = (folderData) => {
// return;
dispatch(ChangeFolder(folderData));
};
+
+ const handleDelete = async (e) => {
+ try {
+ await deleteFolder({ folderId: folderData._id, parentFolderId: folderData.parent });
+ toast.success("Folder deleted successfully!");
+ dispatch(RefreshCurrentFolder());
+ } catch (err) {
+ console.log(err);
+ toast.error("Failed to delete folder.");
+ }
+ setShowConfirm(false);
+ };
+
+ const cancelDelete = () => {
+ setShowConfirm(false);
+ };
+
return (
- onClick(folderData)}>
- {/* {type === "folder" ? (
+ <>
+
onClick(folderData)}>
+ {/* {type === "folder" ? (
)} */}
-
-
-
{""}
-
{name ? name : "Name"}
-
-
-
{subject ? subject.toUpperCase() : "Subject Here"}
+
+
+
{""}
+
{name ? name : "Name"}
+
{
+ e.stopPropagation();
+ setShowConfirm(true);
+ }}
+ title="Delete folder"
+ >
+
+
+
+ {subject ? subject.toUpperCase() : "Subject Here"}
+
+
-
+
+ >
);
};
diff --git a/client/src/screens/browse/components/browsefolder/styles.scss b/client/src/screens/browse/components/browsefolder/styles.scss
index f9232a1a..64db7200 100644
--- a/client/src/screens/browse/components/browsefolder/styles.scss
+++ b/client/src/screens/browse/components/browsefolder/styles.scss
@@ -1,4 +1,4 @@
-.browse-folder{
+.browse-folder {
width: 200px;
height: 175px;
margin: 10px;
@@ -8,45 +8,62 @@
background: url(./folder.svg), none;
background-position: center;
background-repeat: no-repeat;
- &:hover{
+ &:hover {
transform: translateY(-2px);
}
- svg{
+ svg {
position: absolute;
z-index: -1;
}
- .content{
+ .content {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
- .top{
- .path{
+ .top {
+ position: relative;
+ .delete {
+ background: url(./Delete.svg), none;
+ position: absolute;
+ top: 5px;
+ right: 5px;
+ width: 20px;
+ height: 20px;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ cursor: pointer;
+ opacity: 0;
+ transition: opacity 0.2s ease-in-out;
+ z-index: 3;
+ }
+ .path {
padding: 30px 50px 0px 15px;
font-size: 0.9rem;
}
- .name{
- font-family: 'Bold';
+ .name {
+ font-family: "Bold";
font-size: 1.2rem;
padding: 0px 50px 0px 15px;
}
-
}
- .bottom{
+ .top:hover .delete {
+ opacity: 1;
+ }
+ .bottom {
width: 100%;
text-align: right;
display: flex;
justify-content: right;
- .subject{
+ .subject {
width: 85%;
text-align: right;
padding: 0px 15px 20px 0px;
- font-family: 'Bold';
+ font-family: "Bold";
font-size: 1.2rem;
- color: rgba(80, 80, 80, 0.5)
+ color: rgba(80, 80, 80, 0.5);
}
}
}
-
-}
\ No newline at end of file
+}
diff --git a/client/src/screens/browse/components/collapsible/index.jsx b/client/src/screens/browse/components/collapsible/index.jsx
index a97084cb..a4a832c0 100644
--- a/client/src/screens/browse/components/collapsible/index.jsx
+++ b/client/src/screens/browse/components/collapsible/index.jsx
@@ -88,35 +88,41 @@ const Collapsible = ({ course, color, state }) => {
dispatch(ChangeFolder(null));
return currCourse;
};
-
const triggerGetCourse = () => {
const run = async () => {
- const t = await getCurrentCourse(course.code);
- if (t) {
- const yearChildren = Array.isArray(t.children?.[t.children.length - 1]?.children)
- ? t.children[t.children.length - 1].children : [];
- if (initial) {
- dispatch(ChangeCurrentYearData(t.children.length - 1, yearChildren));
- setInitial(false);
- } else {
- try {
- dispatch(ChangeCurrentYearData(t.children.length - 1, yearChildren));
- if (!folderId) {
- dispatch(ChangeFolder(t.children?.[t.children.length - 1]));
- folderId = null;
- }
- } catch (error) {
- // console.log(error);
- dispatch(ChangeCurrentYearData(t.children.length - 1,yearChildren));
- dispatch(ChangeFolder(t.children?.[t.children.length - 1]));
- }
+ try {
+ const fetched = await getCurrentCourse(course.code);
+ if (!fetched) {
+ toast.error("Course data could not be loaded.");
+ return;
+ }
+
+ const yearIndex = fetched.children.length - 1;
+ const yearFolder = fetched.children?.[yearIndex];
+
+ if (!yearFolder) {
+ toast.warn("No folders available for this course.");
+ dispatch(ChangeCurrentYearData(yearIndex, []));
+ dispatch(ChangeFolder(null));
+ return;
}
- dispatch(ChangeCurrentCourse(t.children, t.code));
+
+ const yearChildren = Array.isArray(yearFolder.children) ? yearFolder.children : [];
+
+ dispatch(ChangeCurrentYearData(yearIndex, yearChildren));
+ dispatch(ChangeFolder(yearFolder));
+ dispatch(ChangeCurrentCourse(fetched.children, fetched.code));
+ setInitial(false);
+ } catch (error) {
+ console.error( error);
+ toast.error("Something went wrong while loading the course.");
}
};
+
run();
};
+
let courseCode=course.code.replaceAll(" ", "")
useEffect(() => {
// console.log(currCourseCode);
@@ -174,7 +180,13 @@ const Collapsible = ({ course, color, state }) => {
{loading && "loading..."}
{error && "error"}
{notFound && "course not added yet"}
- {!loading && !error && !notFound &&
}
+ {!loading &&
+ !error &&
+ !notFound &&
+ currCourseCode?.toLowerCase() ===
+ course.code.replaceAll(" ", "").toLowerCase() && (
+
+ )}
);
diff --git a/client/src/screens/browse/components/folder-info/confirmDialog.jsx b/client/src/screens/browse/components/folder-info/confirmDialog.jsx
new file mode 100644
index 00000000..3cf37646
--- /dev/null
+++ b/client/src/screens/browse/components/folder-info/confirmDialog.jsx
@@ -0,0 +1,130 @@
+import React from "react";
+
+const styles = {
+ overlay: {
+ position: "fixed",
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ backgroundColor: "rgba(0, 0, 0, 0.4)",
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ zIndex: 1000,
+ },
+ dialog: {
+ backgroundColor: "#fff",
+ padding: "25px 30px",
+ borderRadius: "8px",
+ maxWidth: "400px",
+ width: "90%",
+ boxShadow: "0 4px 20px rgba(0, 0, 0, 0.2)",
+ textAlign: "center",
+ fontFamily: "sans-serif",
+ },
+ heading: {
+ fontSize: "2em",
+ marginBottom: "0.5em",
+ },
+ input: {
+ padding: "10px",
+ fontSize: "1em",
+ width: "100%",
+ marginBottom: "1.5em",
+ border: "1px solid #ccc",
+ borderRadius: "5px",
+ },
+ selectLabel: {
+ textAlign: "left",
+ fontWeight: "500",
+ marginBottom: "0.3em",
+ display: "block",
+ },
+ select: {
+ width: "100%",
+ padding: "10px",
+ fontSize: "1em",
+ marginBottom: "1.5em",
+ border: "1px solid #ccc",
+ borderRadius: "5px",
+ },
+ buttonGroup: {
+ display: "flex",
+ justifyContent: "center",
+ gap: "1em",
+ },
+ confirmBtn: {
+ backgroundColor: "#22c55e",
+ color: "#fff",
+ border: "none",
+ padding: "10px 18px",
+ borderRadius: "5px",
+ cursor: "pointer",
+ fontWeight: "bold",
+ fontSize: "1em",
+ },
+ cancelBtn: {
+ backgroundColor: "#9ca3af",
+ color: "#fff",
+ border: "none",
+ padding: "10px 18px",
+ borderRadius: "5px",
+ cursor: "pointer",
+ fontWeight: "bold",
+ fontSize: "1em",
+ },
+};
+
+const ConfirmDialog = ({
+ show,
+ input = false,
+ inputValue = "",
+ onInputChange = () => {},
+ childType = "",
+ onChildTypeChange = () => {},
+ onCancel,
+ onConfirm,
+ confirmText = "Confirm",
+ cancelText = "Cancel",
+}) => {
+ if (!show) return null;
+
+ return (
+
+
+
Enter Folder Name
+ {input && (
+
+ )}
+
+
+
+
+
+
+
+ );
+};
+
+export { ConfirmDialog };
diff --git a/client/src/screens/browse/components/folder-info/index.jsx b/client/src/screens/browse/components/folder-info/index.jsx
index f9a59846..fbc3da2c 100644
--- a/client/src/screens/browse/components/folder-info/index.jsx
+++ b/client/src/screens/browse/components/folder-info/index.jsx
@@ -4,11 +4,72 @@ import { CopyToClipboard } from "react-copy-to-clipboard";
import clientRoot from "../../../../api/client";
import Share from "../../../share";
import { useState } from "react";
-const FolderInfo = ({ isBR, path, name, canDownload, contributionHandler, folderId, courseCode }) => {
+import { createFolder } from "../../../../api/Folder";
+import { getCourse } from "../../../../api/Course";
+import { UpdateCourses } from "../../../../actions/filebrowser_actions";
+import { AddNewCourseLocal } from "../../../../actions/user_actions";
+import { useDispatch } from "react-redux";
+import { RefreshCurrentFolder } from "../../../../actions/filebrowser_actions";
+import {ConfirmDialog} from "./confirmDialog";
+
+const FolderInfo = ({
+ isBR,
+ path,
+ name,
+ canDownload,
+ contributionHandler,
+ folderId,
+ courseCode,
+}) => {
+ const dispatch = useDispatch();
+ const [showConfirm, setShowConfirm] = useState(false);
+ const [newFolderName, setNewFolderName] = useState("");
+ const [childType, setChildType] = useState("");
+
+
const handleShare = () => {
const sectionShare = document.getElementById("share");
sectionShare.classList.add("show");
};
+
+ const handleCreateFolder = () => {
+ setNewFolderName("");
+ setChildType("");
+ setShowConfirm(true);
+ };
+
+ const handleConfirmCreateFolder = async () => {
+ const folderName = newFolderName.trim();
+ if (!folderName?.trim() || !childType) return;
+
+ if (!courseCode || !folderId) {
+ toast.error("No course selected.");
+ return;
+ }
+
+ try {
+ const res = await getCourse(courseCode);
+ if (!res.data?.found) {
+ toast.error("Course not found. Cannot create folder.");
+ return;
+ }
+
+ await createFolder({
+ name: folderName.trim(),
+ course: courseCode,
+ parentFolder: folderId,
+ childType: childType,
+ });
+
+ toast.success(`Folder "${folderName}" created`);
+ dispatch(RefreshCurrentFolder());
+ } catch (error) {
+ console.log(error);
+ toast.error("Failed to create folder.");
+ }
+ setShowConfirm(false);
+ };
+
return (
<>
@@ -34,8 +95,7 @@ const FolderInfo = ({ isBR, path, name, canDownload, contributionHandler, folder
- {
- canDownload?
+ {canDownload ? (
{/*
*/}
-
+ ) : (
+ <>>
+ )}
+ {isBR && !canDownload && (
+
+
- {isBR? "Add File": "Contribute"}
+ Add Folder
- : <>>
- }
+ )}
+ setNewFolderName(e.target.value)}
+ childType={childType}
+ onChildTypeChange={setChildType}
+ onConfirm={handleConfirmCreateFolder}
+ onCancel={() => setShowConfirm(false)}
+ confirmText="Create"
+ cancelText="Cancel"
+ />
>
);
};
diff --git a/client/src/screens/browse/index.jsx b/client/src/screens/browse/index.jsx
index d6c861a4..38f56bb2 100644
--- a/client/src/screens/browse/index.jsx
+++ b/client/src/screens/browse/index.jsx
@@ -24,10 +24,12 @@ import { getCourse } from "../../api/Course";
import { toast } from "react-toastify";
import Share from "../share";
import FileController from "./components/collapsible/components/file-controller";
+import { RefreshCurrentFolder } from "../../actions/filebrowser_actions";
const BrowseScreen = () => {
const user = useSelector((state) => state.user);
const folderData = useSelector((state) => state.fileBrowser.currentFolder);
+ const refreshKey = useSelector((state) => state.fileBrowser.refreshKey);
const currCourse = useSelector((state) => state.fileBrowser.currentCourse);
const currCourseCode = useSelector((state) => state.fileBrowser.currentCourseCode);
const currYear = useSelector((state) => state.fileBrowser.currentYear);
@@ -43,7 +45,7 @@ const BrowseScreen = () => {
const { code, folderId } = useParams();
const fb = useSelector((state) => state.fileBrowser);
useEffect(() => {
- sessionStorage.removeItem("AllCourses");
+ sessionStorage.removeItem("AllCourses");
}, []);
useEffect(() => {
if (sessionStorage.getItem("AllCourses") !== null) {
@@ -132,7 +134,7 @@ const BrowseScreen = () => {
// console.log(fb);
// console.log(user);
// }, [fb, user]);
-
+
// const fetchCourseDataAgain = async (courseCode) => {
// try {
// const courseCode = currCourseCode ;
@@ -147,12 +149,38 @@ const BrowseScreen = () => {
// console.error("Error refetching course data:", error);
// }
// };
- const headerText=folderData?.path ?
- folderData.path
- : folderData?.childType==="Folder"? "Select a folder..."
- : folderData?.childType==="File"? "Select a file..."
- :currYear? "Select an year...":"Select a course..."
-
+
+ useEffect(() => {
+ const refreshFolderData = async () => {
+ if (!folderData?._id || !currCourseCode) return;
+
+ try {
+ const res = await getCourse(currCourseCode);
+ if (res.data?.found) {
+ const updatedFolder = findFolderById(res.data.children, folderData._id);
+ if (updatedFolder) {
+ dispatch(ChangeFolder(updatedFolder));
+ }
+ }
+ } catch (err) {
+ toast.error("Could not refresh folder view.");
+ }
+ };
+
+ refreshFolderData();
+ }, [refreshKey]);
+
+ const findFolderById = (folders, id) => {
+ for (const folder of folders) {
+ if (folder._id === id) return folder;
+ if (folder.children?.length) {
+ const result = findFolderById(folder.children, id);
+ if (result) return result;
+ }
+ }
+ return null;
+ };
+
return (
@@ -160,49 +188,61 @@ const BrowseScreen = () => {
+
MY COURSES
{user.localCourses?.length > 0
? ""
: user.user?.courses?.map((course, idx) => {
- return (
-
- );
- })}
+ return (
+
+ );
+ })}
{user.localCourses?.map((course, idx) => {
return ;
})}
+ PREVIOUS COURSES
+ {!(user.user?.isBR && user.user?.previousCourses?.length > 0)
+ ? ""
+ : user.user?.previousCourses?.map((course, idx) => {
+ return (
+
+ );
+ })}
-
+ {folderData && (
+
+ )}
- {!folderData ?
- headerText
- : folderData?.childType === "File" ? (
-
- ) : (
+ {!folderData ? (
+
Select a course
+ ) : folderData?.childType === "File" ? (
+
+ ) : folderData?.children?.length === 0 ? (
+
+
No folders available.
+
+ ) : (
folderData?.children.map((folder) => (
))
- )}
-
+ )}
- {/* // : folderData?.childType === "File"
+ {/* // : folderData?.childType === "File"
// ? folderData?.children?.map((file) => (
//
{
ChangeCurrentYearData(idx, currCourse[idx].children)
);
dispatch(ChangeFolder(currCourse[idx]));
+ dispatch(RefreshCurrentFolder());
}}
key={idx}
>
diff --git a/client/src/screens/browse/styles.scss b/client/src/screens/browse/styles.scss
index 645f2ec1..7452f95a 100644
--- a/client/src/screens/browse/styles.scss
+++ b/client/src/screens/browse/styles.scss
@@ -15,6 +15,12 @@
max-width: 300px;
border-right: 1px solid rgba(0, 0, 0, 0.33);
overflow-y: auto;
+ .heading{
+ padding: 10px 20px;
+ font-family: 'Bold';
+ font-size: 1.2rem;
+ background-color: #FECF6F;
+ }
}
.middle{
flex: 6;
@@ -27,6 +33,9 @@
flex-wrap: wrap;
// justify-content: space-between;
}
+ .empty-message{
+ font-size: 1.2rem;
+ }
&::-webkit-scrollbar{
display: none;
}
diff --git a/server/index.js b/server/index.js
index f6ffe7d1..09dd8d2a 100644
--- a/server/index.js
+++ b/server/index.js
@@ -29,6 +29,7 @@ import scheduleRoutes from "./modules/schedule/schedule.routes.js";
import snapshotRoutes from "./modules/snapshot/snapshot.routes.js";
import brRoutes from "./modules/br/br.routes.js";
import fileRoutes from "./modules/file/file.routes.js";
+import folderRoutes from "./modules/folder/folder.routes.js";
const app = express();
@@ -62,6 +63,7 @@ app.use("/api/schedule", scheduleRoutes);
app.use("/api/snapshot", snapshotRoutes);
app.use("/api/br", brRoutes);
app.use("/api/files", fileRoutes);
+app.use("/api/folder", folderRoutes);
app.use(
"/homepage",
diff --git a/server/modules/auth/auth.controller.js b/server/modules/auth/auth.controller.js
index a6075b99..8625230e 100644
--- a/server/modules/auth/auth.controller.js
+++ b/server/modules/auth/auth.controller.js
@@ -262,6 +262,9 @@ export const redirectHandler = async (req, res, next) => {
if (existingUser && !userUpdated) {
const courses = await fetchCourses(userFromToken.data.surname);
existingUser.courses = courses;
+ if (br) {
+ existingUser.previousCourses = await fetchCoursesForBr(userFromToken.data.surname);
+ }
existingUser.semester = calculateSemester(userFromToken.data.surname);
await existingUser.save();
const newUpdation = new UserUpdate({ rollNumber: roll });
diff --git a/server/modules/folder/folder.controller.js b/server/modules/folder/folder.controller.js
new file mode 100644
index 00000000..2e8d1ab6
--- /dev/null
+++ b/server/modules/folder/folder.controller.js
@@ -0,0 +1,39 @@
+import { FolderModel } from "../course/course.model.js";
+
+async function createFolder(req, res) {
+ const { name, course, parentFolder, childType } = req.body;
+ const newFolder = await FolderModel.create({
+ name,
+ course,
+ childType,
+ children: [],
+ });
+
+ if (parentFolder) {
+ const parent = await FolderModel.findById(parentFolder);
+ parent.children.push(newFolder._id);
+ await parent.save();
+ }
+
+ return res.json(newFolder);
+}
+async function deleteFolder(req, res) {
+ const { folderId, parentFolderId } = req.query;
+
+ try {
+ if (parentFolderId) {
+ await FolderModel.findByIdAndUpdate(parentFolderId, {
+ $pull: { children: folderId },
+ });
+ }
+ const deleted = await FolderModel.findByIdAndDelete(folderId);
+ if (!deleted) {
+ return res.status(404).json({ message: "Folder not found" });
+ }
+
+ return res.json({ success: true, folderId });
+ } catch (err) {
+ return res.status(500).json({ success: false, error: err.message });
+ }
+}
+export { createFolder, deleteFolder };
diff --git a/server/modules/folder/folder.routes.js b/server/modules/folder/folder.routes.js
new file mode 100644
index 00000000..9c51b366
--- /dev/null
+++ b/server/modules/folder/folder.routes.js
@@ -0,0 +1,11 @@
+import express from "express";
+import { createFolder,deleteFolder } from "./folder.controller.js";
+import isAuthenticated from "../../middleware/isAuthenticated.js";
+import {isBR} from "../../middleware/isBR.js"; // if it's a named export
+
+const router = express.Router();
+
+router.post("/create", isAuthenticated, isBR, createFolder);
+router.delete("/delete", isAuthenticated, isBR, deleteFolder);
+
+export default router;