From 69b09f290f9d123e0011f76c15a4915048b7fcee Mon Sep 17 00:00:00 2001 From: lakshan sanjeewa Date: Sat, 28 Sep 2024 17:04:28 +0530 Subject: [PATCH 01/48] merge the edits --- backend/controllers/DLDeliveryController.js | 136 ++++++ backend/controllers/DLDriverController.js | 352 ++++++++++++++ backend/controllers/DLDriverFormController.js | 132 ++++++ backend/controllers/DLEmailController.js | 46 ++ backend/controllers/DLOcontroller.js | 78 ++++ backend/controllers/DLuploadController.js | 19 + backend/middlewares/DLMulter.js | 26 ++ backend/middlewares/DLauthMiddleware.js | 28 ++ backend/models/DLDeliveryFormModel.js | 79 ++++ backend/models/DLDeliveryModel.js | 50 ++ backend/models/DLDriverModel.js | 91 ++++ backend/models/DLImageModel.js | 21 + backend/models/DLOModel.js | 72 +++ backend/routes/DLDeliveryRoute.js | 14 + backend/routes/DLDriverRoutes.js | 67 +++ backend/routes/DLEmailRoutes.js | 10 + backend/routes/DLFormRoutes.js | 40 ++ backend/routes/DLORoutes.js | 18 + backend/routes/DLimageHandlerRoute.js | 10 + backend/server.js | 36 ++ backend/uploads/1727053967402-id1.jpeg | Bin 0 -> 10183 bytes backend/uploads/1727053967402-images (1).jpeg | Bin 0 -> 9385 bytes .../uploads/1727053967402-li1 - Copy (2).jpeg | Bin 0 -> 8111 bytes backend/uploads/1727054250696-id2.jpeg | Bin 0 -> 10434 bytes backend/uploads/1727054250696-images (2).jpeg | Bin 0 -> 6034 bytes .../uploads/1727054250696-li1 - Copy (3).jpeg | Bin 0 -> 8111 bytes backend/uploads/1727055520267-li1.jpeg | Bin 0 -> 8111 bytes backend/uploads/1727060006946-id1 - Copy.jpeg | Bin 0 -> 10183 bytes backend/uploads/1727060006947-id1.jpeg | Bin 0 -> 10183 bytes backend/uploads/1727060006947-images.jpeg | Bin 0 -> 4403 bytes backend/uploads/1727060871491-download.jpeg | Bin 0 -> 4735 bytes backend/uploads/1727060871491-id1 - Copy.jpeg | Bin 0 -> 10183 bytes .../uploads/1727060871491-li1 - Copy (3).jpeg | Bin 0 -> 8111 bytes backend/uploads/1727061115161-id1 - Copy.jpeg | Bin 0 -> 10183 bytes backend/uploads/1727061115162-images (2).jpeg | Bin 0 -> 6034 bytes .../uploads/1727061115162-li1 - Copy (3).jpeg | Bin 0 -> 8111 bytes backend/uploads/1727062096026-id1 - Copy.jpeg | Bin 0 -> 10183 bytes backend/uploads/1727062096026-images (2).jpeg | Bin 0 -> 6034 bytes .../uploads/1727062096026-li1 - Copy (2).jpeg | Bin 0 -> 8111 bytes backend/uploads/1727519172982-images (1).jpeg | Bin 0 -> 9385 bytes backend/uploads/1727519172982-li1 - Copy.jpeg | Bin 0 -> 8111 bytes backend/uploads/1727519172984-id2.jpeg | Bin 0 -> 10434 bytes backend/utils/DLMulter.js | 35 ++ backend/utils/DLSendEmail.js | 31 ++ backend/utils/dlgenerateToken.js | 9 + frontend/src/App.jsx | 84 ++++ .../Components/delivery/DLmanageSidebar.jsx | 77 ++++ .../Components/delivery/DeliverySidebar.jsx | 129 ++++++ frontend/src/Hooks/Delivery/Dlauth.jsx | 57 +++ frontend/src/Pages/delivery/DLALLdrivers.jsx | 188 ++++++++ .../src/Pages/delivery/DLApproveDriver.jsx | 102 ++++ .../src/Pages/delivery/DLDriverAccept.jsx | 272 +++++++++++ .../src/Pages/delivery/DLDriverDashboard.jsx | 128 ++++++ .../src/Pages/delivery/DLDriverProfile.jsx | 278 +++++++++++ .../delivery/DLDriverRegistrationForm.jsx | 342 ++++++++++++++ frontend/src/Pages/delivery/DLImageUpload.jsx | 76 +++ frontend/src/Pages/delivery/DLLogin.jsx | 69 +++ frontend/src/Pages/delivery/DLMap.jsx | 0 frontend/src/Pages/delivery/DLOtable.jsx | 75 +++ frontend/src/Pages/delivery/DLSendEmail.jsx | 82 ++++ frontend/src/Pages/delivery/DLViewDriver.jsx | 319 +++++++++++++ frontend/src/Pages/delivery/DLeditdriver.jsx | 434 ++++++++++++++++++ frontend/src/Pages/delivery/DLlogout.jsx | 60 +++ frontend/src/Pages/delivery/DLmangeDash.jsx | 145 ++++++ .../src/Pages/delivery/DLongoingdelivery.jsx | 0 frontend/src/Pages/delivery/DLoooo.jsx | 192 ++++++++ .../src/Pages/delivery/DLviewDeliveries.jsx | 252 ++++++++++ .../src/Pages/delivery/DLviewDelivery.jsx | 98 ++++ frontend/src/Pages/delivery/Dleditprofile.jsx | 209 +++++++++ .../Pages/delivery/Dlpreviousdeliveries.jsx | 0 .../src/Pages/delivery/or/orderdelete.jsx | 78 ++++ 71 files changed, 5146 insertions(+) create mode 100644 backend/controllers/DLDeliveryController.js create mode 100644 backend/controllers/DLDriverController.js create mode 100644 backend/controllers/DLDriverFormController.js create mode 100644 backend/controllers/DLEmailController.js create mode 100644 backend/controllers/DLOcontroller.js create mode 100644 backend/controllers/DLuploadController.js create mode 100644 backend/middlewares/DLMulter.js create mode 100644 backend/middlewares/DLauthMiddleware.js create mode 100644 backend/models/DLDeliveryFormModel.js create mode 100644 backend/models/DLDeliveryModel.js create mode 100644 backend/models/DLDriverModel.js create mode 100644 backend/models/DLImageModel.js create mode 100644 backend/models/DLOModel.js create mode 100644 backend/routes/DLDeliveryRoute.js create mode 100644 backend/routes/DLDriverRoutes.js create mode 100644 backend/routes/DLEmailRoutes.js create mode 100644 backend/routes/DLFormRoutes.js create mode 100644 backend/routes/DLORoutes.js create mode 100644 backend/routes/DLimageHandlerRoute.js create mode 100644 backend/uploads/1727053967402-id1.jpeg create mode 100644 backend/uploads/1727053967402-images (1).jpeg create mode 100644 backend/uploads/1727053967402-li1 - Copy (2).jpeg create mode 100644 backend/uploads/1727054250696-id2.jpeg create mode 100644 backend/uploads/1727054250696-images (2).jpeg create mode 100644 backend/uploads/1727054250696-li1 - Copy (3).jpeg create mode 100644 backend/uploads/1727055520267-li1.jpeg create mode 100644 backend/uploads/1727060006946-id1 - Copy.jpeg create mode 100644 backend/uploads/1727060006947-id1.jpeg create mode 100644 backend/uploads/1727060006947-images.jpeg create mode 100644 backend/uploads/1727060871491-download.jpeg create mode 100644 backend/uploads/1727060871491-id1 - Copy.jpeg create mode 100644 backend/uploads/1727060871491-li1 - Copy (3).jpeg create mode 100644 backend/uploads/1727061115161-id1 - Copy.jpeg create mode 100644 backend/uploads/1727061115162-images (2).jpeg create mode 100644 backend/uploads/1727061115162-li1 - Copy (3).jpeg create mode 100644 backend/uploads/1727062096026-id1 - Copy.jpeg create mode 100644 backend/uploads/1727062096026-images (2).jpeg create mode 100644 backend/uploads/1727062096026-li1 - Copy (2).jpeg create mode 100644 backend/uploads/1727519172982-images (1).jpeg create mode 100644 backend/uploads/1727519172982-li1 - Copy.jpeg create mode 100644 backend/uploads/1727519172984-id2.jpeg create mode 100644 backend/utils/DLMulter.js create mode 100644 backend/utils/DLSendEmail.js create mode 100644 backend/utils/dlgenerateToken.js create mode 100644 frontend/src/Components/delivery/DLmanageSidebar.jsx create mode 100644 frontend/src/Components/delivery/DeliverySidebar.jsx create mode 100644 frontend/src/Hooks/Delivery/Dlauth.jsx create mode 100644 frontend/src/Pages/delivery/DLALLdrivers.jsx create mode 100644 frontend/src/Pages/delivery/DLApproveDriver.jsx create mode 100644 frontend/src/Pages/delivery/DLDriverAccept.jsx create mode 100644 frontend/src/Pages/delivery/DLDriverDashboard.jsx create mode 100644 frontend/src/Pages/delivery/DLDriverProfile.jsx create mode 100644 frontend/src/Pages/delivery/DLDriverRegistrationForm.jsx create mode 100644 frontend/src/Pages/delivery/DLImageUpload.jsx create mode 100644 frontend/src/Pages/delivery/DLLogin.jsx create mode 100644 frontend/src/Pages/delivery/DLMap.jsx create mode 100644 frontend/src/Pages/delivery/DLOtable.jsx create mode 100644 frontend/src/Pages/delivery/DLSendEmail.jsx create mode 100644 frontend/src/Pages/delivery/DLViewDriver.jsx create mode 100644 frontend/src/Pages/delivery/DLeditdriver.jsx create mode 100644 frontend/src/Pages/delivery/DLlogout.jsx create mode 100644 frontend/src/Pages/delivery/DLmangeDash.jsx create mode 100644 frontend/src/Pages/delivery/DLongoingdelivery.jsx create mode 100644 frontend/src/Pages/delivery/DLoooo.jsx create mode 100644 frontend/src/Pages/delivery/DLviewDeliveries.jsx create mode 100644 frontend/src/Pages/delivery/DLviewDelivery.jsx create mode 100644 frontend/src/Pages/delivery/Dleditprofile.jsx create mode 100644 frontend/src/Pages/delivery/Dlpreviousdeliveries.jsx create mode 100644 frontend/src/Pages/delivery/or/orderdelete.jsx diff --git a/backend/controllers/DLDeliveryController.js b/backend/controllers/DLDeliveryController.js new file mode 100644 index 00000000..d8b34967 --- /dev/null +++ b/backend/controllers/DLDeliveryController.js @@ -0,0 +1,136 @@ +import DLDriver from '../models/DLDriverModel.js'; +import Order from '../models/DLOModel.js'; +import DLDelivery from '../models/DLDeliveryModel.js'; +import asyncHandler from 'express-async-handler'; + + + +// Function to generate a tracking ID starting with 'TR' followed by 5 digits +const generateTrackingID = () => { + const randomNum = Math.floor(10000 + Math.random() * 90000); + return `TR${randomNum}`; +}; + +// Controller to assign a driver and create delivery record +export const assignDriverToOrder = async () => { + try { + // Find the oldest order + const order = await Order.findOne().sort({ createdAt: 1 }); + if (!order) { + /* console.log('No orders available');*/ + return; + } + + // Find an available driver + const driver = await DLDriver.findOne({ isAvailable: true }).sort({ createdAt: 1 }); + if (!driver) { + /* console.log('No drivers available');*/ + return; + } + + // Generate tracking ID + const trackingID = generateTrackingID(); + + // Create a new delivery entry + const delivery = new DLDelivery({ + trackingID, + orderID: order._id, + oID: order.orderID, + driverID: driver._id, + drID: driver.driverID, + shopName: order.shopName, + pickupAddress: `${order.shopAddress.streetName || ''}, ${order.shopAddress.city || ''}, ${order.shopAddress.district || ''}`.trim().replace(/^,|,$/g, ''), + customerName: order.customerName, + dropOffAddress: `${order.customerAddress.streetAddress || ''}, ${order.customerAddress.city || ''}, ${order.customerAddress.zipCode || ''}, ${order.customerAddress.district || ''}`.trim().replace(/^,|,$/g, ''), + }); + + await delivery.save(); + + // Update the driver's status to unavailable + driver.isAvailable = false; + await driver.save(); + + // Delete the order after assignment + await Order.deleteOne({ _id: order._id }); + + /* console.log(`Assigned driver ${driver.fullName} to order ${order._id} with tracking ID ${trackingID}`); */ + } catch (error) { + console.error('Error assigning driver to order:', error); + } +}; + +// Function to constantly check for available drivers and assign them to oldest orders +export const checkForAvailableDrivers = async () => { + try { + // Continuously check every few seconds + setInterval(async () => { + await assignDriverToOrder(); + }, 5000); // Check every 5 seconds + } catch (error) { + console.error('Error in checking for available drivers:', error); + } +}; + + + +// Controller to get all deliveries with search and pagination +export const getAllDeliveries = async (req, res) => { + try { + // Search filters + const { search = '', page = 1, limit = 20 } = req.query; + + // Regular expression for search + const searchRegex = new RegExp(search, 'i'); + + // Fetch deliveries with filters applied + const deliveries = await DLDelivery.find({ + $or: [ + { trackingID: searchRegex }, + { oID: searchRegex }, + { drID: searchRegex }, + { shopName: searchRegex }, + { customerName: searchRegex }, + { pickupAddress: searchRegex }, + { dropOffAddress: searchRegex } + ], + }) + .skip((page - 1) * limit) + .limit(parseInt(limit)); + + // Total number of deliveries for pagination + const total = await DLDelivery.countDocuments({ + $or: [ + { trackingID: searchRegex }, + { oID: searchRegex }, + { drID: searchRegex }, + { shopName: searchRegex }, + { customerName: searchRegex }, + { pickupAddress: searchRegex }, + { dropOffAddress: searchRegex } + ], + }); + + res.status(200).json({ + deliveries, + total, + page: parseInt(page), + pages: Math.ceil(total / limit), + }); + } catch (error) { + res.status(500).json({ message: 'Error fetching deliveries', error }); + } +}; + +// @desc Get a single delivery by ID +// @route GET /api/delivery/:id +// @access Public +export const getDeliveryById = asyncHandler(async (req, res) => { + const delivery = await DLDelivery.findById(req.params.id); + + if (delivery) { + res.json(delivery); + } else { + res.status(404); + throw new Error('Delivery not found'); + } +}); \ No newline at end of file diff --git a/backend/controllers/DLDriverController.js b/backend/controllers/DLDriverController.js new file mode 100644 index 00000000..d6ce0feb --- /dev/null +++ b/backend/controllers/DLDriverController.js @@ -0,0 +1,352 @@ +import bcrypt from 'bcryptjs' +import asyncHandler from 'express-async-handler' +import DLDriver from '../models/DLDriverModel.js' +import DLDeliveryForm from '../models/DLDeliveryFormModel.js' +import { generateToken } from '../utils/dlgenerateToken.js' + +// Function to generate a unique driverID starting with "D" and followed by a 6-digit number +const generateDriverID = async () => { + let isUnique = false; + let driverID; + + while (!isUnique) { + // Generate a random 6-digit number + const randomID = Math.floor(100000 + Math.random() * 900000); // Ensures a 6-digit number + driverID = `D${randomID}`; // Prefix the number with "D" + + // Check if the generated driverID already exists in the database + const existingDriver = await DLDriver.findOne({ driverID }); + + // If no existing driver is found, the driverID is unique + if (!existingDriver) { + isUnique = true; + } + } + + return driverID; +}; + + +const addDriver = asyncHandler(async (req, res) => { + const deliveryForm = await DLDeliveryForm.findById(req.params.id) + + if (deliveryForm) { + const hashedPassword = await bcrypt.hash(deliveryForm.idCardNumber, 10) + + // Generate the unique driverID + const driverID = await generateDriverID(); + + const driver = new DLDriver({ + driverID, // Add the unique driverID + firstName: deliveryForm.firstName, + lastName: deliveryForm.lastName, + fullName: deliveryForm.fullName, + email: deliveryForm.email, + phone: deliveryForm.phone, + dateOfBirth: deliveryForm.dateOfBirth, + idCardNumber: deliveryForm.idCardNumber, + licenseCardNumber: deliveryForm.licenseCardNumber, + address: deliveryForm.address, + vehicleNumber: deliveryForm.vehicleNumber, + vehicleType: deliveryForm.vehicleType, + password: hashedPassword, // Store the hashed ID card number as the password + idCardImageUrl: deliveryForm.idCardImageUrl, // Store the ID card image URL + licenseImageUrl: deliveryForm.licenseImageUrl, // Store the license image URL + personalImageUrl: deliveryForm.personalImageUrl, // Store the personal image URL + }) + + await driver.save() + + res.status(201).json({ message: 'Driver approved and added to the system', driverID }); + + } else { + res.status(404).json({ message: 'Delivery form not found' }) + } +}) + +// Example file: controllers/driverController.js + +// Define the function + +// Get driver by ID +const getDriverById = asyncHandler(async (req, res) => { + const driver = await DLDriver.findById(req.params.id) + + if (driver) { + res.json(driver) + } else { + res.status(404).json({ message: 'Driver not found' }) + } +}) + +// Update driver by ID +const updateDriverById = asyncHandler(async (req, res) => { + const driver = await DLDriver.findById(req.params.id) + + if (driver) { + driver.firstName = req.body.firstName || driver.firstName + driver.lastName = req.body.lastName || driver.lastName + driver.fullName = req.body.fullName || driver.fullName + driver.email = req.body.email || driver.email + driver.phone = req.body.phone || driver.phone + driver.dateOfBirth = req.body.dateOfBirth || driver.dateOfBirth + driver.idCardNumber = req.body.idCardNumber || driver.idCardNumber + driver.licenseCardNumber = + req.body.licenseCardNumber || driver.licenseCardNumber + driver.address = req.body.address || driver.address + driver.vehicleNumber = req.body.vehicleNumber || driver.vehicleNumber + driver.vehicleType = req.body.vehicleType || driver.vehicleType + driver.isAvailable = + req.body.isAvailable !== undefined + ? req.body.isAvailable + : driver.isAvailable + driver.idCardImageUrl = req.body.idCardImageUrl || driver.idCardImageUrl + driver.licenseImageUrl = + req.body.licenseImageUrl || driver.licenseImageUrl + driver.personalImageUrl = + req.body.personalImageUrl || driver.personalImageUrl + + // If a new password is provided, hash it before saving + if (req.body.password) { + driver.password = await bcrypt.hash(req.body.password, 10) + } + + const updatedDriver = await driver.save() + + res.json({ + message: 'Driver updated successfully', + driver: updatedDriver, + }) + } else { + res.status(404).json({ message: 'Driver not found' }) + } +}) + +// Delete driver by ID +const deleteDriverById = asyncHandler(async (req, res) => { + const driver = await DLDriver.findById(req.params.id) + + if (driver) { + await driver.deleteOne() // Use deleteOne instead of remove + res.json({ message: 'Driver deleted successfully' }) + } else { + res.status(404).json({ message: 'Driver not found' }) + } +}) + + +// getting all the drivers + +const getAllDrivers = asyncHandler(async (req, res) => { + const drivers = await DLDriver.find({}); // Find all drivers + res.json(drivers); +}); + + + + + + + +// Driver Login +// Driver Login +const loginDriver = asyncHandler(async (req, res) => { + const { email, password } = req.body + + // Find driver by email + const driver = await DLDriver.findOne({ email }) + + if (driver && (await bcrypt.compare(password, driver.password))) { + // Generate JWT token and return it + const token = generateToken(driver._id) + res.status(200).json({ + _id: driver._id, + fullName: driver.fullName, + email: driver.email, + vehicleType: driver.vehicleType, + isAvailable: driver.isAvailable, + token, // Send the token in the response + }) + } else { + res.status(401).json({ message: 'Invalid email or password' }) + } +}) + +// Get Driver Profile +const getDriverProfile = asyncHandler(async (req, res) => { + const driver = await DLDriver.findById(req.driver._id).select('-password') // Exclude password + + if (driver) { + + res.json({ + _id: driver._id, + driverID: driver.driverID, + firstName: driver.firstName, + lastName: driver.lastName, + fullName: driver.fullName, // Ensure fullName is returned + email: driver.email, + phone: driver.phone, + dateOfBirth: driver.dateOfBirth, + address: driver.address, + vehicleNumber: driver.vehicleNumber, + vehicleType: driver.vehicleType, + idCardNumber: driver.idCardNumber, // Ensure idCardNumber is returned + licenseCardNumber: driver.licenseCardNumber, // Ensure licenseCardNumber is returned + idCardImageUrl: driver.idCardImageUrl, + licenseImageUrl: driver.licenseImageUrl, + personalImageUrl: driver.personalImageUrl, + isAvailable: driver.isAvailable, + }); + } else { + res.status(404); + throw new Error('Driver not found'); + + } +}) + +// Toggle driver's availability + +const updateDriverAvailability = asyncHandler(async (req, res) => { + const driver = await DLDriver.findById(req.params.id); + + if (driver) { + driver.isAvailable = req.body.isAvailable; + await driver.save(); + res.json({ message: 'Driver availability updated', isAvailable: driver.isAvailable }); + } else { + res.status(404).json({ message: 'Driver not found' }); + } +}); + +const updateDriverProfile = asyncHandler(async (req, res) => { + const driver = await DLDriver.findById(req.driver._id); + + if (driver) { + driver.firstName = req.body.firstName || driver.firstName; + driver.lastName = req.body.lastName || driver.lastName; + driver.email = req.body.email || driver.email; + driver.phone = req.body.phone || driver.phone; + driver.dateOfBirth = req.body.dateOfBirth || driver.dateOfBirth; + driver.address = req.body.address || driver.address; + driver.vehicleNumber = req.body.vehicleNumber || driver.vehicleNumber; + driver.vehicleType = req.body.vehicleType || driver.vehicleType; + driver.idCardImageUrl = req.body.idCardImageUrl || driver.idCardImageUrl; + driver.licenseImageUrl = req.body.licenseImageUrl || driver.licenseImageUrl; + driver.personalImageUrl = req.body.personalImageUrl || driver.personalImageUrl; + + const updatedDriver = await driver.save(); + + res.json({ + _id: updatedDriver._id, + driverID: updatedDriver.driverID, + firstName: updatedDriver.firstName, + lastName: updatedDriver.lastName, + email: updatedDriver.email, + phone: updatedDriver.phone, + dateOfBirth: updatedDriver.dateOfBirth, + address: updatedDriver.address, + vehicleNumber: updatedDriver.vehicleNumber, + vehicleType: updatedDriver.vehicleType, + idCardImageUrl: updatedDriver.idCardImageUrl, + licenseImageUrl: updatedDriver.licenseImageUrl, + personalImageUrl: updatedDriver.personalImageUrl, + }); + } else { + res.status(404); + throw new Error('Driver not found'); + + } +}) + +// Logout driver +const logoutDriver = (req, res) => { + res.cookie('jwt', '', { + httpOnly: true, + expires: new Date(0), + + }); + + res.status(200).json({ message: 'Logged out successfully' }); +}; + + +// Update driver password +const updateDriverPassword = asyncHandler(async (req, res) => { + const driver = await DLDriver.findById(req.driver._id); + + if (driver) { + const { currentPassword, newPassword, confirmPassword } = req.body; + + // Check if current password matches + const isMatch = await bcrypt.compare(currentPassword, driver.password); + if (!isMatch) { + res.status(401); + throw new Error('Current password is incorrect'); + } + + // Check if new and confirm password match + if (newPassword !== confirmPassword) { + res.status(400); + throw new Error('New passwords do not match'); + } + + // Hash new password + driver.password = await bcrypt.hash(newPassword, 10); + await driver.save(); + + res.json({ message: 'Password updated successfully' }); + } else { + res.status(404); + throw new Error('Driver not found'); + } +}); + + +const deleteDriverAccount = asyncHandler(async (req, res) => { + const driver = await DLDriver.findById(req.driver._id); + + if (driver) { + await driver.deleteOne(); // Use deleteOne to delete the driver + res.json({ message: 'Driver account deleted successfully' }); + } else { + res.status(404); + throw new Error('Driver not found'); + } +}); + +//verify the password +const verifyPassword = async (req, res) => { + try { + const driver = await DLDriver.findById(req.driver._id); // Assuming driver is authenticated + + if (!driver) { + res.status(404).json({ message: 'Driver not found' }); + return; + } + + const isMatch = await bcrypt.compare(req.body.password, driver.password); // Compare the password + + if (isMatch) { + res.json({ isValid: true }); // Password is valid + } else { + res.status(400).json({ message: 'Invalid password' }); // Password does not match + } + } catch (error) { + res.status(500).json({ message: 'Server error' }); + } +}; + +export { addDriver, + deleteDriverAccount, + updateDriverPassword, + updateDriverAvailability, + getDriverById, + updateDriverById, + deleteDriverById , + loginDriver, + getDriverProfile, + logoutDriver , + updateDriverProfile, + verifyPassword, + getAllDrivers}; + diff --git a/backend/controllers/DLDriverFormController.js b/backend/controllers/DLDriverFormController.js new file mode 100644 index 00000000..5d02d16d --- /dev/null +++ b/backend/controllers/DLDriverFormController.js @@ -0,0 +1,132 @@ +import asyncHandler from 'express-async-handler' +import DLDeliveryForm from '../models/DLDeliveryFormModel.js' + +// Function to submit a delivery form +const submitDLDeliveryForm = asyncHandler(async (req, res) => { + const { + firstName, + lastName, + fullName, + email, + phone, + dateOfBirth, + idCardNumber, + licenseCardNumber, + address, + vehicleNumber, + vehicleType, + } = req.body + + // Check if a form with the same email already exists + const existingForm = await DLDeliveryForm.findOne({ email }) + + if (existingForm) { + res.status(400).json({ + message: 'A form with this email already exists', + }) + return + } + + // Save the images + const idCardImageUrl = req.files['idCardImage'][0].path + const licenseImageUrl = req.files['licenseImage'][0].path + const personalImageUrl = req.files['personalImage'][0].path + + // Create and save the delivery form + const deliveryForm = await DLDeliveryForm.create({ + firstName, + lastName, + fullName, + email, + phone, + dateOfBirth, + idCardNumber, + licenseCardNumber, + address, + vehicleNumber, + vehicleType, + idCardImageUrl, + licenseImageUrl, + personalImageUrl, + }) + + res.status(201).json({ + message: 'Form submitted successfully', + deliveryForm, + }) +}) + +// Function to get all pending forms +const getPendingForms = asyncHandler(async (req, res) => { + const pendingForms = await DLDeliveryForm.find({ status: 'Pending' }) + + if (pendingForms.length > 0) { + res.json(pendingForms) + } else { + res.status(404).json({ message: 'No pending forms found' }) + } +}) + +// Function to get a specific delivery form by ID +const getDeliveryFormById = asyncHandler(async (req, res) => { + const deliveryForm = await DLDeliveryForm.findById(req.params.id) + + if (deliveryForm) { + res.json(deliveryForm) + } else { + res.status(404).json({ message: 'Delivery form not found' }) + } +}) + +// Function to update the status of a delivery form (approve or reject) +const updateDeliveryFormStatus = asyncHandler(async (req, res) => { + const deliveryForm = await DLDeliveryForm.findById(req.params.id) + + if (deliveryForm) { + deliveryForm.status = req.body.status // 'Approved' or 'Rejected' + await deliveryForm.save() + res.json({ message: `Form status updated to ${req.body.status}` }) + } else { + res.status(404).json({ message: 'Delivery form not found' }) + } +}) + +// Function to update a delivery form by ID +const updateDeliveryForm = asyncHandler(async (req, res) => { + const deliveryForm = await DLDeliveryForm.findById(req.params.id) + + if (deliveryForm) { + const updatedForm = await DLDeliveryForm.findByIdAndUpdate( + req.params.id, + req.body, + { new: true } + ) + res.json({ + message: 'Form updated successfully', + updatedForm, + }) + } else { + res.status(404).json({ message: 'Delivery form not found' }) + } +}) + +// Function to delete a delivery form by ID +const deleteDeliveryForm = asyncHandler(async (req, res) => { + const deliveryForm = await DLDeliveryForm.findById(req.params.id) + + if (deliveryForm) { + await deliveryForm.remove() + res.json({ message: 'Delivery form deleted successfully' }) + } else { + res.status(404).json({ message: 'Delivery form not found' }) + } +}) + +export { + submitDLDeliveryForm, + getPendingForms, + getDeliveryFormById, + updateDeliveryFormStatus, + updateDeliveryForm, + deleteDeliveryForm, +} diff --git a/backend/controllers/DLEmailController.js b/backend/controllers/DLEmailController.js new file mode 100644 index 00000000..f103834c --- /dev/null +++ b/backend/controllers/DLEmailController.js @@ -0,0 +1,46 @@ +// controllers/DLEmailController.js +import 'dotenv/config' +import { log } from 'node:console' +import { createTransport } from 'nodemailer' + +import nodemailer from 'nodemailer' +import asyncHandler from 'express-async-handler' +import DLDeliveryForm from '../models/DLDeliveryFormModel.js' + +export const sendApprovalEmail = asyncHandler(async (req, res) => { + const driverId = req.params.id + + // Fetch the driver's details from the database + const driver = await DLDeliveryForm.findById(driverId) + + if (!driver) { + res.status(404) + throw new Error('Driver not found') + } + + // Set up the transporter using environment variables + const transporter = nodemailer.createTransport({ + service: process.env.EMAIL_SERVICE, + auth: { + user: process.env.EMAIL_USER, + pass: process.env.EMAIL_PASS, + }, + }) + + // Email content + const mailOptions = { + from: process.env.EMAIL_USER, + to: driver.email, + subject: 'Approval Confirmation', + text: `Dear ${driver.fullName},\n\nCongratulations! Your driver registration has been approved.Enter Your ID to login in first time . \n\nRegards,\nFarmCart Team`, + } + + // Send the email + try { + await transporter.sendMail(mailOptions) + res.status(200).json({ message: 'Email sent successfully' }) + } catch (error) { + res.status(500) + throw new Error('Failed to send email') + } +}) diff --git a/backend/controllers/DLOcontroller.js b/backend/controllers/DLOcontroller.js new file mode 100644 index 00000000..8c5a11be --- /dev/null +++ b/backend/controllers/DLOcontroller.js @@ -0,0 +1,78 @@ +import asyncHandler from 'express-async-handler'; +import Order from '../models/DLOModel.js'; + +// @desc Create a new order +// @route POST /api/orders +// @access Public +const addOrder = asyncHandler(async (req, res) => { + const { orderID, customerName, customerAddress, shopName, shopAddress, orderStatus, deliveryDate, deliverId, deliverName } = req.body; + + const order = new Order({ + orderID, + customerName, + customerAddress, + shopName, + shopAddress, + orderStatus, + deliveryDate, + deliverId, + deliverName, + }); + + const createdOrder = await order.save(); + res.status(201).json(createdOrder); +}); + + + +// @desc Fetch all orders +// @route GET /api/orders +// @access Public +const getOrders = asyncHandler(async (req, res) => { + const orders = await Order.find({}); + res.json(orders); +}); + +// @desc Update order status +// @route PUT /api/orders/:id/status +// @access Public +const updateOrderStatus = asyncHandler(async (req, res) => { + const order = await Order.findById(req.params.id); + + if (order) { + const allowedStatus = ['Pending', 'Ready', 'Picked Up', 'On The Way', 'Delivered']; + const currentStatusIndex = allowedStatus.indexOf(order.orderStatus); + + // Ensure the status progresses in order and cannot be reversed + if (currentStatusIndex < allowedStatus.length - 1) { + order.orderStatus = allowedStatus[currentStatusIndex + 1]; + } else { + return res.status(400).json({ message: 'Order is already delivered' }); + } + + const updatedOrder = await order.save(); + res.json(updatedOrder); + } else { + res.status(404).json({ message: 'Order not found' }); + } +}); + + + +const deleteOrder = asyncHandler(async (req, res) => { + const order = await Order.findById(req.params.id); + + if (order) { + await order.deleteOne(); // Use deleteOne instead of remove + res.json({ message: 'Order deleted successfully' }); + } else { + res.status(404).json({ message: 'Order not found' }); + } +}); + + +export { deleteOrder }; + +export { getOrders, updateOrderStatus }; + +export { addOrder }; diff --git a/backend/controllers/DLuploadController.js b/backend/controllers/DLuploadController.js new file mode 100644 index 00000000..303ac794 --- /dev/null +++ b/backend/controllers/DLuploadController.js @@ -0,0 +1,19 @@ +// controllers/DLuploadController.js +import DLImage from '../models/DLImageModel.js' +import asyncHandler from 'express-async-handler' + +export const uploadImage = asyncHandler(async (req, res) => { + const { filename, path } = req.file + + const newDLImage = new DLImage({ + filename, + path, + }) + + await newDLImage.save() + + res.status(201).json({ + message: 'Image uploaded successfully', + image: newDLImage, + }) +}) diff --git a/backend/middlewares/DLMulter.js b/backend/middlewares/DLMulter.js new file mode 100644 index 00000000..51d75a99 --- /dev/null +++ b/backend/middlewares/DLMulter.js @@ -0,0 +1,26 @@ +import multer from 'multer' +import path from 'path' + +const storage = multer.diskStorage({ + destination: function (req, file, cb) { + cb(null, 'uploads/') // Specify the folder where images will be stored + }, + filename: function (req, file, cb) { + cb(null, `${Date.now()}-${file.originalname}`) // Create a unique filename + }, +}) + +const fileFilter = (req, file, cb) => { + if (file.mimetype.startsWith('image/')) { + cb(null, true) + } else { + cb('Please upload only images.', false) + } +} + +const upload = multer({ + storage, + fileFilter, +}) + +export default upload diff --git a/backend/middlewares/DLauthMiddleware.js b/backend/middlewares/DLauthMiddleware.js new file mode 100644 index 00000000..68aa8682 --- /dev/null +++ b/backend/middlewares/DLauthMiddleware.js @@ -0,0 +1,28 @@ +import jwt from 'jsonwebtoken' +import asyncHandler from 'express-async-handler' +import DLDriver from '../models/DLDriverModel.js' + +const protectDriver = asyncHandler(async (req, res, next) => { + let token + + if ( + req.headers.authorization && + req.headers.authorization.startsWith('Bearer') + ) { + try { + token = req.headers.authorization.split(' ')[1] + const decoded = jwt.verify(token, process.env.JWT_SECRET) // Verify token + + req.driver = await DLDriver.findById(decoded.id).select('-password') // Attach driver to request + next() + } catch (error) { + res.status(401).json({ message: 'Not authorized, token failed' }) + } + } + + if (!token) { + res.status(401).json({ message: 'Not authorized, no token' }) + } +}) + +export { protectDriver } diff --git a/backend/models/DLDeliveryFormModel.js b/backend/models/DLDeliveryFormModel.js new file mode 100644 index 00000000..b8c0e40c --- /dev/null +++ b/backend/models/DLDeliveryFormModel.js @@ -0,0 +1,79 @@ +import mongoose from 'mongoose' + +const DLDeliveryFormSchema = mongoose.Schema( + { + firstName: { + type: String, + required: true, + }, + lastName: { + type: String, + required: true, + }, + fullName: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + unique: true, + match: [/.+@.+\..+/, 'Please enter a valid email address'], + }, + phone: { + type: String, + required: true, + match: [/^\d{10}$/, 'Please enter a valid phone number'], + }, + dateOfBirth: { + type: Date, + required: true, + }, + idCardNumber: { + type: String, + required: true, + }, + licenseCardNumber: { + type: String, + required: true, + }, + address: { + type: String, + required: true, + }, + vehicleNumber: { + type: String, + required: true, + }, + vehicleType: { + type: String, + required: true, + enum: ['Bike', 'Three-Wheel', 'Lorry'], + }, + status: { + type: String, + required: true, + enum: ['Pending', 'Approved', 'Rejected'], + default: 'Pending', + }, + idCardImageUrl: { + type: String, + required: true, + }, + licenseImageUrl: { + type: String, + required: true, + }, + personalImageUrl: { + type: String, + required: true, + }, + }, + { + timestamps: true, // Automatically adds createdAt and updatedAt fields + } +) + +const DLDeliveryForm = mongoose.model('DLDeliveryForm', DLDeliveryFormSchema) + +export default DLDeliveryForm diff --git a/backend/models/DLDeliveryModel.js b/backend/models/DLDeliveryModel.js new file mode 100644 index 00000000..8488d636 --- /dev/null +++ b/backend/models/DLDeliveryModel.js @@ -0,0 +1,50 @@ +import mongoose from 'mongoose'; + +const DLDeliverySchema = new mongoose.Schema({ + trackingID: { + type: String, + unique: true, + }, + orderID: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Order', + }, + + oID:{type: String,}, + + driverID: { + type: mongoose.Schema.Types.ObjectId, + ref: 'DLDriver', + }, + + drID: { + type: String, + }, + shopName: String, + + pickupAddress: { + type: String, + }, + customerName: String, + + dropOffAddress: { + type: String, + }, + assignDateTime: { + type: Date, + default: Date.now, + }, + deliveryStatus: { + type: String, + enum: ['Ready', 'Picked Up', 'On The Way', 'Delivered'], + default: 'Ready', + }, + deliveredDateTime: { + type: Date, + default: null, + }, +}); + +const DLDelivery = mongoose.model('DLDelivery', DLDeliverySchema); + +export default DLDelivery; diff --git a/backend/models/DLDriverModel.js b/backend/models/DLDriverModel.js new file mode 100644 index 00000000..89a9b3d0 --- /dev/null +++ b/backend/models/DLDriverModel.js @@ -0,0 +1,91 @@ +import mongoose from 'mongoose' + +const DLDriverSchema = mongoose.Schema( + { + driverID: { + type: String, + required: true, + unique: true, // Ensure the driverID is unique + }, + firstName: { + type: String, + required: true, + }, + lastName: { + type: String, + required: true, + }, + fullName: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + unique: true, + }, + phone: { + type: String, + required: true, + }, + dateOfBirth: { + type: Date, + required: true, + }, + idCardNumber: { + type: String, + required: true, + }, + licenseCardNumber: { + type: String, + required: true, + }, + address: { + type: String, + required: true, + }, + vehicleNumber: { + type: String, + required: true, + }, + vehicleType: { + type: String, + required: true, + enum: ['Bike', 'Three-Wheel', 'Lorry'], + }, + password: { + type: String, + required: true, + }, + isAvailable: { + type: Boolean, + default: false, // Set availability to unavailable by default + }, + idCardImageUrl: { + type: String, + required: true, + }, + licenseImageUrl: { + type: String, + required: true, + }, + personalImageUrl: { + type: String, + required: true, + }, + + xxxName: { + type: String, + default: null, + }, + }, + { + timestamps: true, + } + +); + + +const DLDriver = mongoose.model('DLDriver', DLDriverSchema) + +export default DLDriver diff --git a/backend/models/DLImageModel.js b/backend/models/DLImageModel.js new file mode 100644 index 00000000..8cb0cf9e --- /dev/null +++ b/backend/models/DLImageModel.js @@ -0,0 +1,21 @@ +// models/DLImageModel.js +import mongoose from 'mongoose' + +const imageSchema = new mongoose.Schema({ + path: { + type: String, + required: true, + }, + filename: { + type: String, + required: true, + }, + uploadedAt: { + type: Date, + default: Date.now, + }, +}) + +const DLImage = mongoose.model('DLImage', imageSchema) + +export default DLImage diff --git a/backend/models/DLOModel.js b/backend/models/DLOModel.js new file mode 100644 index 00000000..d7424237 --- /dev/null +++ b/backend/models/DLOModel.js @@ -0,0 +1,72 @@ +import mongoose from 'mongoose'; + +const dorderSchema = new mongoose.Schema( + { + /* generate and store the oID:{type: String,}, */ + + orderID: { + type: String, // You can change this to any type or ObjectId if needed + required: false, // Ensure each order has a unique ID + // Make sure this order ID is unique + }, + customerName: { + type: String, + required: false, + }, + customerAddress: { + streetAddress: { type: String, required: false }, + city: { type: String, required: false }, + zipCode: { type: String, required: false }, + district: { type: String, required: false }, + }, + shopName: { + type: String, + required: [false, 'Shop name is required'], + + }, + shopAddress: { + houseNo: { + type: String, + required: [false, 'House number is required'], + }, + streetName: { + type: String, + required: [false, 'Street name is required'], + }, + city: { + type: String, + required: [false, 'City is required'], + }, + }, + orderStatus: { + type: String, + enum: ['Pending', 'Ready', 'Picked Up', 'On The Way', 'Delivered'], + default: 'Pending', + required: false, + }, + deliveryDate: { + type: Date, + default: null, + }, + deliveredAt: { + type: Date, + default: null, + }, + deliverId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Driver', // Reference to a 'Driver' model + default: null, + }, + deliverName: { + type: String, + default: null, + }, + }, + { + timestamps: true, // Automatically add createdAt and updatedAt fields + } +); + +const Order = mongoose.model('dOrder', dorderSchema, ); + +export default Order; diff --git a/backend/routes/DLDeliveryRoute.js b/backend/routes/DLDeliveryRoute.js new file mode 100644 index 00000000..9e6d0718 --- /dev/null +++ b/backend/routes/DLDeliveryRoute.js @@ -0,0 +1,14 @@ +import express from 'express'; +import { getAllDeliveries ,getDeliveryById} from '../controllers/DLDeliveryController.js'; + +const router = express.Router(); + +// Route to get all deliveries +router.get('/deliveries', getAllDeliveries); + + + +// Route to get a single delivery by ID +router.get('/d/:id', getDeliveryById); + +export default router; diff --git a/backend/routes/DLDriverRoutes.js b/backend/routes/DLDriverRoutes.js new file mode 100644 index 00000000..aa5ff4e6 --- /dev/null +++ b/backend/routes/DLDriverRoutes.js @@ -0,0 +1,67 @@ +import express from 'express' +import { + addDriver, + getDriverById, + updateDriverById, + deleteDriverById, + loginDriver, + getDriverProfile, + + updateDriverAvailability, + logoutDriver, + updateDriverProfile, + updateDriverPassword, + deleteDriverAccount, + getAllDrivers, + verifyPassword, + + // Add logout driver function +} from '../controllers/DLDriverController.js'; + + +import { protectDriver } from '../middlewares/DLauthMiddleware.js' + +const router = express.Router() + +// Route for driver login +router.post('/login', loginDriver) + +router.post('/addDriver/:id', addDriver) + +// Route to get a driver by ID +router.get('/get/:id', getDriverById) + +// Route to update a driver by ID +router.put('/update/:id', updateDriverById) + +// Route to delete a driver by ID +router.delete('/delete/:id', deleteDriverById) + + + +router.route('/profile') + .get(protectDriver, getDriverProfile) // GET profile + .put(protectDriver, updateDriverProfile) // PUT (update) profile + + + +// Route to logout the driver +router.post('/logout', logoutDriver) // Add logout route + +// Route to toggle the driver's availability + +router.put('/:id/availability', protectDriver, updateDriverAvailability); + +router.put('/profile/password', protectDriver, updateDriverPassword); + +router.delete('/delete', protectDriver, deleteDriverAccount); + +router.delete('/verifyPass', protectDriver, verifyPassword); + +router.get('/drivers', getAllDrivers); + + + + + +export default router diff --git a/backend/routes/DLEmailRoutes.js b/backend/routes/DLEmailRoutes.js new file mode 100644 index 00000000..f765354e --- /dev/null +++ b/backend/routes/DLEmailRoutes.js @@ -0,0 +1,10 @@ +// routes/DLEmailRoutes.js +import express from 'express' +import { sendApprovalEmail } from '../controllers/DLEmailController.js' + +const router = express.Router() + +// Route to send approval email +router.post('/send-approval-email/:id', sendApprovalEmail) + +export default router diff --git a/backend/routes/DLFormRoutes.js b/backend/routes/DLFormRoutes.js new file mode 100644 index 00000000..60a2485d --- /dev/null +++ b/backend/routes/DLFormRoutes.js @@ -0,0 +1,40 @@ +import express from 'express' +import { + submitDLDeliveryForm, + getPendingForms, + getDeliveryFormById, + updateDeliveryFormStatus, + updateDeliveryForm, + deleteDeliveryForm, +} from '../controllers/DLDriverFormController.js' +import upload from '../utils/DLMulter.js' + +const router = express.Router() + +// Route to submit the delivery form with images +router.post( + '/', + upload.fields([ + { name: 'idCardImage', maxCount: 1 }, + { name: 'licenseImage', maxCount: 1 }, + { name: 'personalImage', maxCount: 1 }, + ]), + submitDLDeliveryForm +) + +// Route to get all pending forms +router.get('/pending-forms', getPendingForms) + +// Route to get a specific delivery form by ID +router.get('/:id', getDeliveryFormById) + +// Route to update the status of a delivery form (approve or reject) +router.put('/:id/status', updateDeliveryFormStatus) + +// Route to update a delivery form by ID +router.put('/edit/:id', updateDeliveryForm) + +// Route to delete a delivery form by ID +router.delete('/remove/:id', deleteDeliveryForm) + +export default router diff --git a/backend/routes/DLORoutes.js b/backend/routes/DLORoutes.js new file mode 100644 index 00000000..8443c6ab --- /dev/null +++ b/backend/routes/DLORoutes.js @@ -0,0 +1,18 @@ +import express from 'express'; +import { addOrder,getOrders ,deleteOrder,updateOrderStatus} from '../controllers/DLOcontroller.js'; + +const router = express.Router(); + +// Route to add a new order +router.post('/a', addOrder); + +// Fetch all orders +router.get('/g', getOrders); + +// Update order status +router.put('/u/:id/status', updateOrderStatus); + +// Delete an order +router.delete('/d/:id', deleteOrder); + +export default router; diff --git a/backend/routes/DLimageHandlerRoute.js b/backend/routes/DLimageHandlerRoute.js new file mode 100644 index 00000000..1a450604 --- /dev/null +++ b/backend/routes/DLimageHandlerRoute.js @@ -0,0 +1,10 @@ +// routes/DLimageHandlerRoute.js +import express from 'express' +import { uploadImage } from '../controllers/DLuploadController.js' +import upload from '../middlewares/DLMulter.js' + +const router = express.Router() + +router.post('/upload', upload.single('image'), uploadImage) + +export default router diff --git a/backend/server.js b/backend/server.js index cfc7d68c..a74dd311 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,5 +1,6 @@ import express from 'express' import cors from 'cors' +import path from 'path'; //DL import cookieParser from 'cookie-parser' import connectDB from './config/db.js' import userRoute from './routes/userRoute.js' @@ -17,6 +18,18 @@ import promotionRoutes from './routes/Admin/AdminPromotionRoutes.js'; import productRoutes from './routes/Admin/AdminProductRoutes.js'; import staffRoutes from './routes/Admin/AdminStaffRoutes.js'; import customerRoutes from './routes/Admin/AdminCustomerRoutes.js'; + + +//Delivery imports +import DLFormRoutes from './routes/DLFormRoutes.js';//DL +import driverRoutes from './routes/DLDriverRoutes.js';//DL +import { fileURLToPath } from 'url'; //DL +import DLEmailRoutes from './routes/DLEmailRoutes.js'; //DL +import oRoutes from './routes/DLORoutes.js'; // DL +import { checkForAvailableDrivers } from './controllers/DLDeliveryController.js'; //DL THIS IS CHECKING ALL ODRS AND ASSIGN DRIVERS +import deliveryRoutes from './routes/DLDeliveryRoute.js'; //DL +checkForAvailableDrivers(); //DL + //Admin dotenv.config(); @@ -35,6 +48,17 @@ app.use(express.urlencoded({ extended: true })) app.use(cookieParser()) + +//Delivery image + +const __filename = fileURLToPath(import.meta.url); //DL +const __dirname = path.dirname(__filename);//DL + +app.use('/uploads', express.static(path.join(__dirname, 'uploads')));//DL + + + + // routes app.get('/', (_req, res) => { res.send('FarmCart API is Running...') @@ -59,6 +83,18 @@ app.use('/api/product', productRoutes); app.use('/api/staff', staffRoutes); app.use('/api/customer', customerRoutes); + + +//Delivery Routes +// Routes +app.use('/api/images', imageHandler);//DL +app.use('/api/d_forms', DLFormRoutes);//DL +app.use('/api/drivers', driverRoutes); // Added driver routes +app.use('/api/email', DLEmailRoutes); // Use the email routes +app.use('/api/od', oRoutes);//dl +app.use('/api/delivery', deliveryRoutes); // Use the delivery routes + + // Middleware to handle errors and send appropriate responses app.use(errorHandler) diff --git a/backend/uploads/1727053967402-id1.jpeg b/backend/uploads/1727053967402-id1.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..ed7e2639738ce0ac6a4b9b42bbb9cc15edf24245 GIT binary patch literal 10183 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 T)UahH_fEn<^$5;;cMJau;FyAm literal 0 HcmV?d00001 diff --git a/backend/uploads/1727054250696-id2.jpeg b/backend/uploads/1727054250696-id2.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..2efa566d96bc7296d0b7c55e35c04125aa98098d GIT binary patch literal 10434 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