anas044/attendance-nodejs
0
1const axios = require("axios")2const path = require('path')3const fs = require('fs')4const pool = require("../pg/pg");5const bcrypt = require("bcrypt");6const { generateAccessToken, generateRefreshToken, generateJitsiToken } = require("../utils/tokens");7const { generateRoomId } = require("../utils/functions");8const UPLOADS_DIR = path.join(__dirname, '..', 'uploads');9const { Resend } = require('resend');10const resend = new Resend(process.env.RESENDAPIKEY);11 12exports.signup = async (req, res) => {13 try {14 const { email, firstName, lastName, password } = req.body;15 16 if (!email || !firstName || !lastName || !password) {17 return res.status(400).json({ 18 message: "All fields are required: email, firstName, lastName, password" 19 });20 }21 22 const existing = await pool.query(23 "SELECT * FROM users WHERE email = $1",24 [email]25 );26 27 if (existing.rows.length > 0) {28 return res.status(400).json({ 29 message: "Email already in use. Please login or use a different email." 30 });31 }32 33 const hashedPassword = await bcrypt.hash(password, 10);34 35 const result = await pool.query(36 `INSERT INTO users (email, first_name, last_name, password_hash, role)37 VALUES ($1, $2, $3, $4, $5) 38 RETURNING id, email, first_name, last_name, role, created_at`,39 [email, firstName, lastName, hashedPassword, 'student'] 40 );41 42 const newUser = result.rows[0];43 44 45 46 return res.status(201).json({47 success: true,48 message: "User registered successfully",49 });50 51 } catch (err) {52 console.error('Signup error:', err);53 54 if (err.code === '23505') { 55 return res.status(400).json({ 56 message: "Email already exists. Please use a different email." 57 });58 }59 60 if (err.code === '22001') { 61 return res.status(400).json({ 62 message: "One or more fields are too long." 63 });64 }65 66 return res.status(500).json({ 67 message: "Server error during registration. Please try again." 68 });69 }70};71 72exports.login = async (req, res) => {73 try {74 const { email, password } = req.body;75 76 const result = await pool.query(77 "SELECT * FROM users WHERE email = $1",78 [email]79 );80 81 if (result.rows.length === 0) {82 return res.status(400).json({ message: "Invalid credentials" });83 }84 85 const user = result.rows[0];86 87 const valid = await bcrypt.compare(password, user.password_hash);88 if (!valid) return res.status(400).json({ message: "Invalid credentials" });89 90 const payload = { id: user.id, email: user.email, role: user.role };91 const accessToken = generateAccessToken(payload);92 const refreshToken = generateRefreshToken(payload);93 94 await pool.query("insert into refreshtokens (token, user_id) VALUES ($1,$2)", [95 refreshToken,96 user.id,97 ]);98 99 const seid = {100 user_id: user.id,101 firstName: user.first_name,102 lastName: user.last_name,103 profile_image: user.profile_image,104 role: user.role105 106 }107 108 res.cookie("refresh", refreshToken, {109 httpOnly: true,110 secure: true,111 sameSite: "strict",112 path: '/api/refresh',113 maxAge: 7 * 24 * 60 * 60 * 1000,114 });115 116 res.cookie("access", accessToken, {117 httpOnly: true,118 secure: true,119 sameSite: "lax",120 maxAge: 15 * 60 * 1000,121 });122 123 res.cookie('details', JSON.stringify(seid), {124 httpOnly: false,125 secure: false,126 sameSite: 'strict',127 path: '/',128 maxAge: undefined129 });130 131 return res.status(200).json({132 message: "Login successful",133 134 135 });136 137 } catch (err) {138 console.log(err);139 res.status(500).json({ message: err.message });140 }141};142 143exports.refreshToken = async (req, res) => {144 const jwt = req.cookies.refresh;145 if (!jwt)146 return res.status(401).json({ message: "No refresh token provided" });147 148 try {149 const decoded = require("jsonwebtoken").verify(150 jwt,151 process.env.REFRESH_TOKEN_SECRET152 );153 154 const accessToken = generateAccessToken({155 id: decoded.id,156 email: decoded.email,157 role: decoded.role,158 });159 160 res.cookie("access", accessToken, {161 httpOnly: true,162 secure: true,163 sameSite: "lax",164 maxAge: 15 * 60 * 1000,165 });166 167 return res.status(200).json({ message: 'access token sent' })168 } catch (err) {169 return res.status(403).json({ message: "Invalid refresh token" });170 }171};172 173exports.checkFaceChange = async (req, res) => {174 try {175 const check = await pool.query('select face_change from users where id = $1', [req.user.id])176 const check2 = await pool.query(`select * from face_change where status = 'pending' and student_id = $1 `, [req.user.id])177 return res.status(200).json({ message: check.rows[0].face_change, message2: check2.rowCount > 0 ? true : false });178 179 } catch (error) {180 console.log(error)181 return res.status(500).json({ message: "server error" });182 183 }184}185 186exports.requestFaceChange = async (req, res) => {187 try {188 await pool.query('insert into face_change (student_id) values ($1)', [req.user.id])189 return res.status(200).json({ message: 'Success' });190 191 } catch (error) {192 return res.status(500).json({ message: "Server error" });193 194 }195}196exports.getRequestFaceChange = async (req, res) => {197 try {198 const { course_id } = req.body199 if (req.user.role !== 'teacher') {200 return res.status(401).json({ message: 'unauthorized' })201 202 }203 const ress = await pool.query('select fc.*, u.first_name, u.last_name from face_change fc left join users u on fc.student_id = u.id left join student_courses sc on u.id = sc.student_id left join teacher_courses tc on sc.course_id = tc.course_id where tc.teacher_id = $1 and tc.course_id = $2', [req.user.id, parseInt(course_id, 10)])204 return res.status(200).json({ message: ress.rows[0] });205 206 } catch (error) {207 console.log(error)208 return res.status(500).json({ message: "Server error" });209 210 }211}212exports.manageRequestFaceChange = async (req, res) => {213 try {214 const { ans, rid, student_id } = req.body215 216 if (req.user.role !== 'teacher') {217 return res.status(401).json({ message: 'unauthorized' })218 219 }220 await pool.query(`update face_change set status = $1 where id = $2`, [ans ? 'approved' : 'declined', rid])221 await pool.query('update users set face_change = $1 where id = $2 ', [ans ? 'true' : 'false', student_id])222 return res.status(200).json({ message: 'Success' });223 224 } catch (error) {225 return res.status(500).json({ message: "Server error" });226 227 }228}229 230exports.processFrame = async (req, res) => {231 try {232 const { frame } = req.body;233 234 if (!frame) {235 return res.status(400).json({ error: "Frame not provided" });236 }237 238 const check = await pool.query('select face_change from users where id = $1', [req.user.id])239 if (!check.rows[0].face_change) {240 return res.status(500).json({ message: 'you do not have access to change your face encoding' });241 }242 const base64Data = frame.replace(/^data:image\/\w+;base64,/, "");243 244 const result = await axios.post(245 "https://anas044-attendance.hf.space/encode_face",246 { image: base64Data },247 { headers: { "Content-Type": "application/json" } }248 );249 250 user_id = req.user.id251 252 await pool.query("update users set face_encoding= $1 where id = $2", [253 result.data.encoding,254 user_id,255 ]);256 await pool.query('update users set face_change = false where id = $1', [req.user.id])257 258 return res.json({ message: 'encoding saved' });259 260 261 } catch (err) {262 console.log(err)263 const status = err.response?.status || 500;264 const message = err.response?.data?.detail || err.response?.data?.message || "Internal server error";265 266 return res.status(status).json({ message: message });267 }268 269}270 271exports.saveCourse = async (req, res) => {272 try {273 const { name, schedule } = req.body274 275 const teacher_id = req.user.id276 const save = await pool.query('insert into courses (name, schedule) values ($1 ,$2) returning *', [name, schedule])277 await pool.query('insert into teacher_courses (teacher_id, course_id) values ($1 ,$2)', [teacher_id, save.rows[0].id])278 279 return res.status(200).json({ message: 'saved' })280 } catch (error) {281 return res.status(500).json({ message: 'server error' });282 283 }284}285 286exports.updateCourse = async (req, res) => {287 try {288 const { id, name, schedule } = req.body289 290 const teacher_id = req.user.id291 292 const check = await pool.query('select * from teacher_courses where teacher_id = $1 and course_id = $2', [teacher_id, id])293 294 if (check.rowCount === 0) {295 return res.status(401).json({ message: 'unauthorized' })296 }297 298 await pool.query('update courses set name = $1, schedule = $2 where id = $3', [name, schedule, id])299 return res.status(200).json({ message: 'updated' })300 } catch (error) {301 console.log(error)302 return res.status(500).json({ message: 'server error' });303 304 }305}306 307exports.deleteCourse = async (req, res) => {308 try {309 const { id } = req.body310 311 const teacher_id = req.user.id312 313 const check = await pool.query('select * from teacher_courses where teacher_id = $1 and course_id = $2', [teacher_id, id])314 315 if (check.rowCount === 0) {316 return res.status(401).json({ message: 'unauthorized' })317 }318 319 await pool.query('delete from courses where id = $1', [id])320 return res.status(200).json({ message: 'updated' })321 } catch (error) {322 console.log(error)323 return res.status(500).json({ message: 'server error' });324 325 }326}327 328exports.addStudentToCourse = async (req, res) => {329 try {330 const { student_id, course_id } = req.body331 332 const teacher_id = req.user.id333 334 const check = await pool.query('select * from teacher_courses where teacher_id = $1 and course_id = $2', [teacher_id, course_id])335 336 if (check.rowCount === 0) {337 return res.status(401).json({ message: 'unauthorized' })338 }339 340 await pool.query('insert into student_courses (student_id, course_id) values ($1 ,$2)', [student_id, course_id])341 return res.status(200).json({ message: 'saved' })342 } catch (error) {343 344 return res.status(500).json({ message: 'server error' });345 346 }347}348 349exports.getCourseStudents = async (req, res) => {350 try {351 const { id } = req.body352 const teacher_id = req.user.id353 354 const check = await pool.query('select * from teacher_courses where teacher_id = $1 and course_id = $2', [teacher_id, id])355 356 if (check.rowCount === 0) {357 return res.status(401).json({ message: 'unauthorized' })358 }359 const students = await pool.query('select sc.*, u.first_name, u.last_name, u.profile_image from student_courses sc left join users u on sc.student_id = u.id where course_id = $1', [id])360 return res.status(200).json({ message: students.rows })361 } catch (error) {362 console.log(error)363 return res.status(500).json({ message: 'server error' });364 365 }366}367 368exports.getCourses = async (req, res) => {369 try {370 const teacher_id = req.user.id371 372 const result = await pool.query(373 `SELECT c.*374 FROM teacher_courses tc375 JOIN courses c ON tc.course_id = c.id376 WHERE tc.teacher_id = $1`,377 [teacher_id]378 ); return res.status(200).json({ message: result.rows })379 } catch (error) {380 381 return res.status(500).json({ message: 'server error' });382 383 }384}385 386exports.removeStudentFromCourse = async (req, res) => {387 try {388 const { student_id, course_id } = req.body389 390 const teacher_id = req.user.id391 392 const check = await pool.query('select * from teacher_courses where teacher_id = $1 and course_id = $2', [teacher_id, course_id])393 394 if (check.rowCount === 0) {395 return res.status(401).json({ message: 'unauthorized' })396 }397 await pool.query('delete from student_courses where student_id = $1 and course_id = $2', [student_id, course_id])398 await pool.query('delete from courses_registrations where student_id = $1 and course_id = $2', [student_id, course_id])399 400 return res.status(200).json({ message: 'updated' })401 } catch (error) {402 403 return res.status(500).json({ message: 'server error' });404 405 }406}407 408exports.getStudentCourses = async (req, res) => {409 try {410 const id = req.user.id411 const courses = await pool.query('select c.* from courses c JOIN student_courses sc ON sc.course_id = c.id WHERE sc.student_id = $1 ', [id])412 console.log(courses.rows)413 return res.status(200).json({ message: courses.rows })414 } catch (error) {415 return res.status(500).json({ message: 'server error' });416 417 }418}419 420exports.getStudentSessions = async (req, res) => {421 try {422 const { course_id } = req.body423 424 const check = await pool.query('select * from student_courses where course_id = $1 and student_id = $2', [course_id, req.user.id])425 if (check.rowCount === 0) {426 return res.status(401).json({ message: 'unauthorized' })427 }428 const sessions = await pool.query('select * from sessions where class_id = $1', [course_id])429 return res.status(200).json({ message: sessions.rowCount > 0 ? sessions.rows : 'no sessions' })430 } catch (error) {431 console.log(error)432 return res.status(500).json({ message: 'server error' });433 434 }435}436 437exports.getSessions = async (req, res) => {438 try {439 440 const { course_id } = req.body441 442 const teacher_id = req.user.id443 444 const check = await pool.query('select * from teacher_courses where teacher_id = $1 and course_id = $2', [teacher_id, course_id])445 446 if (check.rowCount === 0) {447 return res.status(401).json({ message: 'unauthorized' })448 }449 450 const sessions = await pool.query('select * from sessions where class_id = $1', [course_id])451 return res.status(200).json({ message: sessions.rowCount > 0 ? sessions.rows : 'no sessions' })452 453 } catch (error) {454 return res.status(500).json({ message: 'server error' });455 456 }457}458 459exports.getStudentAttendance = async (req, res) => {460 try {461 const { student_id, session_id } = req.body;462 463 console.log(req.body)464 465 const course = await pool.query('select * from sessions where id = $1', [session_id])466 467 const teacher_id = req.user.id468 469 const check = await pool.query('select * from teacher_courses where teacher_id = $1 and course_id = $2', [teacher_id, course.rows[0].class_id])470 471 if (check.rowCount === 0) {472 return res.status(401).json({ message: 'unauthorized' })473 }474 const user = await pool.query('select first_name, last_name from users where id = $1', [student_id])475 console.log(user.rows[0])476 const attendance = await pool.query('select a.*, u.first_name, u.last_name from attendance a left join users u on a.student_id = u.id where session_id = $1 and student_id = $2', [session_id, student_id])477 return res.status(200).json({ message: attendance.rowCount > 0 ? attendance.rows : 'Absent', fullName: user.rows[0] })478 479 } catch (error) {480 console.log(error)481 return res.status(500).json({ message: 'server error' });482 483 }484}485 486exports.deleteSession = async (req, res) => {487 try {488 489 const { session_id } = req.body490 491 const course = await pool.query('select class_id from sessions where id =$1', [session_id])492 493 const teacher_id = req.user.id494 495 const check = await pool.query('select * from teacher_courses where teacher_id = $1 and course_id = $2', [teacher_id, course.rows[0].class_id])496 497 if (check.rowCount === 0) {498 return res.status(401).json({ message: 'unauthorized' })499 }500 501 const sessions = await pool.query('delete from sessions where id = $1', [session_id])502 return res.status(200).json({ message: 'Deleted' })503 504 } catch (error) {505 return res.status(500).json({ message: 'server error' });506 507 }508}509exports.createSession = async (req, res) => {510 try {511 const {512 class_id,513 room_name,514 scheduled_time,515 end_scheduled_time516 } = req.body;517 518 const teacher_id = req.user.id;519 const teacherCheck = await pool.query(520 `SELECT * FROM teacher_courses 521 WHERE teacher_id = $1 AND course_id = $2`,522 [teacher_id, class_id]523 );524 525 if (teacherCheck.rowCount === 0) {526 return res.status(403).json({527 message: 'You are not authorized to create sessions for this course'528 });529 }530 531 const courseCheck = await pool.query(532 `SELECT name FROM courses WHERE id = $1`,533 [class_id]534 );535 536 if (courseCheck.rowCount === 0) {537 return res.status(404).json({538 message: 'Course not found'539 });540 }541 const courseName = courseCheck.rows[0].name;542 543 const roomId = generateRoomId(class_id, teacher_id);544 545 546 547 const defaultSettings = {548 enable_waiting_room: true,549 allow_recordings: false,550 enable_chat: true,551 enable_screen_sharing: false,552 start_with_audio_muted: true,553 start_with_video_muted: false,554 require_display_name: true,555 enable_close_page: false556 };557 558 const sessionSettings = {559 ...defaultSettings,560 };561 562 563 564 const sessionData = {565 class_id,566 teacher_id,567 room_name,568 jitsi_room_id: roomId,569 start_time: scheduled_time || new Date(),570 end_time: end_scheduled_time,571 settings: JSON.stringify(sessionSettings),572 is_active: false,573 session_status: 'scheduled',574 end_time: null575 };576 577 const insertQuery = `578 INSERT INTO sessions (579 class_id, teacher_id, room_name, jitsi_room_id, 580 start_time, settings, is_active, session_status, end_time581 ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) 582 RETURNING *583 `;584 585 const sessionResult = await pool.query(insertQuery, [586 sessionData.class_id,587 sessionData.teacher_id,588 sessionData.room_name,589 sessionData.jitsi_room_id,590 sessionData.start_time,591 sessionData.settings,592 sessionData.is_active,593 sessionData.session_status,594 sessionData.end_time595 596 597 ]);598 const createdSession = sessionResult.rows[0];599 600 const response = {601 message: 'Session created successfully',602 603 604 };605 606 607 return res.status(201).json(response);608 609 610 } catch (error) {611 console.log(error)612 613 res.status(500).json({ message: 'server error' })614 }615}616 617const jwt = require('jsonwebtoken');618const { transporter } = require("../utils/mail");619 620exports.getSessionToken = async (req, res) => {621 try {622 const { session_id } = req.body;623 console.log('Getting session token for session:', session_id);624 625 // Get session details626 const room_id = await pool.query(627 'SELECT jitsi_room_id, is_active, start_time, end_time FROM sessions WHERE id = $1',628 [session_id]629 );630 631 if (room_id.rowCount === 0) {632 return res.status(404).json({ message: 'no session with that id' })633 }634 635 const session = room_id.rows[0];636 637 if (!session.is_active) {638 console.log('Session not active:', session.is_active);639 return res.status(400).json({ message: 'session not active' })640 }641 642 643 644 const get = await pool.query(645 'SELECT * FROM session_tokens WHERE session_id = $1 AND user_id = $2',646 [session_id, req.user.id]647 );648 649 let token;650 651 if (get.rowCount === 0) {652 return res.status(401).json({ message: 'verify your face' })653 } else {654 const existingToken = get.rows[0].token;655 656 try {657 const decoded = jwt.decode(existingToken);658 659 if (!decoded) {660 return res.status(401).json({ message: 'verify your face' })661 }662 663 const currentTime = Math.floor(Date.now() / 1000);664 665 if (decoded.exp && decoded.exp < currentTime) {666 667 await pool.query(668 'DELETE FROM session_tokens WHERE session_id = $1 AND user_id = $2',669 [session_id, req.user.id]670 );671 672 return res.status(401).json({ message: 'verify your face' })673 } else {674 token = existingToken;675 const expiresIn = decoded.exp ? decoded.exp - currentTime : null;676 }677 } catch (decodeError) {678 return res.status(401).json({ message: 'verify your face' })679 }680 }681 682 return res.status(200).json({683 message: token,684 room_name: session.jitsi_room_id685 });686 687 } catch (error) {688 res.status(500).json({ message: 'server error' })689 }690}691 692exports.verifyFace = async (req, res) => {693 try {694 const { frame } = req.body;695 696 if (!frame) {697 return res.status(400).json({ error: "Frame not provided" });698 }699 700 const base64Data = frame.replace(/^data:image\/\w+;base64,/, "");701 user_id = req.user.id702 703 const check = await pool.query("select face_encoding from users where id = $1", [704 user_id,705 ]);706 if (check.rows[0].face_encoding === null || !check.rows[0].face_encoding) {707 return res.status(500).json({ message: 'no face encoding saved' })708 709 }710 711 const result = await axios.post(712 "https://anas044-attendance.hf.space/verify_face",713 { image: base64Data, encoding: check.rows[0].face_encoding },714 { headers: { "Content-Type": "application/json" } }715 );716 if (result.data.verified) {717 const { session_id } = req.body718 console.log(session_id)719 const room_id = await pool.query('select jitsi_room_id, is_active from sessions where id = $1', [session_id])720 721 if (room_id.rowCount === 0) {722 return res.status(404).json({ message: 'no session with that id' })723 }724 725 if (!room_id.rows[0].is_active) {726 console.log(room_id.rows[0].isactive)727 return res.status(400).json({ message: 'session not active' })728 729 }730 731 let isTeacher = false;732 let room_name;733 734 const role = req.user.role;735 if (role === 'teacher') {736 const teacherCheck = await pool.query(737 'SELECT * FROM sessions WHERE id = $1 AND teacher_id = $2',738 [session_id, req.user.id]739 );740 741 isTeacher = teacherCheck.rowCount > 0;742 743 }744 token = generateJitsiToken(req.user, room_id.rows[0].jitsi_room_id, isTeacher)745 await pool.query('insert into session_tokens (session_id, user_id, token) values ($1, $2, $3)', [session_id, req.user.id, token])746 }747 748 749 return res.status(200).json({ message: result.data.verified ? 'verified' : 'not verified' });750 751 752 } catch (err) {753 console.log(err)754 const status = err.response?.status || 500;755 const message = err.response?.data?.detail || err.response?.data?.message || "Internal server error";756 757 return res.status(status).json({ message: message });758 }759 760}761exports.startSession = async (req, res) => {762 try {763 const { session_id } = req.body764 765 const teacherCheck = await pool.query(766 `SELECT s.* FROM sessions s767 JOIN teacher_courses tc ON s.class_id = tc.course_id768 WHERE s.id = $1 AND tc.teacher_id = $2`,769 [session_id, req.user.id]770 );771 if (teacherCheck.rowCount === 0) {772 return res.status(401).json({ message: 'not authorized' })773 };774 775 await pool.query(`update sessions set session_status = 'active', is_active = true where id = $1`, [session_id])776 return res.status(200).json({ message: 'updated' })777 778 } catch (error) {779 console.log(error)780 res.status(500).json({ message: 'server error' })781 782 }783}784 785exports.endSession = async (req, res) => {786 try {787 const { session_id } = req.body788 789 const teacherCheck = await pool.query(790 `SELECT s.* FROM sessions s791 JOIN teacher_courses tc ON s.class_id = tc.course_id792 WHERE s.id = $1 AND tc.teacher_id = $2`,793 [session_id, req.user.id]794 );795 if (teacherCheck.rowCount === 0) {796 return res.status(401).json({ message: 'not authorized' })797 };798 799 await pool.query(`update sessions set session_status = 'ended', is_active = false where id = $1`, [session_id])800 return res.status(200).json({ message: 'updated' })801 802 } catch (error) {803 console.log(error)804 res.status(500).json({ message: 'server error' })805 806 }807}808 809exports.courseRegister = async (req, res) => {810 try {811 const { course_id } = req.body812 const user_id = req.user.id813 814 await pool.query('insert into courses_registrations (course_id, student_id) values ($1,$2)', [course_id, user_id])815 return res.status(200).json({ message: 'saved' })816 } catch (error) {817 res.status(500).json({ message: 'server error' })818 819 }820}821 822exports.courseRegistrRes = async (req, res) => {823 try {824 const { request_id, isAccepted } = req.body825 const user_id = req.user.id826 console.log(req.body)827 828 const check = await pool.query('select tc.* from teacher_courses tc JOIN courses_registrations cr ON tc.course_id = cr.course_id where tc.teacher_id = $1', [user_id])829 if (check.rowCount === 0) {830 return res.status(401).json({ message: 'not authorized' })831 }832 833 const save = await pool.query('update courses_registrations set status = $1 where id = $2 returning *', [isAccepted ? 'approved' : 'declined', request_id])834 835 if (isAccepted) {836 await pool.query('insert into student_courses (student_id, course_id) values ($1 ,$2)', [save.rows[0].student_id, save.rows[0].course_id])837 }838 return res.status(200).json({ message: 'saved' })839 } catch (error) {840 console.log(error)841 res.status(500).json({ message: 'server error' })842 843 }844}845 846exports.GetcourseRegistrRes = async (req, res) => {847 try {848 const user_id = req.user.id849 850 const check = await pool.query('select cr.id, c.name, u.first_name, u.last_name, cr.student_id from teacher_courses tc JOIN courses_registrations cr ON tc.course_id = cr.course_id join courses c on c.id = cr.course_id left join users u on cr.student_id = u.id where tc.teacher_id = $1 and cr.status =$2', [user_id, 'pending'])851 // if (check.rowCount === 0) {852 // return res.status(401).json({ message: 'not authorized' })853 // }854 855 // const save = await pool.query('update courses_registrations set status = $3 where id = $4', [isAccepted ? 'approved' : 'declined', user_id])856 // if (isAccepted) {857 // await pool.query('insert into student_courses (student_id, course_id) values ($1 ,$2)', [save.rows[0].student_id, save.rows[0].course_id])858 // }859 return res.status(200).json({ message: check.rows })860 } catch (error) {861 res.status(500).json({ message: 'server error' })862 863 }864}865 866 867exports.getAvailableCourses = async (req, res) => {868 try {869 const user_id = req.user.id870 871 const result = await pool.query(872 `SELECT c.*, cr.status from courses c left join courses_registrations cr on c.id = cr.course_id and cr.student_id = $1 where c.id not in (select course_id from student_courses where student_id = $1);`, [user_id]);873 return res.status(200).json({ message: result.rows })874 } catch (error) {875 console.log(error)876 return res.status(500).json({ message: 'server error' });877 878 }879}880 881exports.setAttendance = async (req, res) => {882 try {883 const { session_id, joined_at, left_at } = req.body884 if (req.user.role === 'teacher') {885 return res.status(200).json({ message: 'no need' })886 }887 const user_id = req.user.id888 const check = await pool.query('select * from session_tokens where user_id = $1 and session_id = $2', [user_id, session_id])889 890 if (check.rowCount < 0) {891 return res.status(401).json({ message: 'did not attend the session' })892 }893 await pool.query('insert into attendance (session_id, student_id, joined_at, left_at) values ($1,$2, $3, $4)', [session_id, user_id, joined_at, left_at])894 res.status(200).json({ message: 'saved' })895 } catch (error) {896 return res.status(500).json({ message: 'server error' });897 898 }899}900 901 902 903exports.getProfile = async (req, res) => {904 try {905 const userId = req.user.id;906 907 const result = await pool.query(908 `SELECT 909 id, 910 email, 911 first_name, 912 last_name, 913 profile_image914 FROM users 915 WHERE id = $1`,916 [userId]917 );918 919 if (result.rowCount === 0) {920 return res.status(404).json({ message: 'User not found' });921 }922 923 res.status(200).json({ message: result.rows[0] });924 } catch (error) {925 console.error('Error fetching profile:', error);926 res.status(500).json({ message: 'Server error' });927 }928};929 930exports.updateProfile = async (req, res) => {931 try {932 const userId = req.user.id;933 const { email, first_name, last_name, phone_number } = req.body;934 935 936 937 if (email) {938 const emailCheck = await pool.query(939 'SELECT id FROM users WHERE email = $1 AND id != $2',940 [email, userId]941 );942 if (emailCheck.rowCount > 0) {943 return res.status(400).json({ message: 'Email already registered' });944 }945 }946 947 const result = await pool.query(948 `UPDATE users 949 SET 950 email = COALESCE($1, email),951 first_name = COALESCE($2, first_name),952 last_name = COALESCE($3, last_name)953 WHERE id = $4954 RETURNING id, email, first_name, last_name`,955 [email, first_name, last_name, userId]956 );957 958 const user = result.rows[0];959 960 const seid = {961 user_id: user.id,962 firstName: user.first_name,963 lastName: user.last_name,964 profile_image: user.profile_image,965 role: user.role966 967 }968 969 970 res.clearCookie('details')971 res.cookie('details', JSON.stringify(seid), {972 httpOnly: false,973 secure: false,974 sameSite: 'strict',975 path: '/',976 maxAge: undefined977 });978 979 return res.status(200).json({980 message: 'Profile updated successfully',981 user: result.rows[0]982 });983 } catch (error) {984 console.error('Error updating profile:', error);985 res.status(500).json({ message: 'Server error' });986 }987};988 989exports.changePassword = async (req, res) => {990 try {991 const userId = req.user.id;992 const { current_password, new_password, confirm_password } = req.body;993 994 if (!current_password || !new_password || !confirm_password) {995 return res.status(400).json({ message: 'All password fields are required' });996 }997 998 if (new_password !== confirm_password) {999 return res.status(400).json({ message: 'New passwords do not match' });1000 }1001 1002 if (new_password.length < 6) {1003 return res.status(400).json({ message: 'Password must be at least 6 characters long' });1004 }1005 1006 const userResult = await pool.query(1007 'SELECT password_hash FROM users WHERE id = $1',1008 [userId]1009 );1010 1011 if (userResult.rowCount === 0) {1012 return res.status(404).json({ message: 'User not found' });1013 }1014 1015 const currentHashedPassword = userResult.rows[0].password_hash;1016 1017 const isPasswordValid = await bcrypt.compare(current_password, currentHashedPassword);1018 if (!isPasswordValid) {1019 return res.status(400).json({ message: 'Current password is incorrect' });1020 }1021 1022 const saltRounds = 10;1023 const newHashedPassword = await bcrypt.hash(new_password, saltRounds);1024 1025 await pool.query(1026 'UPDATE users SET password_hash = $1 WHERE id = $2',1027 [newHashedPassword, userId]1028 );1029 1030 res.status(200).json({ message: 'Password changed successfully' });1031 } catch (error) {1032 console.error('Error changing password:', error);1033 res.status(500).json({ message: 'Server error' });1034 }1035};1036 1037exports.uploadProfileImage = async (req, res) => {1038 try {1039 const userId = req.user.id;1040 1041 if (!req.file) {1042 return res.status(400).json({ message: 'No image uploaded' });1043 }1044 1045 const imageUrl = `/uploads/${req.file.filename}`;1046 1047 const update = await pool.query(1048 'UPDATE users SET profile_image = $1 WHERE id = $2 returning *',1049 [imageUrl, userId]1050 );1051 1052 const user = update.rows[0];1053 1054 const seid = {1055 user_id: user.id,1056 firstName: user.first_name,1057 lastName: user.last_name,1058 profile_image: user.profile_image,1059 role: user.role1060 1061 }1062 1063 1064 res.clearCookie('details')1065 res.cookie('details', JSON.stringify(seid), {1066 httpOnly: false,1067 secure: false,1068 sameSite: 'strict',1069 path: '/',1070 maxAge: undefined1071 });1072 return res.status(200).json({1073 message: 'Profile image updated successfully',1074 image_url: imageUrl1075 });1076 } catch (error) {1077 console.error('Error uploading profile image:', error);1078 res.status(500).json({ message: 'Server error' });1079 }1080};1081 1082 1083exports.streamImage = async (req, res) => {1084 try {1085 const { filename } = req.params;1086 1087 if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {1088 return res.status(400).json({ message: 'Invalid filename' });1089 }1090 1091 let imagePath;1092 1093 const profilePath = path.join(UPLOADS_DIR, filename);1094 if (fs.existsSync(profilePath)) {1095 imagePath = profilePath;1096 } else {1097 1098 return res.status(404).json({ message: 'Image not found' });1099 }1100 1101 const stats = fs.statSync(imagePath);1102 const fileSize = stats.size;1103 const ext = path.extname(filename).toLowerCase();1104 1105 const mimeTypes = {1106 '.jpg': 'image/jpeg',1107 '.jpeg': 'image/jpeg',1108 '.png': 'image/png',1109 '.gif': 'image/gif',1110 '.webp': 'image/webp',1111 '.svg': 'image/svg+xml'1112 };1113 1114 const contentType = mimeTypes[ext] || 'application/octet-stream';1115 1116 res.setHeader('Content-Type', contentType);1117 res.setHeader('Content-Length', fileSize);1118 res.setHeader('Cache-Control', 'public, max-age=86400');1119 1120 const stream = fs.createReadStream(imagePath);1121 stream.pipe(res);1122 1123 // Handle stream errors1124 stream.on('error', (err) => {1125 console.error('Error streaming image:', err);1126 if (!res.headersSent) {1127 res.status(500).json({ message: 'Error streaming image' });1128 }1129 });1130 1131 } catch (error) {1132 console.error('Error in streamImage:', error);1133 if (!res.headersSent) {1134 res.status(500).json({ message: 'Server error' });1135 }1136 }1137}1138 1139exports.searchStudents = async (req, res) => {1140 try {1141 const { search, course_id } = req.body;1142 console.log(req.body)1143 1144 if (!search || search.trim().length === 0) {1145 return res.status(200).json({ message: [] });1146 }1147 1148 const searchTerm = `%${search}%`;1149 1150 let query = `1151 SELECT 1152 u.id as student_id,1153 u.email,1154 u.first_name,1155 u.last_name,1156 u.role1157 FROM users u1158 WHERE u.role = 'student'1159 AND (1160 u.id::TEXT ILIKE $1 OR 1161 u.email ILIKE $1 OR1162 CONCAT(u.first_name, ' ', u.last_name) ILIKE $11163 )1164 AND u.id NOT IN (1165 SELECT student_id 1166 FROM student_courses 1167 WHERE course_id = $21168 )1169 ORDER BY u.id1170 LIMIT 101171 `;1172 1173 const result = await pool.query(query, [searchTerm, course_id || 0]);1174 1175 res.status(200).json({ message: result.rows });1176 } catch (error) {1177 console.error('Error searching students:', error);1178 res.status(500).json({ message: 'Server error' });1179 }1180};1181 1182 1183 1184exports.requestPasswordReset = async (req, res) => {1185 try {1186 const { email } = req.body;1187 1188 const existing = await pool.query(1189 "SELECT * FROM users WHERE email = $1",1190 [email]1191 );1192 1193 if (existing.rows.length === 0) {1194 return res.status(200).json({1195 message: 'If an account exists with this email, a reset code has been sent.'1196 });1197 }1198 1199 const resetCode = Math.floor(100000 + Math.random() * 900000);1200 