From 0742bc646976b12c3bce1054a2823b00a6185360 Mon Sep 17 00:00:00 2001 From: Bhaskara Kishore Date: Tue, 14 Mar 2023 11:44:13 +0530 Subject: [PATCH] Supplier Schema and signup,signin --- src/controllers/supplierController.js | 150 +++++++++ src/handlers/supplierHandler.js | 467 ++++++++++++++++++++++++++ src/index.js | 3 + src/models/supplier.js | 89 +++++ src/routes/supplierRoute.js | 99 ++++++ 5 files changed, 808 insertions(+) create mode 100644 src/controllers/supplierController.js create mode 100644 src/handlers/supplierHandler.js create mode 100644 src/models/supplier.js create mode 100644 src/routes/supplierRoute.js diff --git a/src/controllers/supplierController.js b/src/controllers/supplierController.js new file mode 100644 index 00000000..bfd1778a --- /dev/null +++ b/src/controllers/supplierController.js @@ -0,0 +1,150 @@ +const fastify = require("fastify")({ + logger: true, + }); +const boom = require("boom"); +const customJwtAuth = require("../customAuthJwt"); + +const bcrypt = require("bcrypt"); +const saltRounds = 10; + + + +//Get the data models +const { Supplier ,ProfilePicture, generateSupplierId} = require('../models/supplier'); + + +async function bcryptPassword(password) { + encryptedPwd = bcrypt.hash(password, saltRounds); + return encryptedPwd; +} + +async function bcryptComparePassword(pwd, encpassword) { + isSame = bcrypt.compare(pwd, encpassword); + return isSame; +} + + + +//Supplier Login Controller + +exports.loginSupplier = async (req) => { + try { + const phone = req.body.phone; + const password = req.body.password; + + const supplier = await Supplier.findOne({ phone: phone }); + // compare supplier password with what is supplied + if (supplier) { + isSame = await bcryptComparePassword( + password, + supplier.services.password.bcrypt + ); + // if password supplied matches return object + if (isSame) { + return { same: true, supplier: supplier }; + } else { + return { same: false }; + } + } else { + return { same: false }; + } + } catch (err) { + throw boom.boomify(err); + } + }; + + + + exports.addSupplier = async (req, reply) => { + try { + // await resetCounter();//to set customer id back to 0 + var s_id = await generateSupplierId() + var building= ((req.body.suppliername).slice(0, 3)).toUpperCase(); + var supplier_id = `AWS${building}${s_id}` + // console.log("This is the reply in the handler after the validations", reply); + s_data = { + supplierId: supplier_id, + suppliername: req.body.suppliername, + emails: req.body.emails, + password: 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_adress: req.body.office_adress, + zip: req.body.zip, + }, + }; + + + var supplier = new Supplier(s_data); + + //password is not at the top level in the collection. + supplierpass = req.body.password; + + // If fields are sent via form encoding , capture the fields and assign them to the user Object. + + checkFormEncoding = isSupplierFormUrlEncoded(req); + if (checkFormEncoding.isSupplierFormUrlEncoded) { + suppliertobeInserted = checkFormEncoding.supplier; + console.log("thsi true url string"); + supplier.suppliername = suppliertobeInserted.suppliername; + supplier.firstName = suppliertobeInserted.firstName; + supplier.lastName = suppliertobeInserted.lastName; + supplier.phone = suppliertobeInserted.phone; + supplier.emails = suppliertobeInserted.emails; + supplier.passsword = suppliertobeInserted.password; + supplier.supplierId = suppliertobeInserted.supplier_id; + supplier.office_adress = suppliertobeInserted.office_adress; + supplier.alternativeContactNumber = suppliertobeInserted.alternativeContactNumber; + + } + + console.log("---------checkurl ecnoded string-----------------------"); + + // Store hash in your password DB. + hash = await bcryptPassword(supplierpass); + + if (hash) { + supplier.services.password.bcrypt = hash; + if (req.body.role) { + supplier.profile.role = req.body.role; + console.log("******************************************************"); + console.log(supplier); + } else { + role = ["supplier"]; + supplier.profile.role = role; + } + + insertedSupplier = await supplier.save(); + console.log(insertedSupplier); + if (insertedSupplier) { + // Prepare supplier object and wrap it inside the armintatankdata + var retSupplier = { + armintatankdata: { + suppliername: insertedSupplier.suppliername, + phone: insertedSupplier.phone, + supplierId: insertedSupplier.supplierId, + office_adress: insertedSupplier.office_adress, + emails: [ + { + email: insertedSupplier.emails[0].email, + }, + ], + profile: insertedSupplier.profile, + }, + status_code: 200, + }; + + return retSupplier; + } + } + } catch (err) { + throw boom.boomify(err); + } + }; \ No newline at end of file diff --git a/src/handlers/supplierHandler.js b/src/handlers/supplierHandler.js new file mode 100644 index 00000000..d69cf079 --- /dev/null +++ b/src/handlers/supplierHandler.js @@ -0,0 +1,467 @@ +//Get the data models +const { Supplier } = require('../models/supplier'); +const { ProfilePicture } = 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 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.address1, + 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 ProfilePicture.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.address1, + phoneVerified: loginObject.supplier.phoneVerified, + oneTimePasswordSetFlag: loginObject.supplier.oneTimePasswordSetFlag, + type: loginObject.supplier.profile.role, + 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.address1, + phoneVerified: loginObject.supplier.phoneVerified, + oneTimePasswordSetFlag: loginObject.supplier.oneTimePasswordSetFlag, + type: loginObject.supplier.profile.role, + typeasobj: stringToJsonObject, + }, + }); + } + + } + } else { + error = { + simplydata: { + error: true, + code: 400, + message: "Invalid SupplierId , Password supplied", + }, + }; + reply.send(error); + } +} + + + + +// 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 # " + + user.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' }) + + } + + diff --git a/src/index.js b/src/index.js index 3cbf5040..d0068a2a 100644 --- a/src/index.js +++ b/src/index.js @@ -298,6 +298,7 @@ fastify.get("/api/reset_token/:customerId", { }, { expiresIn: "30d" } ); + reply.send({ access_token: token, customerId: get_user.customerId }); } catch (err) { console.log(err); @@ -334,6 +335,8 @@ fastify.register(require("./routes/usersRoute")); fastify.register(require("./routes/tanksRoute")); fastify.register(require("./routes/createConnectionsRoute")); fastify.register(require("./routes/tankersRoute.js")); +fastify.register(require("./routes/supplierRoute")) + // Testing route allows for retrieving a user by phone so one can see what is the phone verification code sent for a given user's phone // Also allows deletion of a user with a given phone number diff --git a/src/models/supplier.js b/src/models/supplier.js new file mode 100644 index 00000000..ab2a34e5 --- /dev/null +++ b/src/models/supplier.js @@ -0,0 +1,89 @@ +const mongoose = require("mongoose"); + +const Schema = mongoose.Schema; +const ObjectId = Schema.Types.ObjectId; + +const code = Math.floor(100000 + Math.random() * 900000); + +const { User,Counter, generateBookingId,resetCounter,generateCustomerId,ProfilePicture} = require('../models/User') + + + + +const generateSupplierId = async () => { + var result = await Counter.findOneAndUpdate( + { _id: 'supplier_id' }, + { $inc: { seq: 1 } }, + { upsert: true, new: true } + ); + + return result.seq; +}; + +const supplierSchema = new mongoose.Schema( + { + suppliername: { type: String }, + phone: { type: String, unique: true, trim: true }, + supplierId: {type : String, default: null}, + phoneVerified: { type: Boolean, default: false }, + phoneVerificationCode: { type: Number, default: 11111 }, + passwordResetCode: { type: Number, default: code }, + oneTimePasswordSetFlag: { type: Boolean, default: false }, + emails: [{ email: String, verified: { type: Boolean, default: false } }], + services: { password: { bcrypt: String } }, + + profile: { + role: [{ type: String, default: "supplier" }], + firstName: { type: String, default: null }, + lastName: { type: String, default: null }, + contactNumber: { type: String, default: null }, + alternativeContactNumber : { type: String, default: null }, + office_address: { type: String, default: null }, + city: { type: String, default: null }, + state: { type: String, default: null }, + country: { type: String, default: null }, + zip: { type: String, default: null }, + }, + status: { + type: String, + enum: ['accept', 'pending', 'reject'], + default: 'pending' + }, + + currentGPS: { + // It's important to define type within type field, because + // mongoose use "type" to identify field's object type. + + gpsType: { type: String, default: "Point" }, + // Default value is needed. Mongoose pass an empty array to + // array type by default, but it will fail MongoDB's pre-save + // validation. + coordinates: { type: [Number], default: [0, 0] }, + }, + + isActive: Boolean, + tenantId: ObjectId, + createdAt: { + type: Date, + default: function () { + return Date.now(); + }, + }, + createdBy: ObjectId, + updatedAt: { + type: Date, + default: function () { + return Date.now(); + }, + }, + updatedBy: ObjectId, + }, + { versionKey: false } + ); + + + +const Supplier = mongoose.model("Supplier", supplierSchema); + + +module.exports = { Supplier, generateSupplierId} diff --git a/src/routes/supplierRoute.js b/src/routes/supplierRoute.js new file mode 100644 index 00000000..e90f8d38 --- /dev/null +++ b/src/routes/supplierRoute.js @@ -0,0 +1,99 @@ +const fastify = require("fastify"); +const supplierController = require("../controllers/supplierController"); +const validationHandler = require("../handlers/supplierHandler"); + + +module.exports = function (fastify, opts, next) { + + fastify.post("/api/supplierlogin", { + schema: { + description: "This is for Login Supplier", + tags: ["Supplier-Data"], + summary: "This is for Login Supplier", + body: { + type: "object", + required: ["phone", "password"], + properties: { + phone: { type: "string" }, + password: { type: "string" }, + }, + }, + }, + handler: validationHandler.loginSupplier, + }); + + + fastify.route({ + method: "POST", + url: "/api/supplierlogout", + schema: { + description: "This is for Supplier logout", + tags: ["Supplier-Data"], + summary: "This is for Supplier logout", + params: { + type: "object", + properties: { + supplierId: { + type: "string", + description: "supplierId", + }, + }, + }, + }, + // preHandler: fastify.auth([fastify.authenticate]), + handler: validationHandler.logoutsupplier, + // onResponse: (request,reply) => {validationHandler.resetPassword(request,reply)} + }); + + + + + fastify.route({ + method: "POST", + url: "/api/suppliers", + schema: { + tags: ["Supplier-Data"], + description: "This is for cretae New supplier", + summary: "This is for Create New supplier.", + body: { + type: "object", + properties: { + suppliername: { type: "string" }, + phone: { type: "string" }, + alternativeContactNumber : { type : "string" }, + password: { type: "string" }, + emails: { + type: "array", + maxItems: 2, + items: { + type: "object", + properties: { + email: { type: "string", default: null }, + }, + }, + }, + office_address: { type: "string", default: null }, + city: { type: "string", default: null }, + state: { type: "string", default: null }, + zip: { type: "string", default: null }, + country: { type: "string", default: null }, + }, + }, + security: [ + { + basicAuth: [], + }, + ], + }, + preHandler: [ + validationHandler.fieldCheck, + validationHandler.verifySupplier, + // validationHandler.validatePhoneFormat, + validationHandler.validateEmailFormat, + ], + handler: supplierController.addSupplier, + }); + + next(); +} +