vinodh0512/E-learning
0
1const express = require("express");2const cors = require("cors");3const { connectDB, getDB } = require("./db");4const { ObjectId } = require("mongodb");5const bcrypt = require("bcrypt");6 7const app = express();8app.use(cors());9app.use(express.json());10 11app.use((req, res, next) => {12 console.log(`[BACKEND LOG] ${req.method} ${req.url}`);13 next();14});15 16connectDB();17 18// DEBUG ROUTE19app.get('/test-erase', (req, res) => res.json({ message: "Erase route is reachable" }));20 21// -- DATA MANAGEMENT --22app.post('/admin/erase-database', async (req, res) => {23 console.log("CRITICAL: Erase database request received.");24 try {25 const db = getDB();26 const collections = ['students', 'instructors', 'courses', 'assignments', 'submissions', 'content', 'notes', 'notifications', 'enrollments'];27 for (const coll of collections) {28 await db.collection(coll).deleteMany({});29 }30 res.json({ message: "Institutional database reset successful." });31 } catch (error) {32 console.error("Erase error:", error);33 res.status(500).json({ error: error.message });34 }35});36 37app.get("/", (req, res) => {38 res.send("Backend is running ๐");39});40 41// -- DASHBOARD STATS --42app.get('/admin/stats', async (req, res) => {43 try {44 const db = getDB();45 const totalStudents = await db.collection('students').countDocuments();46 const totalTeachers = await db.collection('instructors').countDocuments();47 const activeCourses = await db.collection('courses').countDocuments(); // Total courses for now48 49 // For "Pending Approvals", we can count unevaluated submissions or courses with "Pending" status50 const pendingSubmissions = await db.collection('submissions').countDocuments({ evaluated: false });51 const pendingCourses = await db.collection('courses').countDocuments({ status: 'Pending' });52 const pendingApprovals = pendingSubmissions + pendingCourses;53 54 // Presence logic (mocking based on totals since no attendance system exists yet)55 // In a real system, this would query an attendance collection56 const studentsPresent = totalStudents > 0 ? Math.floor(totalStudents * 0.94) : 0;57 const facultyPresent = totalTeachers > 0 ? Math.floor(totalTeachers * 0.97) : 0;58 59 res.json({60 totalStudents: totalStudents,61 totalTeachers: totalTeachers,62 studentsPresent: studentsPresent,63 facultyPresent: facultyPresent,64 activeCourses: activeCourses,65 pendingApprovals: pendingApprovals66 });67 } catch (error) {68 res.status(500).json({ error: error.message });69 }70});71 72// -- AUTH & USERS --73app.post('/auth/login', async (req, res) => {74 try {75 const { role, identifier, password } = req.body;76 if (!['student', 'instructor', 'admin'].includes(role)) return res.status(400).json({ error: 'Invalid role' });77 78 const db = getDB();79 const collectionName = role + 's';80 81 const user = await db.collection(collectionName).findOne({82 $or: [{ email: identifier }, { username: identifier }]83 });84 85 if (user) {86 let isMatch = false;87 if (user.password && !user.password.startsWith('$2b$')) {88 isMatch = (user.password === password);89 if(isMatch) {90 const hashedPassword = await bcrypt.hash(password, 10);91 await db.collection(collectionName).updateOne(92 { _id: user._id },93 { $set: { password: hashedPassword } }94 );95 }96 } else {97 isMatch = await bcrypt.compare(password, user.password);98 }99 100 if (isMatch) {101 res.json({ 102 user: { 103 _id: user._id, 104 username: user.username, 105 email: user.email, 106 role,107 dept: user.dept || '',108 year: user.year || '',109 section: user.section || '',110 rollno: user.rollno || '',111 batch: user.batch || ''112 } 113 });114 } else {115 res.status(401).json({ error: 'Invalid credentials' });116 }117 } else {118 res.status(401).json({ error: 'Invalid credentials' });119 }120 } catch (error) {121 res.status(500).json({ error: error.message });122 }123});124 125app.post('/admin/create-user', async (req, res) => {126 try {127 const { role, password, ...otherData } = req.body;128 if (!['student', 'instructor'].includes(role)) return res.status(400).json({ error: 'Invalid role' });129 const db = getDB();130 const hashedPassword = await bcrypt.hash(password, 10);131 const result = await db.collection(role + 's').insertOne({ 132 ...otherData, 133 password: hashedPassword 134 });135 res.json({ message: "User created", result });136 } catch (error) {137 res.status(500).json({ error: error.message });138 }139});140 141app.put('/users/:role/:id', async (req, res) => {142 try {143 const { role, id } = req.params;144 const { password, ...updateData } = req.body;145 if (!['student', 'instructor', 'admin'].includes(role)) return res.status(400).json({ error: 'Invalid role' });146 147 const db = getDB();148 if (password) {149 updateData.password = await bcrypt.hash(password, 10);150 }151 152 let query;153 try {154 query = { _id: new ObjectId(id) };155 } catch(e) {156 query = { _id: id }; // Fallback for string-based IDs157 }158 159 const result = await db.collection(role + 's').updateOne(160 query,161 { $set: updateData }162 );163 res.json(result);164 } catch (error) {165 res.status(500).json({ error: error.message });166 }167});168 169app.get('/student', async (req, res) => {170 const db = getDB();171 const students = await db.collection("students").find({}).project({ password: 0 }).toArray();172 res.json(students);173});174 175app.get('/instructor', async (req, res) => {176 const db = getDB();177 const instructors = await db.collection("instructors").find({}).project({ password: 0 }).toArray();178 res.json(instructors);179});180 181app.get('/users/:role/:id', async (req, res) => {182 try {183 const { role, id } = req.params;184 const db = getDB();185 186 let query;187 try {188 query = { _id: new ObjectId(id) };189 } catch(e) {190 query = { _id: id }; // Fallback for string-based IDs191 }192 193 const user = await db.collection(role + 's').findOne(query, { projection: { password: 0 } });194 res.json(user);195 } catch (error) {196 res.status(500).json({ error: error.message });197 }198});199 200app.delete('/users/:role/:id', async (req, res) => {201 try {202 const { role, id } = req.params;203 const db = getDB();204 const result = await db.collection(role + 's').deleteOne({ _id: new ObjectId(id) });205 res.json(result);206 } catch (error) {207 res.status(500).json({ error: error.message });208 }209});210 211// -- COURSES --212app.post('/courses', async (req, res) => {213 try {214 const db = getDB();215 const result = await db.collection('courses').insertOne(req.body);216 res.json(result);217 } catch (error) {218 res.status(500).json({ error: error.message });219 }220});221 222app.get('/courses', async (req, res) => {223 try {224 const db = getDB();225 const courses = await db.collection('courses').find({}).toArray();226 res.json(courses);227 } catch (error) {228 res.status(500).json({ error: error.message });229 }230});231 232app.put('/courses/:id', async (req, res) => {233 try {234 const { id } = req.params;235 const db = getDB();236 237 let query;238 try {239 query = { _id: new ObjectId(id) };240 } catch(e) {241 query = { _id: id }; // Fallback for string-based IDs242 }243 244 const result = await db.collection('courses').updateOne(245 query,246 { $set: req.body }247 );248 console.log(`[BACKEND] Course Update Outcome for ${id}: Matched=${result.matchedCount}, Modified=${result.modifiedCount}`);249 res.json(result);250 } catch (error) {251 res.status(500).json({ error: error.message });252 }253});254 255app.delete('/courses/:id', async (req, res) => {256 try {257 const { id } = req.params;258 const db = getDB();259 const result = await db.collection('courses').deleteOne({ _id: new ObjectId(id) });260 res.json(result);261 } catch(error) {262 res.status(500).json({ error: error.message });263 }264});265 266// -- ASSIGNMENTS --267app.post('/assignments', async (req, res) => {268 try {269 const db = getDB();270 const result = await db.collection('assignments').insertOne(req.body);271 res.json(result);272 } catch (error) {273 res.status(500).json({ error: error.message });274 }275});276 277app.get('/assignments', async (req, res) => {278 try {279 const db = getDB();280 const assignments = await db.collection('assignments').find({}).toArray();281 res.json(assignments);282 } catch (error) {283 res.status(500).json({ error: error.message });284 }285});286 287app.delete('/assignments/:id', async (req, res) => {288 try {289 const { id } = req.params;290 const db = getDB();291 const result = await db.collection('assignments').deleteOne({ _id: new ObjectId(id) });292 res.json(result);293 } catch (error) {294 res.status(500).json({ error: error.message });295 }296});297 298// -- SUBMISSIONS --299app.post('/submissions', async (req, res) => {300 try {301 const db = getDB();302 const submission = {303 ...req.body,304 createdAt: new Date(),305 evaluated: false,306 grade: '',307 feedback: ''308 };309 const result = await db.collection('submissions').insertOne(submission);310 311 await db.collection('assignments').updateOne(312 { _id: new ObjectId(req.body.assignmentId) },313 { $inc: { submissionsCount: 1 } }314 );315 316 res.json(result);317 } catch (error) {318 res.status(500).json({ error: error.message });319 }320});321 322app.get('/submissions/:assignmentId', async (req, res) => {323 try {324 const { assignmentId } = req.params;325 const db = getDB();326 const submissions = await db.collection('submissions').find({ assignmentId }).toArray();327 res.json(submissions);328 } catch (error) {329 res.status(500).json({ error: error.message });330 }331});332 333app.put('/submissions/:id', async (req, res) => {334 try {335 const { id } = req.params;336 const { grade, feedback } = req.body;337 const db = getDB();338 const result = await db.collection('submissions').updateOne(339 { _id: new ObjectId(id) },340 { $set: { grade, feedback, evaluated: true } }341 );342 res.json(result);343 } catch (error) {344 res.status(500).json({ error: error.message });345 }346});347 348// -- CONTENT --349app.post('/content', async (req, res) => {350 try {351 const db = getDB();352 const result = await db.collection('content').insertOne(req.body);353 res.json(result);354 } catch (error) {355 res.status(500).json({ error: error.message });356 }357});358 359app.get('/content', async (req, res) => {360 try {361 const db = getDB();362 const content = await db.collection('content').find({}).toArray();363 res.json(content);364 } catch (error) {365 res.status(500).json({ error: error.message });366 }367});368 369app.put('/content/:id', async (req, res) => {370 try {371 const { id } = req.params;372 const { items } = req.body;373 const db = getDB();374 const result = await db.collection('content').updateOne(375 { _id: new ObjectId(id) },376 { $set: { items } }377 );378 res.json(result);379 } catch (error) {380 res.status(500).json({ error: error.message });381 }382});383 384// -- NOTES --385app.post('/notes', async (req, res) => {386 try {387 const db = getDB();388 const result = await db.collection('notes').insertOne(req.body);389 res.json(result);390 } catch (error) {391 res.status(500).json({ error: error.message });392 }393});394 395app.get('/notes', async (req, res) => {396 try {397 const db = getDB();398 const notes = await db.collection('notes').find({}).toArray();399 res.json(notes);400 } catch (error) {401 res.status(500).json({ error: error.message });402 }403});404 405app.put('/notes/:id/upvote', async (req, res) => {406 try {407 const { id } = req.params;408 const db = getDB();409 const result = await db.collection('notes').updateOne(410 { _id: new ObjectId(id) },411 { $inc: { upvotes: 1 } }412 );413 res.json(result);414 } catch (error) {415 res.status(500).json({ error: error.message });416 }417});418 419// -- NOTIFICATIONS --420app.post('/notifications', async (req, res) => {421 try {422 const db = getDB();423 const notification = {424 ...req.body,425 createdAt: new Date()426 };427 const result = await db.collection('notifications').insertOne(notification);428 res.json(result);429 } catch (error) {430 res.status(500).json({ error: error.message });431 }432});433 434app.get('/notifications', async (req, res) => {435 try {436 const db = getDB();437 const notifications = await db.collection('notifications').find({}).sort({ createdAt: -1 }).toArray();438 res.json(notifications);439 } catch (error) {440 res.status(500).json({ error: error.message });441 }442});443 444// -- ENROLLMENTS --445app.post('/enroll', async (req, res) => {446 try {447 const { studentId, courseId } = req.body;448 console.log(`Enrollment Request: Student=${studentId}, Course=${courseId}`);449 const db = getDB();450 const existing = await db.collection('enrollments').findOne({ studentId, courseId });451 if (existing) {452 console.log("Already enrolled.");453 return res.status(400).json({ error: 'Already enrolled' });454 }455 const result = await db.collection('enrollments').insertOne({ studentId, courseId, status: 'Active' });456 console.log("Enrollment successful.");457 res.json(result);458 } catch (error) { 459 console.error("Enrollment error:", error);460 res.status(500).json({ error: error.message }); 461 }462});463 464app.get('/enroll/:studentId', async (req, res) => {465 try {466 const { studentId } = req.params;467 const db = getDB();468 const enrollments = await db.collection('enrollments').find({ studentId }).toArray();469 res.json(enrollments);470 } catch (error) { res.status(500).json({ error: error.message }); }471});472 473const path = require('path');474app.use(express.static(path.join(__dirname, 'public')));475 476// Catch-all middleware to serve the frontend index.html477app.use((req, res) => {478 res.sendFile(path.join(__dirname, 'public', 'index.html'));479});480 481const PORT = process.env.PORT || 5000;482app.listen(PORT, "0.0.0.0", () => {483 console.log(`Server running on port ${PORT}`);484});485 