//Get the data models const { Supplier, DeliveryBoy, profilePictureSupplier } = require("../models/supplier"); const { FriendRequest,RequestedBooking,RecurringRequestedBooking } = require("../models/supplier"); const { Tanker,Tankerbooking } = require("../models/tankers"); const { ProfilePicture, User } = require("../models/User"); const supplierController = require("../controllers/supplierController"); const customJwtAuth = require("../customAuthJwt"); const fastify = require("fastify")({ logger: true, //disableRequestLogging: true, genReqId(req) { // you get access to the req here if you need it - must be a synchronous function return uuidv4(); }, }); const moment = require('moment'); const fastifyEnv = require("fastify-env"); const boom = require("boom"); const emailValidator = require("email-validator"); const libphonenumberjs = require("libphonenumber-js"); fastify.register(customJwtAuth); const schema = { type: "object", required: ["PORT"], properties: { PORT: { type: "string", default: 3000, }, APIVERSION: { type: "string", default: "1.0.0", }, }, }; const options = { confKey: "config", // optional, default: 'config' schema: schema, // data: data // optional, default: process.env }; fastify.register(fastifyEnv, options).ready((err) => { if (err) console.error(err); console.log(fastify.config.PORT); // or fastify[options.confKey] // output: { PORT: 3000 } fastify.decorate("conf", { port: fastify.config.PORT, APIVERSION: fastify.config.APIVERSION, }); }); const apiversion = "1.0.0"; // fastify.register(require('fastify-cookie')) // fastify.register(require('fastify-session'), { // secret: 'my-secret-key', // cookie: { secure: true } // }); isSupplierFormUrlEncoded = (req) => { var isSupplierFormUrlEncoded = false; console.log("check is Supplier encoe url funtion"); // This iterates through the req headers object. // could not access req.headers.content-type due to the hyphen in the content-type key. // console.log(`${key}: ${value}`); for (const [key, value] of Object.entries(req.headers)) { if (`${key}` === "content-type") { if (`${value}` == "application/x-www-form-urlencoded") { // console.log( "data supplied is with content type," , `${value}`) // set isUserFormUrlEncoded value to true isSupplierFormUrlEncoded = true; // create user object with form variables . Password is used from the request object directly. s_data = { suppliername: req.body.suppliername, phone: req.body.phone, office_address: req.body.office_address, password: req.body.password, emails: [ { email: req.body.email, }, ], profile: { firstName: req.body.firstName, lastName: req.body.lastName, }, }; return { isSupplierFormUrlEncoded: isSupplierFormUrlEncoded, supplier: s_data, }; } else { return { isSupplierFormUrlEncoded: false, s_data: "" }; } } } }; fastify.register((fastify, opts, done) => { fastify.addContentTypeParser( "application/json", { parseAs: "buffer" }, function (_req, body, done) { try { done(null, body); } catch (error) { error.statusCode = 400; done(error, undefined); } } ); done(null); }); //Login Supplier Handler exports.loginSupplier = async (request, reply) => { loginObject = await supplierController.loginSupplier(request); console.log("loginObject...", loginObject); if (loginObject.same) { const phoneVerified = loginObject.supplier.phoneVerified; const oneTimePasswordSetFlag = loginObject.supplier.oneTimePasswordSetFlag; console.log( "oneTimePasswordSetFlag is ......", oneTimePasswordSetFlag, typeof oneTimePasswordSetFlag, typeof phoneVerified ); if (!phoneVerified) { reply.send({ simplydata: { error: false, phoneVerified: false, phone: loginObject.supplier.phone, oneTimePasswordSetFlag: oneTimePasswordSetFlag, message: "Please Verify your phone number", }, }); } else if (oneTimePasswordSetFlag) { reply.send({ simplydata: { error: false, phoneVerified: phoneVerified, phone: loginObject.supplier.phone, oneTimePasswordSetFlag: true, message: "Password must be reset", }, }); } else { const token = fastify.jwt.sign( { suppliername: loginObject.supplier.suppliername, supplierId: loginObject.supplier._id, roles: loginObject.supplier.profile.role, }, //expiresIn: expressed in seconds or a string describing a time span zeit/ms. Eg: 60, "2 days", "10h", "7d". //A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (days, hours, etc), //otherwise milliseconds unit is used by default ("120" is equal to "120ms"). { expiresIn: "30d" } ); console.log(token, "..token"); var arr = loginObject.supplier.profile.role; var arrayToString = JSON.stringify(Object.assign({}, arr)); // convert array to string var stringToJsonObject = JSON.parse(arrayToString); // convert string to json object var s_id = loginObject.supplier.supplierId; console.log(s_id, "supplierId"); var profilePicture = await profilePictureSupplier.findOne({ supplierId: s_id }); // request.session.set('supplierId', loginObject.supplier._id) if (!profilePicture) { reply.send({ simplydata: { error: false, apiversion: fastify.config.APIVERSION, access_token: token, email: loginObject.supplier.emails, phone: loginObject.supplier.phone, supplierId: loginObject.supplier.supplierId, suppliername: loginObject.supplier.suppliername, office_address: loginObject.supplier.profile.office_address, phoneVerified: loginObject.supplier.phoneVerified, oneTimePasswordSetFlag: loginObject.supplier.oneTimePasswordSetFlag, latitude: loginObject.supplier.latitude, longitude: loginObject.supplier.longitude, type: loginObject.supplier.profile.role, description : loginObject.supplier.description, typeasobj: stringToJsonObject, }, }); } if (profilePicture) { reply.send({ simplydata: { error: false, apiversion: fastify.config.APIVERSION, access_token: token, picture: profilePicture.picture, email: loginObject.supplier.emails, phone: loginObject.supplier.phone, supplierId: loginObject.supplier.supplierId, suppliername: loginObject.supplier.suppliername, office_address: loginObject.supplier.profile.office_address, phoneVerified: loginObject.supplier.phoneVerified, oneTimePasswordSetFlag: loginObject.supplier.oneTimePasswordSetFlag, latitude: loginObject.supplier.latitude, longitude: loginObject.supplier.longitude, description : loginObject.supplier.description, type: loginObject.supplier.profile.role, typeasobj: stringToJsonObject, }, }); } } } else { error = { simplydata: { error: true, code: 400, message: "Invalid SupplierId , Password supplied", }, }; reply.send(error); } }; //Login Delivery Handler exports.loginDeliveryBoy = async (request, reply) => { phone = request.body.phone; phoneVerificationCode = request.body.phoneVerificationCode; // check if user exists in the system. If user exists , display message that // username is not available console.log( "this is the phone and verification code", phone, phoneVerificationCode ); deliveryBoyExists = await DeliveryBoy.findOne({ phone: phone, phoneVerified: false, phoneVerificationCode: phoneVerificationCode, }); console.log(deliveryBoyExists); if (deliveryBoyExists) { // update the phoneVerified flag to true. const filter = { phone: phone, phoneVerificationCode: phoneVerificationCode, }; const update = { phoneVerified: true }; const doc = await DeliveryBoy.findOneAndUpdate(filter, update); updatedDeliveryBoy = await DeliveryBoy.findOne({ phone: phone }); } loginObject = await supplierController.loginDeliveryBoy(request); console.log("loginObject...", loginObject); if (loginObject.same) { const phoneVerified = loginObject.delivery.phoneVerified; const oneTimePasswordSetFlag = loginObject.delivery.oneTimePasswordSetFlag; console.log( "oneTimePasswordSetFlag is ......", oneTimePasswordSetFlag, typeof oneTimePasswordSetFlag, typeof phoneVerified ); if (!phoneVerified) { reply.send({ simplydata: { error: false, phoneVerified: false, phone: loginObject.delivery.phone, oneTimePasswordSetFlag: oneTimePasswordSetFlag, message: "Please Verify your phone number", }, }); } else if (oneTimePasswordSetFlag) { reply.send({ simplydata: { error: false, phoneVerified: phoneVerified, phone: loginObject.delivery.phone, oneTimePasswordSetFlag: true, message: "Password must be reset", }, }); } else { const token = fastify.jwt.sign( { deliveryBoyname: loginObject.delivery.name, deliveryBoyId: loginObject.delivery._id, }, //expiresIn: expressed in seconds or a string describing a time span zeit/ms. Eg: 60, "2 days", "10h", "7d". //A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (days, hours, etc), //otherwise milliseconds unit is used by default ("120" is equal to "120ms"). { expiresIn: "30d" } ); console.log(token, "..token"); var d_id = loginObject.delivery._id; console.log(d_id, "deliveryId"); var profilePicture = await ProfilePicture.findOne({ deliveryBoyId: d_id, }); // request.session.set('supplierId', loginObject.supplier._id) if (!profilePicture) { reply.send({ simplydata: { error: false, apiversion: fastify.config.APIVERSION, access_token: token, phone: loginObject.delivery.phone, deliveryBoyId: loginObject.delivery.deliveryBoyId, deliveryBoyname: loginObject.delivery.name, address: loginObject.delivery.address, phoneVerified: loginObject.delivery.phoneVerified, oneTimePasswordSetFlag: loginObject.delivery.oneTimePasswordSetFlag, supplierId: loginObject.delivery.supplierId, suppliername: loginObject.delivery.suppliername, longitude: loginObject.delivery.longitude, latitude: loginObject.delivery.latitude, }, }); } if (profilePicture) { reply.send({ simplydata: { error: false, apiversion: fastify.config.APIVERSION, access_token: token, picture: profilePicture.picture, phone: loginObject.delivery.phone, deliveryBoyId: loginObject.delivery.deliveryBoyId, deliveryBoyname: loginObject.delivery.name, address: loginObject.delivery.address, phoneVerified: loginObject.delivery.phoneVerified, oneTimePasswordSetFlag: loginObject.delivery.oneTimePasswordSetFlag, supplierId: loginObject.delivery.supplierId, suppliername: loginObject.delivery.suppliername, longitude: loginObject.delivery.longitude, latitude: loginObject.delivery.latitude, }, }); } } } else { error = { simplydata: { error: true, code: 400, message: "Invalid Details", }, }; reply.send(error); } }; exports.deliveryBoyVerifyPhone = async (req, reply) => { console.log("-------------------------------------------------"); try { phone = req.body.phone; phoneVerificationCode = req.body.phoneVerificationCode; // check if user exists in the system. If user exists , display message that // username is not available console.log( "this is the phone and verification code", phone, phoneVerificationCode ); deliveryBoyExists = await DeliveryBoy.findOne({ phone: phone, //phoneVerified: false, phoneVerificationCode: phoneVerificationCode, }); console.log(deliveryBoyExists); if (deliveryBoyExists) { // update the phoneVerified flag to true. const filter = { phone: phone, phoneVerificationCode: phoneVerificationCode, }; const update = { phoneVerified: true }; const doc = await DeliveryBoy.findOneAndUpdate(filter, update); updatedDeliveryBoy = await DeliveryBoy.findOne({ phone: phone }); if (updatedDeliveryBoy.phoneVerified) { loginObject = await supplierController.loginDeliveryBoy(req); console.log("loginObject...", loginObject); if (loginObject.same) { const phoneVerified = loginObject.delivery.phoneVerified; const oneTimePasswordSetFlag = loginObject.delivery.oneTimePasswordSetFlag; console.log( "oneTimePasswordSetFlag is ......", oneTimePasswordSetFlag, typeof oneTimePasswordSetFlag, typeof phoneVerified ); if (!phoneVerified) { reply.send({ simplydata: { error: false, phoneVerified: false, phone: loginObject.delivery.phone, oneTimePasswordSetFlag: oneTimePasswordSetFlag, message: "Please Verify your phone number", }, }); } else if (oneTimePasswordSetFlag) { reply.send({ simplydata: { error: false, phoneVerified: phoneVerified, phone: loginObject.delivery.phone, oneTimePasswordSetFlag: true, message: "Password must be reset", }, }); } else { const token = fastify.jwt.sign( { deliveryBoyname: loginObject.delivery.name, deliveryBoyId: loginObject.delivery._id, }, //expiresIn: expressed in seconds or a string describing a time span zeit/ms. Eg: 60, "2 days", "10h", "7d". //A numeric value is interpreted as a seconds count. If you use a string be sure you provide the time units (days, hours, etc), //otherwise milliseconds unit is used by default ("120" is equal to "120ms"). { expiresIn: "30d" } ); console.log(token, "..token"); var d_id = loginObject.delivery._id; console.log(d_id, "deliveryId"); var profilePicture = await ProfilePicture.findOne({ deliveryBoyId: d_id, }); // request.session.set('supplierId', loginObject.supplier._id) if (!profilePicture) { reply.send({ simplydata: { error: false, apiversion: fastify.config.APIVERSION, access_token: token, phone: loginObject.delivery.phone, deliveryBoyId: loginObject.delivery.deliveryBoyId, deliveryBoyname: loginObject.delivery.name, address: loginObject.delivery.address, phoneVerified: loginObject.delivery.phoneVerified, oneTimePasswordSetFlag: loginObject.delivery.oneTimePasswordSetFlag, supplierId: loginObject.delivery.supplierId, suppliername: loginObject.delivery.suppliername, longitude: loginObject.delivery.longitude, latitude: loginObject.delivery.latitude, }, }); } if (profilePicture) { reply.send({ simplydata: { error: false, apiversion: fastify.config.APIVERSION, access_token: token, picture: profilePicture.picture, phone: loginObject.delivery.phone, deliveryBoyId: loginObject.delivery.deliveryBoyId, deliveryBoyname: loginObject.delivery.name, address: loginObject.delivery.address, phoneVerified: loginObject.delivery.phoneVerified, oneTimePasswordSetFlag: loginObject.delivery.oneTimePasswordSetFlag, supplierId: loginObject.delivery.supplierId, suppliername: loginObject.delivery.suppliername, longitude: loginObject.delivery.longitude, latitude: loginObject.delivery.latitude, }, }); } } } else { error = { simplydata: { error: true, code: 400, message: "Invalid Details", }, }; reply.send(error); } } }else { error = { armintatankdata: { error: true, code: 10005, message: "10005 - Verification code entered cannot be validated.", }, }; req.body.regError = error; reply.send(error); } } catch (err) { throw boom.boomify(err); } }; // Check if all the required fields are supplied by the user exports.fieldCheck = async (req, reply) => { try { s_Data = { suppliername: req.body.suppliername, emails: req.body.emails, password: req.body.password, services: { password: { bcrypt: req.body.password } }, phone: req.body.phone, profile: { firstName: req.body.firstName, lastName: req.body.lastName, contactNumber: req.body.phone, alternativeContactNumber: req.body.alternativeContactNumber, country: req.body.country, state: req.body.state, city: req.body.city, office_address: req.body.office_adress, zip: req.body.zip, }, }; var supplier = new Supplier(s_Data); console.log(supplier, "..supplier"); //password is not at the top level in the collection. password = req.body.password; // capture fields if data is sent via form instead of json encoding checkFormEncoding = isSupplierFormUrlEncoded(req); if (checkFormEncoding.isSupplierFormUrlEncoded) { suppliertobeInserted = checkFormEncoding.supplier; supplier.suppliername = suppliertobeInserted.suppliername; supplier.phone = suppliertobeInserted.phone; supplier.emails = suppliertobeInserted.emails; password = suppliertobeInserted.password; } console.log( "User to be inserted is ", supplier.suppliername, password, supplier.phone, supplier.profile ); // check if all rerquired fields are passed. if ( !( supplier.suppliername && password && supplier.phone && // user.profile.firstName && // user.profile.lastName && // user.profile.address1 && supplier.emails[0].email ) ) { console.log( supplier.suppliername, password, supplier.phone, // user.profile.firstName, // user.profile.lastName, supplier.emails[0].email ); // Required Fields are missing suppliedvalues = supplier.suppliername + " ," + password + " ," + supplier.phone + " ," + supplier.firstName + " ," + supplier.lastName + " ," + supplier.emails[0].email; error = { armintatankdata: { error: true, code: 10004, message: "10004 - suppliername, password, phone , firstname , lastname email city country state address1 and zip are required fields. Supplied values are " + suppliedvalues, }, }; req.body.regError = error; reply.send(error); } } catch (err) { throw boom.boomify(err); } }; exports.validatePhoneFormat = async (req, reply) => { try { var supplier = new Supplier(req.body); // check if user supplied phone is of the right format. // Handle if the user data is supplied via a url encoded form // capture fields if data is sent via form instead of json encoding checkFormEncoding = isSupplierFormUrlEncoded(req); console.log(checkFormEncoding); if (checkFormEncoding.isSupplierFormUrlEncoded) { suppliertobeInserted = checkFormEncoding.supplier; supplier.suppliername = suppliertobeInserted.suppliername; supplierser.firstName = suppliertobeInserted.firstName; supplier.lastName = suppliertobeInserted.lastName; supplier.phone = suppliertobeInserted.phone; supplier.emails = suppliertobeInserted.emails; } if (supplier) { const phoneNumber = libphonenumberjs.parsePhoneNumber(supplier.phone); if (phoneNumber) { // access returned collection if (!phoneNumber.isValid()) { error = { armintatankdata: { error: true, code: 10002, message: "10002 - Phone # " + supplier.phone + " is not a valid phone number", }, }; req.body.regError = error; reply.status(406).send(error); } } } } catch (err) { throw boom.boomify(err); } }; exports.verifySupplier = async (req, reply) => { try { var supplier = new Supplier(req.body); // Handle if the user data is supplied via a url encoded form // capture fields if data is sent via form instead of json encoding checkFormEncoding = isSupplierFormUrlEncoded(req); if (checkFormEncoding.isSupplierFormUrlEncoded) { suppliertobeInserted = checkFormEncoding.supplier; supplier.suppliername = suppliertobeInserted.suppliername; supplier.firstName = suppliertobeInserted.firstName; supplier.lastName = suppliertobeInserted.lastName; supplierr.phone = suppliertobeInserted.phone; supplier.emails = suppliertobeInserted.emails; } phone = supplier.phone; supplierpass = req.body.password; // check if user exists in the system. If user exists , display message that // phone number is not available supplierExists = await Supplier.findOne({ phone: phone }); if (supplierExists) { // return user exists message error = { armintatankdata: { error: true, code: 10001, message: "10001 - Phone " + supplierExists.phone + " is not available. please use a different phone number", }, }; req.body.regError = error; reply.send(error); } } catch (err) { throw boom.boomify(err); } }; exports.validateEmailFormat = async (req, reply) => { try { var supplier = new Supplier(req.body); // Handle if the user data is supplied via a url encoded form // capture fields if data is sent via form instead of json encoding checkFormEncoding = isSupplierFormUrlEncoded(req); if (checkFormEncoding.isSupplierFormUrlEncoded) { suppliertobeInserted = checkFormEncoding.supplier; supplier.suppliername = suppliertobeInserted.suppliername; supplier.firstName = suppliertobeInserted.firstName; supplier.lastName = suppliertobeInserted.lastName; supplierr.phone = suppliertobeInserted.phone; supplier.emails = suppliertobeInserted.emails; } supplieremail = await supplier.emails[0].email; // check if user supplied email is of the right format. if (supplier) { const isValidEmail = emailValidator.validate(supplieremail.trim()); if (!isValidEmail) { // Return email invalid format message error = { armintatankdata: { error: true, code: 10003, message: "10003 - Email " + supplier.emails[0].email + " is not a valid email", }, }; req.body.regError = error; reply.send(error); } } } catch (err) { throw boom.boomify(err); } }; exports.logoutsupplier = async (request, reply) => { // request.session.delete(); // // send response to clear token // reply.send({ message: 'Successfully logged out' }) const invalidatedTokens = {}; const accessToken = request.headers.authorization && request.body.access_token; invalidatedTokens[accessToken] = true; // // localStorage.removeItem(invalidatedTokens[accessToken]) reply.send({ message: "Logout successful" }); }; exports.verifyPhone = async (req, reply) => { console.log("-------------------------------------------------"); try { phone = req.body.phone; phoneVerificationCode = req.body.phoneVerificationCode; // check if user exists in the system. If user exists , display message that // username is not available console.log( "this is the phone and verification code", phone, phoneVerificationCode ); supplierExists = await Supplier.findOne({ phone: phone, phoneVerified: false, phoneVerificationCode: phoneVerificationCode, }); console.log(supplierExists); if (supplierExists) { // update the phoneVerified flag to true. const filter = { phone: phone, phoneVerificationCode: phoneVerificationCode, }; const update = { phoneVerified: true }; const doc = await Supplier.findOneAndUpdate(filter, update); updatedSupplier = await Supplier.findOne({ phone: phone }); if (updatedSupplier.phoneVerified) { reply.send('{"armintatankdata":{"error":false,"verified": true}}'); } else { error = { armintatankdata: { error: true, code: 10005, message: "10005 - Verification code entered cannot be validated.", }, }; req.body.regError = error; reply.send(error); } } else { error = { armintatankdata: { error: true, code: 10005, message: "10005 - Verification code entered cannot be validated.", }, }; req.body.regError = error; reply.send(error); } } catch (err) { throw boom.boomify(err); } }; // exports.deliveryBoyVerifyPhone = async (req, reply) => { // console.log("-------------------------------------------------"); // try { // phone = req.body.phone; // phoneVerificationCode = req.body.phoneVerificationCode; // // check if user exists in the system. If user exists , display message that // // username is not available // console.log( // "this is the phone and verification code", // phone, // phoneVerificationCode // ); // deliveryBoyExists = await DeliveryBoy.findOne({ // phone: phone, // phoneVerified: false, // phoneVerificationCode: phoneVerificationCode, // }); // console.log(deliveryBoyExists); // if (deliveryBoyExists) { // // update the phoneVerified flag to true. // const filter = { // phone: phone, // phoneVerificationCode: phoneVerificationCode, // }; // const update = { phoneVerified: true }; // const doc = await DeliveryBoy.findOneAndUpdate(filter, update); // updatedDeliveryBoy = await DeliveryBoy.findOne({ phone: phone }); // if (updatedDeliveryBoy.phoneVerified) { // reply.send('{"armintatankdata":{"error":false,"verified": true}}'); // } else { // error = { // armintatankdata: { // error: true, // code: 10005, // message: "10005 - Verification code entered cannot be validated.", // }, // }; // req.body.regError = error; // reply.send(error); // } // } // } catch (err) { // throw boom.boomify(err); // } // }; //delete selected deliveryboy exports.deleteDeliveryBoy = async (req, reply) => { try { var supplierId = req.params.supplierId; var phone = req.query.phone; const delivery = await DeliveryBoy.findOneAndDelete({ phone: phone, supplierId: supplierId, }); reply.send({ status_code: 200, data: delivery }); // return tank; } catch (err) { throw boom.boomify(err); } }; //update selected Delivery Boy Details exports.updateDeliveryBoy = async (req, reply) => { try { var supplierId = req.params.supplierId; var phone = req.query.phone; const delivery = req.body; const { ...updateData } = delivery; const update = await DeliveryBoy.findOneAndUpdate( { phone: phone, supplierId: supplierId }, updateData, { new: true } ); console.log(update); //return update; reply.send({ status_code: 200, data: update }); } catch (err) { throw boom.boomify(err); } }; // Get current supplier exports.getCurrentSupplier = async (req, reply) => { try { const supplierId = req.body.supplierId; const suppliers = await Supplier.findOne({ supplierId: supplierId }); return suppliers; } catch (err) { throw boom.boomify(err); } }; // Get all suppliers // exports.getSuppliers = async (req, reply) => { // const limit = parseInt(req.query.limit) || 100; // const page = parseInt(req.query.page) || 1; // const startindex = (page - 1) * limit; // try { // await Supplier.find() // .limit(limit) // .skip(startindex) // .exec() // .then((docs) => { // reply.send({ status_code: 200, data: docs, count: docs.length }); // }) // .catch((err) => { // console.log(err); // reply.send({ error: err }); // }); // } catch (err) { // throw boom.boomify(err); // } // }; exports.getSuppliers = async (req, reply) => { const customerId = req.params.customerId; const { type_of_water, capacity: requestedCapacityStr, quantity: requestedQuantityStr, date, time, price_from, price_to, radius_from, radius_to, rating_from, rating_to } = req.body; const parseCapacity = (value) => parseFloat((value || "0").toString().replace(/,/g, "")); const parsePrice = (value) => parseInt((value || "0").toString().replace(/,/g, "")); const requestedCapacity = parseCapacity(requestedCapacityStr); const requestedQuantity = parseInt(requestedQuantityStr || "0"); const totalRequiredCapacity = requestedCapacity * requestedQuantity; const priceFrom = parsePrice(price_from); const priceTo = parsePrice(price_to); try { const customerData = await User.findOne({ customerId }); const favorateSuppliers = customerData?.favorate_suppliers || []; const tankerBookings = await Tankerbooking.find({ date }); const bookedTankerSet = new Set( tankerBookings.map(booking => `${booking.supplierId}_${booking.tankerName}`) ); const tankerQuery = {}; if (type_of_water && type_of_water.trim() !== "") { tankerQuery.typeofwater = type_of_water; } let tankers = await Tanker.find(tankerQuery); const isValidPrice = (val) => { const num = parseInt(val); return !isNaN(num) && isFinite(num); }; if (isValidPrice(price_from) && isValidPrice(price_to)) { tankers = tankers.filter(tanker => { const tankerPrice = parsePrice(tanker.price); return tankerPrice >= priceFrom && tankerPrice <= priceTo; }); } tankers = tankers.filter(tanker => { const key = `${tanker.supplierId}_${tanker.tankerName}`; return !bookedTankerSet.has(key); }); const supplierTankerMap = {}; for (let tanker of tankers) { if (!supplierTankerMap[tanker.supplierId]) { supplierTankerMap[tanker.supplierId] = []; } supplierTankerMap[tanker.supplierId].push(tanker); } const qualifiedSuppliers = []; for (let [supplierId, supplierTankers] of Object.entries(supplierTankerMap)) { const totalAvailableCapacity = supplierTankers.reduce( (sum, t) => sum + parseCapacity(t.capacity), 0 ); if (requestedCapacity > 0 && requestedQuantity > 0) { if (totalAvailableCapacity < totalRequiredCapacity) { continue; } } qualifiedSuppliers.push({ supplierId, tankers: supplierTankers }); } const suppliers = []; for (let supplierObj of qualifiedSuppliers) { const supplierData = await Supplier.findOne({ supplierId: supplierObj.supplierId }); const friendRequest = await FriendRequest.findOne({ customerId: customerId, supplierId: supplierObj.supplierId }); const isConnected = friendRequest && friendRequest.status === "accepted"; const isFavorite = favorateSuppliers.includes(supplierObj.supplierId); // 🔥 Now check RequestedBooking collection const requestedBookingRecord = await RequestedBooking.findOne({ customerId: customerId, "requested_suppliers.supplierId": supplierObj.supplierId }); let requestedBooking = { status: false }; if (requestedBookingRecord) { requestedBooking = { status: true, time: requestedBookingRecord.time }; } suppliers.push({ supplier: supplierData, tankers: supplierObj.tankers, isConnected: isConnected, isFavorite: isFavorite, requestedBooking: requestedBooking }); } reply.send({ status_code: 200, suppliers }); } catch (err) { console.error(err); reply.send({ status_code: 500, message: "Something went wrong", error: err.message }); } }; // GET SUPPLIERS (simple): only needs customerId; no tanker checks exports.getSupplierswithoutbooking = async (req, reply) => { try { const { customerId } = req.params; // 1) Load customer to read favorites const customer = await User.findOne({ customerId }, { favorate_suppliers: 1, _id: 0 }); if (!customer) { return reply.code(404).send({ status_code: 404, message: "Customer not found" }); } const favoriteSet = new Set(customer.favorate_suppliers || []); // 2) Load all suppliers const suppliers = await Supplier.find({}); // add projection if you want to slim payload // 3) Find accepted connections for this customer across ALL suppliers in one go const supplierIds = suppliers.map(s => s.supplierId).filter(Boolean); const acceptedReqs = await FriendRequest.find( { customerId, supplierId: { $in: supplierIds }, status: "accepted" }, { supplierId: 1, _id: 0 } ); const connectedSet = new Set(acceptedReqs.map(r => r.supplierId)); // 4) Build response const result = suppliers.map(s => ({ supplier: s, isFavorite: favoriteSet.has(s.supplierId), isConnected: connectedSet.has(s.supplierId), })); return reply.send({ status_code: 200, suppliers: result }); } catch (err) { console.error(err); return reply.code(500).send({ status_code: 500, message: "Something went wrong", error: err.message, }); } }; exports.createRequestedBooking = async (req, reply) => { const { customerId, type_of_water, capacity, quantity, date, time, requested_suppliers } = req.body; const parseCapacity = (value) => parseFloat((value || "0").toString().replace(/,/g, "")); const requestedCapacity = parseCapacity(capacity); const requestedQuantity = parseInt(quantity || "0"); const totalRequiredCapacity = requestedCapacity * requestedQuantity; try { const requestedBooking = new RequestedBooking({ customerId, type_of_water, capacity, quantity, total_required_capacity: totalRequiredCapacity, date, time, requested_suppliers, // ✅ already contains supplierId, quoted_amount, custom_field status: "pending" }); await requestedBooking.save(); reply.send({ status_code: 200, message: "Requested booking created successfully", data: requestedBooking }); } catch (err) { console.error(err); reply.send({ status_code: 500, message: "Something went wrong while saving", error: err.message }); } } // Get single user by ID exports.getSingleSupplier = async (req, reply) => { try { const supplierId = req.params.supplierId; const supplier = await Supplier.findOne({ supplierId: supplierId }); return supplier; } catch (err) { throw boom.boomify(err); } }; // exports.getConnectedSuppliers = async (req, reply) => { // const limit = parseInt(req.query.limit) || 100; // const page = parseInt(req.query.page) || 1; // const startindex = (page - 1) * limit; // const customerId = req.params.customerId; // Assuming you have already authenticated the user and stored their ID in the request object // try { // const friendRequests = await FriendRequest.find({ // customerId, // status: "accepted", // }); // // console.log(friendRequests,customerId) // const supplierIdsToInclude = friendRequests.map( // (request) => request.supplierId // ); // await Supplier.find({ supplierId: { $in: supplierIdsToInclude } }) // .limit(limit) // .skip(startindex) // .exec() // .then((docs) => { // reply.send({ status_code: 200, data: docs, count: docs.length }); // }) // .catch((err) => { // console.log(err); // reply.send({ error: err }); // }); // } catch (err) { // throw boom.boomify(err); // } // }; exports.getConnectedSuppliers = async (req, reply) => { const limit = parseInt(req.query.limit) || 100; const page = parseInt(req.query.page) || 1; const startindex = (page - 1) * limit; const customerId = req.params.customerId; try { // Get user's favorite suppliers const user = await User.findOne({ customerId }, 'favorate_suppliers'); const favorateSuppliers = user?.favorate_suppliers || []; // Get accepted friend requests const friendRequests = await FriendRequest.find({ customerId, status: "accepted", }); const supplierIdsToInclude = friendRequests.map(req => req.supplierId); // Get suppliers const suppliers = await Supplier.find({ supplierId: { $in: supplierIdsToInclude } }) .limit(limit) .skip(startindex) .exec(); const supplierIds = suppliers.map(s => s.supplierId); // Get profile pictures const profilePictures = await profilePictureSupplier.find({ supplierId: { $in: supplierIds } }); // Construct final response const data = suppliers.map((supplier) => { const profilePicture = profilePictures.find( (pic) => pic.supplierId === supplier.supplierId ); const isFavorate = favorateSuppliers.includes(supplier.supplierId); return { ...supplier.toObject(), picture: profilePicture ? profilePicture.picture : null, favorate: isFavorate, }; }); reply.send({ status_code: 200, data, count: data.length }); } catch (err) { throw boom.boomify(err); } }; // exports.getPendingSuppliers = async (req, reply) => { // const limit = parseInt(req.query.limit) || 100; // const page = parseInt(req.query.page) || 1; // const startindex = (page - 1) * limit; // const customerId = req.params.customerId; // Assuming you have already authenticated the user and stored their ID in the request object // try { // const friendRequests = await FriendRequest.find({ // customerId, // status: ["pending"], // }); // // console.log(friendRequests,customerId) // const supplierIdsToInclude = friendRequests.map( // (request) => request.supplierId // ); // console.log(supplierIdsToInclude, "SUPLIERINCLUDE"); // const timestamps = friendRequests.map(request => request.timestamp); // console.log(timestamps, "timestamps"); // await Supplier.find({ supplierId: { $in: supplierIdsToInclude } }) // .limit(limit) // .skip(startindex) // .exec() // .then((docs) => { // const customerDataWithTimestamp = docs.map((doc, index) => ({ // ...doc._doc, // timestamp: timestamps[index], // })); // reply.send({ // status_code: 200, // data: customerDataWithTimestamp, // count: docs.length, // }); // //reply.send({ status_code: 200, data: docs, count: docs.length }); // }) // .catch((err) => { // console.log(err); // reply.send({ error: err }); // }); // } catch (err) { // throw boom.boomify(err); // } // }; exports.getPendingSuppliers = async (req, reply) => { const limit = parseInt(req.query.limit) || 100; const page = parseInt(req.query.page) || 1; const startindex = (page - 1) * limit; const customerId = req.params.customerId; // Assuming you have already authenticated the user and stored their ID in the request object try { const friendRequests = await FriendRequest.find({ customerId, status: ["pending"], }); const supplierIdsToInclude = friendRequests.map( (request) => request.supplierId ); const timestamps = friendRequests.map((request) => request.timestamp); const suppliers = await Supplier.find({ supplierId: { $in: supplierIdsToInclude }, }) .limit(limit) .skip(startindex) .exec(); const supplierIds = suppliers.map((supplier) => supplier.supplierId); const profilePictures = await profilePictureSupplier.find({ supplierId: { $in: supplierIds }, }).exec(); const data = suppliers.map((supplier, index) => { const profilePicture = profilePictures.find( (picture) => picture.supplierId === supplier.supplierId ); return { ...supplier.toObject(), timestamp: timestamps[index], picture: profilePicture ? profilePicture.picture : null, }; }); reply.send({ status_code: 200, data, count: data.length }); } catch (err) { throw boom.boomify(err); } }; // exports.getRejectSuppliers = async (req, reply) => { // const limit = parseInt(req.query.limit) || 100; // const page = parseInt(req.query.page) || 1; // const startindex = (page - 1) * limit; // const customerId = req.params.customerId; // Assuming you have already authenticated the user and stored their ID in the request object // try { // const friendRequests = await FriendRequest.find({ // customerId, // status: ["rejected"], // }); // // console.log(friendRequests,customerId) // const supplierIdsToInclude = friendRequests.map( // (request) => request.supplierId // ); // // console.log(supplierIdsToInclude) // await Supplier.find({ supplierId: { $in: supplierIdsToInclude } }) // .limit(limit) // .skip(startindex) // .exec() // .then((docs) => { // reply.send({ status_code: 200, data: docs, count: docs.length }); // }) // .catch((err) => { // console.log(err); // reply.send({ error: err }); // }); // } catch (err) { // throw boom.boomify(err); // } // }; exports.getRejectSuppliers = async (req, reply) => { const limit = parseInt(req.query.limit) || 100; const page = parseInt(req.query.page) || 1; const startindex = (page - 1) * limit; const customerId = req.params.customerId; // Assuming you have already authenticated the user and stored their ID in the request object try { const friendRequests = await FriendRequest.find({ customerId, status: ["rejected"], }); const supplierIdsToInclude = friendRequests.map( (request) => request.supplierId ); const suppliers = await Supplier.find({ supplierId: { $in: supplierIdsToInclude }, }) .limit(limit) .skip(startindex) .exec(); const supplierIds = suppliers.map((supplier) => supplier.supplierId); const profilePictures = await profilePictureSupplier.find({ supplierId: { $in: supplierIds }, }).exec(); const data = suppliers.map((supplier) => { const profilePicture = profilePictures.find( (picture) => picture.supplierId === supplier.supplierId ); return { ...supplier.toObject(), picture: profilePicture ? profilePicture.picture : null, }; }); reply.send({ status_code: 200, data, count: data.length }); } catch (err) { throw boom.boomify(err); } }; exports.getPendingCustomers = async (req, reply) => { const limit = parseInt(req.query.limit) || 100; const page = parseInt(req.query.page) || 1; const startindex = (page - 1) * limit; const supplierId = req.params.supplierId; try { const friendRequests = await FriendRequest.find({ supplierId, status: ["pending"], }); const supplierIdsToInclude = friendRequests.map( (request) => request.customerId ); // const timestamps = friendRequests.map(request => // moment(request.timestamp, "DD-MM-YYYYTHH:mm:ss.SSSZ").format("DD-MM-YYYY hh:mm:ss") // ); const timestamps = friendRequests.map(request => request.timestamp); console.log(timestamps, "timestamps"); await User.find({ customerId: { $in: supplierIdsToInclude } }) .limit(limit) .skip(startindex) .exec() .then((docs) => { const customerDataWithTimestamp = docs.map((doc, index) => ({ ...doc._doc, timestamp: timestamps[index], })); reply.send({ status_code: 200, data: customerDataWithTimestamp, count: docs.length, }); }) .catch((err) => { console.log(err); reply.send({ error: err }); }); } catch (err) { throw boom.boomify(err); } }; exports.getRejectCustomers = async (req, reply) => { const limit = parseInt(req.query.limit) || 100; const page = parseInt(req.query.page) || 1; const startindex = (page - 1) * limit; const supplierId = req.params.supplierId; // Assuming you have already authenticated the user and stored their ID in the request object try { const friendRequests = await FriendRequest.find({ supplierId, status: ["rejected"], }); console.log(friendRequests, supplierId, "su...."); const supplierIdsToInclude = friendRequests.map( (request) => request.customerId ); console.log(supplierIdsToInclude, "supplierIdsToInclude.."); await User.find({ customerId: { $in: supplierIdsToInclude } }) .limit(limit) .skip(startindex) .exec() .then((docs) => { reply.send({ status_code: 200, data: docs, count: docs.length }); }) .catch((err) => { console.log(err); reply.send({ error: err }); }); } catch (err) { throw boom.boomify(err); } }; exports.getconnectedCustomers = async (req, reply) => { const limit = parseInt(req.query.limit) || 100; const page = parseInt(req.query.page) || 1; const startindex = (page - 1) * limit; const supplierId = req.params.supplierId; // Assuming you have already authenticated the user and stored their ID in the request object try { const friendRequests = await FriendRequest.find({ supplierId, status: ["accepted"], }); console.log(friendRequests, supplierId, "su...."); const supplierIdsToInclude = friendRequests.map( (request) => request.customerId ); console.log(supplierIdsToInclude, "supplierIdsToInclude.."); await User.find({ customerId: { $in: supplierIdsToInclude } }) .limit(limit) .skip(startindex) .exec() .then((docs) => { reply.send({ status_code: 200, data: docs, count: docs.length }); }) .catch((err) => { console.log(err); reply.send({ error: err }); }); } catch (err) { throw boom.boomify(err); } }; // exports.uploadProfilePicture = async (req, reply) => { // try { // const supplierId = req.params.supplierId; // const picture = req.body.picture; // let profilePicture = await profilePictureSupplier.findOne({ supplierId }); // console.log(profilePicture,"profile===") // if (!profilePicture) { // profilePicture = new profilePictureSupplier({ // supplierId, // picture, // }); // } else { // profilePicture.picture = picture; // } // await profilePicture.save(); // reply.send({ message: 'Profile picture uploaded successfully' }); // } catch (error) { // reply.status(500).send({ error: error.message }); // } // }; // const multer = require('multer'); // const fs = require('fs'); // // Multer storage configuration // const storage = multer.diskStorage({ // destination: function (req, file, cb) { // // Specify the destination folder for storing uploaded files // cb(null, './uploads'); // }, // filename: function (req, file, cb) { // // Generate a unique filename for the uploaded file // cb(null, file.originalname); // }, // }); // // Multer upload configuration // const upload = multer({ storage: storage }).single('picture'); // // Handler for uploading profile picture // exports.uploadProfilePicture = async (req, res) => { // try { // upload(req, res, async (err) => { // if (err) { // return res.status(400).send({ error: 'Failed to upload profile picture' }); // } // const supplierId = req.params.supplierId; // const picture = req.file.filename; // let profilePicture = await ProfilePictureSupplier.findOne({ supplierId }); // if (!profilePicture) { // profilePicture = new ProfilePictureSupplier({ // supplierId, // picture, // }); // } else { // profilePicture.picture = picture; // } // await profilePicture.save(); // res.send({ message: 'Profile picture uploaded successfully' }); // }); // } catch (error) { // res.status(500).send({ error: error.message }); // } // }; // const { Storage } = require('@google-cloud/storage'); // const storage = new Storage({ // keyFilename :`/home/bhaskar/Downloads/arminta-tank-0f492875da39.json`, // projectId : `arminta-tank` // }); // const bucketName = 'armintaprofile_picture'; // exports.uploadProfilePicture = async (req, res) => { // try { // const supplierId = req.params.supplierId; // const file = req.body.picture; // // Check if the required properties are present // if (!file) { // return res.status(400).send({ error: 'No picture file provided' }); // } // // Upload the file to the GCS bucket // const bucket = storage.bucket(bucketName); // const fileName = `${supplierId}/${file.originalname}`; // const fileOptions = { // metadata: { // contentType: 'multipart/form-data', // }, // }; // await bucket.upload(file.path, { // destination: `armintaprofile_picture/${fileName}`, // ...fileOptions, // }); // const imageUrl = `https://storage.googleapis.com/${bucketName}/${fileName}`; // console.log(imageUrl); // let profilePicture = await ProfilePictureSupplier.findOne({ supplierId }); // console.log("profilePicture", profilePicture); // if (!profilePicture) { // profilePicture = new ProfilePictureSupplier({ // supplierId, // picture: imageUrl, // }); // } else { // profilePicture.picture = imageUrl; // } // await profilePicture.save(); // // Delete the temporary file after uploading to GCS // fs.unlinkSync(file.path); // res.send({ message: 'Profile picture uploaded successfully' }); // } catch (error) { // res.status(500).send({ error: error.message }); // } // }, // const { Storage } = require('@google-cloud/storage'); // const storage = new Storage({ // keyFilename: '/home/bhaskar/Downloads/arminta-tank-0f492875da39.json', // projectId: 'arminta-tank', // }); // const bucketName = 'armintaprofile_picture'; // exports.uploadProfilePicture = async (req, res) => { // try { // const supplierId = req.params.supplierId; // // Check if the required properties are present // if (!req.files || !req.files.picture) { // return res.status(400).send({ error: 'No picture file provided' }); // } // const file = req.files.picture; // console.log("file", file) // // Upload the file to the GCS bucket // const bucket = storage.bucket(bucketName); // const fileName = `${supplierId}/${file.name}`; // const fileOptions = { // metadata: { // contentType: file.mimetype, // }, // }; // await bucket.upload(file.tempFilePath, { // destination: fileName, // ...fileOptions, // }); // const imageUrl = `https://storage.googleapis.com/${bucketName}/${fileName}`; // console.log(imageUrl); // let profilePicture = await ProfilePictureSupplier.findOne({ supplierId }); // console.log('profilePicture', profilePicture); // if (!profilePicture) { // profilePicture = new ProfilePictureSupplier({ // supplierId, // picture: imageUrl, // }); // } else { // profilePicture.picture = imageUrl; // } // await profilePicture.save(); // // Delete the temporary file after uploading to GCS // fs.unlinkSync(file.tempFilePath); // res.send({ message: 'Profile picture uploaded successfully' }); // } catch (error) { // res.status(500).send({ error: error.message }); // } // }; const { Storage } = require('@google-cloud/storage'); const fs = require('fs'); const storage = new Storage({ keyFilename: '/home/bhaskar/Downloads/arminta-tank-3c665db761bc.json', projectId: 'arminta-tank', }); const bucketName = 'armintaprofile_picture'; // exports.uploadProfilePicture = async (req, res) => { // try { // const supplierId = req.body.supplierId; // // Check if the required properties are present // if (!req.files || !req.files.picture) { // return res.status(400).send({ error: 'No picture file provided' }); // } // const file = `/home/bhaskar/Desktop/download.jpeg`; // console.log("file", file) // console.log("supplierId", supplierId) // // Upload the file to the GCS bucket // const bucket = storage.bucket(bucketName); // const fileName = `${supplierId}/${file.name}`; // const fileOptions = { // metadata: { // contentType: file.mimetype, // }, // }; // await bucket.upload(file.tempFilePath, { // destination: fileName, // ...fileOptions, // }); // const imageUrl = `https://storage.googleapis.com/${bucketName}/${fileName}`; // let profilePicture = await profilePictureSupplier.findOne({ supplierId }); // if (!profilePicture) { // profilePicture = new profilePictureSupplier({ // supplierId, // picture: imageUrl, // }); // } else { // profilePicture.picture = imageUrl; // } // await profilePicture.save(); // // Delete the temporary file after uploading to GCS // fs.unlinkSync(file.tempFilePath); // res.send({ message: 'Profile picture uploaded successfully' }); // } catch (error) { // res.status(500).send({ error: error.message }); // } // }; // exports.uploadProfilePicture = async (req, res) => { // try { // upload(req, res, async (err) => { // if (err) { // return res.status(400).send({ error: 'Failed to upload profile picture' }); // } // const supplierId = req.params.supplierId; // const picture = req.file; // Assuming the file field in the request is named 'picture' // // Upload the file to the GCS bucket // const bucket = storage.bucket(bucketName); // const fileName = `${supplierId}/${file.originalname}`; // const fileOptions = { // metadata: { // contentType: file.mimetype, // }, // }; // await bucket.upload(filepath, { // destination: `armintaprofile_picture/${picture}`, // ...fileOptions, // }); // const imageUrl = `https://storage.googleapis.com/${bucketName}/${fileName}`; // console.log(imageUrl) // let profilePicture = await ProfilePictureSupplier.findOne({ supplierId }); // console.log("profilePicture", profilePicture) // if (!profilePicture) { // profilePicture = new ProfilePictureSupplier({ // supplierId, // picture: imageUrl, // }); // } else { // profilePicture.picture = imageUrl; // } // await profilePicture.save(); // // Delete the temporary file after uploading to GCS // fs.unlinkSync(file.path); // res.send({ message: 'Profile picture uploaded successfully' }); // }); // } catch (error) { // res.status(500).send({ error: error.message }); // } // }; // exports.uploadProfilePicture = async (req, res) => { // try { // upload(req, res, async (err) => { // if (err) { // return res.status(400).send({ error: 'Failed to upload profile picture' }); // } // const supplierId = req.params.supplierId; // const picture = req.file.filename; // Assuming the file field in the request is named 'picture' // // Upload the picture to the GCP bucket // const bucketName = 'armintaprofile_picture'; // const bucket = storage.bucket(bucketName); // const uploadPath = `armintaprofile_picture/${picture}`; // await bucket.upload(req.file.path, { // destination: uploadPath, // }); // console.log("bucket",bucket) // console.log(uploadPath, "uploadPath") // // Get the public URL of the uploaded picture // const file = bucket.file(uploadPath); // const [metadata] = await file.getMetadata(); // const pictureUrl = metadata.mediaLink; // let profilePicture = await profilePictureSupplier.findOne({ supplierId }); // if (!profilePicture) { // profilePicture = new profilePictureSupplier({ // supplierId, // picture: pictureUrl, // }); // } else { // profilePicture.picture = pictureUrl; // } // await profilePicture.save(); // res.send({ message: 'Profile picture uploaded successfully' }); // }); // } catch (error) { // res.status(500).send({ error: error.message }); // } // }; // Route for fetching profile picture data fastify.get('/api/users/profile-picture-supplier/:supplierId', async (req, res) => { try { const supplierId = req.params.supplierId; const profilePicture = await ProfilePictureSupplier.findOne({ supplierId }); if (!profilePicture) { return res.status(404).send({ error: 'Profile picture not found' }); } // Send the profile picture data as a response res.send({ picture: profilePicture.picture }); } catch (error) { res.status(500).send({ error: error.message }); } }); exports.getSuppliersForPlanSearch = async (req, reply) => { const { customerId } = req.params; const { type_of_water, capacity: requestedCapacityStr, quantity: requestedQuantityStr, frequency, start_date, end_date, // currently not used to filter suppliers // new filters radius_from, radius_to, rating_from, rating_to, price_from, price_to, pump } = req.body; // ---- helpers (kept inside as you prefer) ---- const parseFloatSafe = (v) => { const n = parseFloat((v ?? "").toString().replace(/,/g, "")); return Number.isFinite(n) ? n : NaN; }; const parseIntSafe = (v) => { const n = parseInt((v ?? "").toString().replace(/,/g, ""), 10); return Number.isFinite(n) ? n : NaN; }; const isValid = (n) => Number.isFinite(n); const inRange = (n, from, to) => (!isValid(from) || n >= from) && (!isValid(to) || n <= to); const normalizePump = (val) => { if (val == null) return undefined; const s = String(val).trim().toLowerCase(); if (["1","true","yes","y"].includes(s)) return true; if (["0","false","no","n"].includes(s)) return false; return undefined; // ignore if unknown }; const parseLatLng = (raw) => { // supports: "17.38,78.49" | {lat: 17.38, lng: 78.49} | [17.38, 78.49] if (!raw) return null; try { if (typeof raw === "string") { const parts = raw.split(",").map(x => parseFloat(x.trim())); if (parts.length === 2 && parts.every(Number.isFinite)) return { lat: parts[0], lng: parts[1] }; // try JSON const j = JSON.parse(raw); return parseLatLng(j); } if (Array.isArray(raw) && raw.length === 2) { const [lat, lng] = raw.map(Number); if (Number.isFinite(lat) && Number.isFinite(lng)) return { lat, lng }; } if (typeof raw === "object" && raw !== null) { const lat = parseFloat(raw.lat ?? raw.latitude); const lng = parseFloat(raw.lng ?? raw.lon ?? raw.longitude); if (Number.isFinite(lat) && Number.isFinite(lng)) return { lat, lng }; } } catch (_) {} return null; }; const haversineKm = (a, b) => { const R = 6371; const dLat = (b.lat - a.lat) * Math.PI / 180; const dLng = (b.lng - a.lng) * Math.PI / 180; const s1 = Math.sin(dLat/2) ** 2; const s2 = Math.cos(a.lat*Math.PI/180) * Math.cos(b.lat*Math.PI/180) * Math.sin(dLng/2) ** 2; return 2 * R * Math.asin(Math.sqrt(s1 + s2)); }; const getSupplierRating = (s) => { // adapt to whatever field you actually store const cands = [s.rating, s.avgRating, s.averageRating, s.overallRating]; const n = cands.find(x => Number.isFinite(Number(x))); return Number(n ?? NaN); }; // ---- end helpers ---- // parse inputs const requestedCapacity = parseFloatSafe(requestedCapacityStr) || 0; const requestedQuantity = parseIntSafe(requestedQuantityStr) || 0; const totalRequiredCapacity = requestedCapacity * requestedQuantity; const priceFrom = parseIntSafe(price_from); const priceTo = parseIntSafe(price_to); const ratingFrom = parseFloatSafe(rating_from); const ratingTo = parseFloatSafe(rating_to); const radiusFrom = parseFloatSafe(radius_from); const radiusTo = parseFloatSafe(radius_to); const pumpWanted = normalizePump(pump); try { // favorites + customer coords (for radius) const customer = await User.findOne({ customerId }, { favorate_suppliers: 1, googleLocation: 1, location: 1 }).lean(); const favoriteSet = new Set(customer?.favorate_suppliers || []); const customerCoords = parseLatLng(customer?.googleLocation) || parseLatLng(customer?.location); // 1) Tankers base query: by type_of_water (+ pump if requested) const tankerQuery = {}; if (type_of_water?.trim()) tankerQuery.typeofwater = type_of_water.trim(); if (pumpWanted !== undefined) { // try to match common representations tankerQuery.$or = [ { pump: pumpWanted ? { $in: [true, "1", "yes", "true", 1, "Y", "y"] } : { $in: [false, "0", "no", "false", 0, "N", "n"] } }, { pumpAvailable: pumpWanted } // if you store as boolean ]; } let tankers = await Tanker.find(tankerQuery).lean(); // 2) Price range on tanker.price if (isValid(priceFrom) || isValid(priceTo)) { tankers = tankers.filter(t => { const p = parseIntSafe(t.price); return isValid(p) && inRange(p, priceFrom, priceTo); }); } // 3) Group by supplier const supplierTankerMap = {}; for (const t of tankers) { if (!t?.supplierId) continue; (supplierTankerMap[t.supplierId] ||= []).push(t); } // 4) Capacity qualification let qualified = []; for (const [supplierId, supplierTankers] of Object.entries(supplierTankerMap)) { const totalAvail = supplierTankers.reduce((sum, tt) => sum + (parseFloatSafe(tt.capacity) || 0), 0); if (requestedCapacity > 0 && requestedQuantity > 0 && totalAvail < totalRequiredCapacity) continue; qualified.push({ supplierId, tankers: supplierTankers }); } // 5) Fetch suppliers for remaining filters (rating & radius) + flags const supplierIds = qualified.map(q => q.supplierId); const [suppliersData, acceptedReqs] = await Promise.all([ Supplier.find({ supplierId: { $in: supplierIds } }).lean(), FriendRequest.find( { customerId, supplierId: { $in: supplierIds }, status: "accepted" }, { supplierId: 1, _id: 0 } ).lean() ]); // Build quick lookup const supplierById = new Map(suppliersData.map(s => [s.supplierId, s])); const connectedSet = new Set(acceptedReqs.map(r => r.supplierId)); // 6) Apply rating & radius filters on suppliers if (isValid(ratingFrom) || isValid(ratingTo) || (isValid(radiusFrom) || isValid(radiusTo))) { qualified = qualified.filter(q => { const s = supplierById.get(q.supplierId); if (!s) return false; // rating if (isValid(ratingFrom) || isValid(ratingTo)) { const r = getSupplierRating(s); if (!isValid(r) || !inRange(r, ratingFrom, ratingTo)) return false; } // radius (requires coords on both sides) if ((isValid(radiusFrom) || isValid(radiusTo)) && customerCoords) { const supCoords = parseLatLng(s.googleLocation) || parseLatLng(s.location) || parseLatLng(s.addressLocation); if (!supCoords) return false; const distKm = haversineKm(customerCoords, supCoords); if (!inRange(distKm, radiusFrom, radiusTo)) return false; } return true; }); } // 7) Build response with flags + optional 'requestedBooking' flag const suppliers = []; for (const q of qualified) { const s = supplierById.get(q.supplierId); if (!s) continue; const isConnected = connectedSet.has(q.supplierId); const isFavorite = favoriteSet.has(q.supplierId); // If you want to expose a hint that user has already sent a single-day request earlier const requestedBookingRecord = await RequestedBooking.findOne({ customerId, "requested_suppliers.supplierId": q.supplierId }, { time: 1 }).lean(); suppliers.push({ supplier: s, tankers: q.tankers, isConnected, isFavorite, requestedBooking: requestedBookingRecord ? { status: true, time: requestedBookingRecord.time } : { status: false } }); } return reply.send({ status_code: 200, suppliers }); } catch (err) { console.error(err); return reply.send({ status_code: 500, message: "Something went wrong", error: err.message }); } }; // controllers/validationHandler.js (add below the previous handler) // exports.createRequestedPlanBooking = async (req, reply) => { // const { // customerId, // type_of_water, // capacity, // quantity, // start_date, // end_date, // time, // frequency, // "daily" | "weekly_once" | "weekly_twice" | "weekly_thrice" | "weekly" // weekly_count, // used only if frequency === "weekly" // requested_suppliers // } = req.body; // // helpers inside function (as you prefer) // const parseCapacity = (v) => parseFloat((v || "0").toString().replace(/,/g, "")) || 0; // const parseIntSafe = (v) => parseInt((v || "0").toString().replace(/,/g, ""), 10) || 0; // const toISODate = (d) => d.toISOString().slice(0, 10); // const mkUTCDate = (yyyy_mm_dd) => { // const [y, m, d] = (yyyy_mm_dd || "").split("-").map(Number); // return new Date(Date.UTC(y, (m || 1) - 1, d || 1)); // }; // const normalizeWeeklyCount = (freq, wc) => { // if (freq === "weekly_once") return 1; // if (freq === "weekly_twice") return 2; // if (freq === "weekly_thrice") return 3; // if (freq === "weekly") return wc || 1; // return 1; // }; // const computeWeeklyDOWs = ({ anchorDow, weeklyCount }) => { // if (weeklyCount === 1) return [anchorDow]; // if (weeklyCount === 2) return [anchorDow, (anchorDow + 3) % 7]; // if (weeklyCount === 3) return [anchorDow, (anchorDow + 2) % 7, (anchorDow + 4) % 7]; // return [anchorDow]; // }; // const generateDates = ({ frequency, start_date, end_date, weekly_count }) => { // const start = mkUTCDate(start_date); // const end = mkUTCDate(end_date); // if (isNaN(start) || isNaN(end)) throw new Error("Invalid start_date or end_date"); // if (end < start) throw new Error("end_date must be after or equal to start_date"); // // ~3 months cap // const maxMs = 92 * 24 * 60 * 60 * 1000; // if ((end - start) > maxMs) throw new Error("Range exceeds 3 months"); // const out = []; // if (frequency === "daily") { // for (let d = new Date(start); d <= end; d.setUTCDate(d.getUTCDate() + 1)) { // out.push(toISODate(d)); // } // return out; // } // if (frequency.startsWith("weekly") || frequency === "weekly") { // const wc = normalizeWeeklyCount(frequency, weekly_count); // const dows = computeWeeklyDOWs({ anchorDow: start.getUTCDay(), weeklyCount: wc }); // const set = new Set(dows); // for (let d = new Date(start); d <= end; d.setUTCDate(d.getUTCDate() + 1)) { // if (set.has(d.getUTCDay())) out.push(toISODate(d)); // } // return out; // } // throw new Error("Unsupported frequency"); // }; // try { // if (!customerId || !type_of_water || !capacity || !quantity || // !start_date || !end_date || !time || !frequency || !requested_suppliers) { // return reply.code(400).send({ // status_code: 400, // message: "Missing required fields" // }); // } // const cap = parseCapacity(capacity); // const qty = parseIntSafe(quantity); // const total_required_capacity = cap * qty; // const dates = generateDates({ frequency, start_date, end_date, weekly_count }); // if (dates.length === 0) { // return reply.code(400).send({ status_code: 400, message: "No dates generated for given inputs" }); // } // const doc = new RecurringRequestedBooking({ // customerId, // type_of_water, // capacity, // quantity, // total_required_capacity, // frequency, // weekly_count: normalizeWeeklyCount(frequency, weekly_count), // start_date, // end_date, // time, // dates, // requested_suppliers, // status: "pending" // }); // await doc.save(); // return reply.send({ // status_code: 200, // message: "Plan requested booking created successfully", // count: dates.length, // dates, // data: doc // }); // } catch (err) { // console.error(err); // return reply.code(500).send({ // status_code: 500, // message: "Something went wrong while saving", // error: err.message // }); // } // }; // controllers/plan.controller.js //const RecurringRequestedBooking = require("../models/RecurringRequestedBooking"); // ---------- Helpers ---------- const MONTHS = { jan: 0, feb: 1, mar: 2, apr: 3, may: 4, jun: 5, jul: 6, aug: 7, sep: 8, oct: 9, nov: 10, dec: 11, }; const parseCapacity = (v) => parseFloat((v ?? "0").toString().replace(/,/g, "")) || 0; const parseIntSafe = (v) => parseInt((v ?? "0").toString().replace(/,/g, ""), 10) || 0; const toISODate = (d) => d.toISOString().slice(0, 10); /** * Accepts: * - "YYYY-MM-DD" * - "DD-MMM-YYYY" * - "DD-MMM-YYYY - HH:mm" (time portion ignored for date gen) * Returns a Date in UTC midnight of that calendar day. */ const mkUTCDate = (input) => { if (!input || typeof input !== "string") throw new Error("Invalid date string"); const s = input.trim(); // ISO: YYYY-MM-DD (optionally with time, but we only take the first three parts) const iso = s.match(/^(\d{4})-(\d{2})-(\d{2})/); if (iso) { const y = Number(iso[1]); const m = Number(iso[2]) - 1; const d = Number(iso[3]); return new Date(Date.UTC(y, m, d)); } // D-MMM-YYYY (optional " - HH:mm") const mmm = s.match(/^(\d{1,2})-([A-Za-z]{3})-(\d{4})(?:\s*-\s*(\d{1,2}):(\d{2}))?$/); if (mmm) { const d = Number(mmm[1]); const mon = MONTHS[mmm[2].toLowerCase()]; const y = Number(mmm[3]); if (mon == null) throw new Error("Invalid month abbreviation in date"); return new Date(Date.UTC(y, mon, d)); } throw new Error("Unsupported date format. Use YYYY-MM-DD or DD-MMM-YYYY (- HH:mm)."); }; const normalizeWeeklyCount = (freq, wc) => { if (freq === "weekly_once") return 1; if (freq === "weekly_twice") return 2; if (freq === "weekly_thrice") return 3; if (freq === "weekly") return wc || 1; return 1; }; const computeWeeklyDOWs = ({ anchorDow, weeklyCount }) => { if (weeklyCount === 1) return [anchorDow]; if (weeklyCount === 2) return [anchorDow, (anchorDow + 3) % 7]; if (weeklyCount === 3) return [anchorDow, (anchorDow + 2) % 7, (anchorDow + 4) % 7]; return [anchorDow]; }; const generateDates = ({ frequency, start_date, end_date, weekly_count }) => { const start = mkUTCDate(start_date); const end = mkUTCDate(end_date); if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) { throw new Error("Invalid start_date or end_date"); } if (end < start) throw new Error("end_date must be after or equal to start_date"); // ~3 months cap const maxMs = 92 * 24 * 60 * 60 * 1000; if ((end - start) > maxMs) throw new Error("Range exceeds 3 months"); const out = []; if (frequency === "daily") { for (let d = new Date(start); d <= end; d.setUTCDate(d.getUTCDate() + 1)) { out.push(toISODate(d)); } return out; } if (frequency.startsWith("weekly") || frequency === "weekly") { const wc = normalizeWeeklyCount(frequency, weekly_count); const dows = computeWeeklyDOWs({ anchorDow: start.getUTCDay(), weeklyCount: wc }); const set = new Set(dows); for (let d = new Date(start); d <= end; d.setUTCDate(d.getUTCDate() + 1)) { if (set.has(d.getUTCDay())) out.push(toISODate(d)); } return out; } throw new Error("Unsupported frequency"); }; const ensureRequestedSuppliers = (arr) => { const inArr = Array.isArray(arr) ? arr : []; return inArr.map((x) => ({ supplierId: x?.supplierId ?? "", quoted_amount: typeof x?.quoted_amount === "number" ? x.quoted_amount : 0, time: x?.time ?? null, status: x?.status ?? "pending", })); }; // ---------- Controller ---------- exports.createRequestedPlanBooking = async (req, reply) => { try { const { customerId, type_of_water, capacity, quantity, start_date, end_date, time, frequency, // "daily" | "weekly_once" | "weekly_twice" | "weekly_thrice" | "weekly" weekly_count, // used only if frequency === "weekly" requested_suppliers } = req.body || {}; // Basic presence check const missing = [ ["customerId", customerId], ["type_of_water", type_of_water], ["capacity", capacity], ["quantity", quantity], ["start_date", start_date], ["end_date", end_date], ["time", time], ["frequency", frequency], ["requested_suppliers", requested_suppliers], ].filter(([k, v]) => v == null || (typeof v === "string" && v.trim() === "")); if (missing.length) { return reply.code(400).send({ status_code: 400, message: `Missing required fields: ${missing.map(([k]) => k).join(", ")}` }); } // Validate frequency early const ALLOWED_FREQ = new Set(["daily", "weekly_once", "weekly_twice", "weekly_thrice", "weekly"]); if (!ALLOWED_FREQ.has(frequency)) { return reply.code(400).send({ status_code: 400, message: "Invalid frequency. Allowed: daily, weekly_once, weekly_twice, weekly_thrice, weekly" }); } // Parse numbers const cap = parseCapacity(capacity); const qty = parseIntSafe(quantity); const total_required_capacity = cap * qty; if (cap <= 0 || qty <= 0) { return reply.code(400).send({ status_code: 400, message: "capacity and quantity must be positive numbers" }); } // Build dates let dates; try { dates = generateDates({ frequency, start_date, end_date, weekly_count }); } catch (e) { return reply.code(400).send({ status_code: 400, message: e.message || "Invalid dates" }); } if (!Array.isArray(dates) || dates.length === 0) { return reply.code(400).send({ status_code: 400, message: "No dates generated for the given inputs" }); } // Suppliers normalization const suppliers = ensureRequestedSuppliers(requested_suppliers); if (suppliers.length === 0) { return reply.code(400).send({ status_code: 400, message: "requested_suppliers must contain at least one supplier" }); } const doc = new RecurringRequestedBooking({ customerId, type_of_water, capacity, quantity, total_required_capacity, frequency, weekly_count: normalizeWeeklyCount(frequency, weekly_count), start_date, end_date, time, dates, requested_suppliers: suppliers, status: "pending" }); await doc.save(); return reply.send({ status_code: 200, message: "Plan requested booking created successfully", count: dates.length, dates, data: doc }); } catch (err) { console.error(err); return reply.code(500).send({ status_code: 500, message: "Something went wrong while saving", error: err.message }); } };