diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6bba59 --- /dev/null +++ b/.gitignore @@ -0,0 +1,130 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* diff --git a/Controllers/Clients.Controllers.js b/Controllers/Clients.Controllers.js new file mode 100644 index 0000000..604d477 --- /dev/null +++ b/Controllers/Clients.Controllers.js @@ -0,0 +1,106 @@ +const User = require('../Modals/Schema/UserSch') +const Token = require('../Modals/Schema/token') +const {HTTP_STATUS_CODES,RESPONSE_MESSAGES} = require('../config/constants') +const {sendEmail} = require('../Helpers/mailer') +const {sendEmailVerification} = require('../Helpers/mailverify') +const {hashPassword,comparePassword} = require('../Helpers/Hashing') +const {generateToken} = require('../Helpers/JWT') +const crypto = require('crypto') + +//Register +exports.registerUser = async (req,res) => { + try{ + const {username,email,password,age,clientAddress,country,sex,phoneNumber,bio,verified} = req.body + const user = await User.findOne({email :email}) + if(user){ + return res.status(HTTP_STATUS_CODES.BAD_REQUEST).json({message: 'User already exists'}) + } + const hashedPassword = await hashPassword(password) + const newUser = new User({ + username, + email : email, + password : hashedPassword, + age , + clientAddress, + country , + sex , + phoneNumber , + bio, + verified + }) + const result = await newUser.save() + const token = await new Token({ + userId : newUser._id, + token : crypto.randomBytes(32).toString("hex") + }).save() + const sendTokenMail = await sendEmailVerification(newUser.email,token.userId,token.token) //function to verify email client + if(sendTokenMail){ + sendEmail(newUser.email,newUser.username) + } + res.status(HTTP_STATUS_CODES.OK).send(RESPONSE_MESSAGES.USER_CREATED_SUCCESS) + }catch(error){ + res.status(HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR).send('Server Error'); + } +} + +//To Verify Email Address +exports.verifyEmail = async (req,res) => { + try{ + const token = req.params.token + const userToken = await Token.findOne({ + userId : req.params.id, + token : token + }) + + if(!userToken){ + return res.status(HTTP_STATUS_CODES.BAD_REQUEST).send({message: "Your verification link may have expired."}) + }else{ + const user = await User.findOne({_id : req.params.id}) + if(!user){ + return res.status(HTTP_STATUS_CODES.UNAUTHORIZED).send({message: "We were enable to find a user for this verification.Signup!"}) + }else if(user.verified){ + return res.status(HTTP_STATUS_CODES.OK).send({message: "User has been already verified.Please login"}) + }else{ + const updated = await User.updateOne({verified:true}) + + if(!updated){ + return res.status(HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR).send({message: "error"}) + }else{ + return res.status(HTTP_STATUS_CODES.OK).send({message : "Your account has been successfuly verified"}) + } + } + } + }catch(error){ + res.status(HTTP_STATUS_CODES.BAD_REQUEST).send("An error occurred") + } +} + +//Login +exports.userLogin = async (req,res) => { + try{ + const {email,password} = req.body + const user = await User.findOne({email}) + if(!user){ + return res.status(HTTP_STATUS_CODES.BAD_REQUEST).json(RESPONSE_MESSAGES.INVALID_CREDENTIALS) + } + + const checked =await comparePassword(password,user.password) + if(!checked) return res.status(HTTP_STATUS_CODES.BAD_REQUEST).json({message:"Incorrect Password"}) + + const token = await generateToken({user}) + res.status(HTTP_STATUS_CODES.OK).cookie('tokenAuth',token).send('User logged successfuly') + }catch(error){ + res.status(HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR).send('Server Error'); + } + +} + +//Logout +exports.userLogout = async (req,res) => { + try{ + res.clearCookie('tokenAuth') + res.send("Logout successfuly") + }catch(error){ + res.status(HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR).send('Server Error') + } +} \ No newline at end of file diff --git a/Controllers/Orders.Controllers.js b/Controllers/Orders.Controllers.js new file mode 100644 index 0000000..ba3c995 --- /dev/null +++ b/Controllers/Orders.Controllers.js @@ -0,0 +1,91 @@ +const Order = require("../Modals/Schema/OrderSchema") +const {HTTP_STATUS_CODES,RESPONSE_MESSAGES} = require('../config/constants') + +//View all orders page +exports.allOrders = async (rea,res) => { + try{ + const orders = await Order.find({}) + res.status(HTTP_STATUS_CODES.OK).json({ + message : RESPONSE_MESSAGES.ORDER_CREATED_SUCCESS , + data : orders + }) + }catch(error){ + res.status(HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR).send(RESPONSE_MESSAGES.INTERNAL_SERVER_ERROR) + } +} + +//Order details page +exports.orderDetails = async (req,res) => { + try{ + const id = req.params.id + const order = await Order.findById(id) + if(!order){ + res.status(HTTP_STATUS_CODES.NOT_FOUND).send(RESPONSE_MESSAGES.ORDER_NOT_FOUND) + } + const details = { + Restaurant : order.restaurant , + Address : order.deliveryAddress , + Status : order.status , + Total : order.totalPrice , + PayementMethod : order.paymentMethod + } + res.status(HTTP_STATUS_CODES.OK).json(details) + }catch(error){ + res.status(HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR).send(RESPONSE_MESSAGES.INTERNAL_SERVER_ERROR) + } +} + +//Update order +exports.updateOrder = async (req,res) => { + try { + const id = req.params.id + const updates = req.body + + const updatedOrder = await Order.findByIdAndUpdate(id, updates, { new: true }); + + if (!updatedOrder) { + return res.status(HTTP_STATUS_CODES.NOT_FOUND).json(RESPONSE_MESSAGES.ORDER_NOT_FOUND) + } + + res.json({ + message: RESPONSE_MESSAGES.ORDER_UPDATED_SUCCESS , + data: updatedOrder + }) + } catch (error) { + res.status(HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR).json(RESPONSE_MESSAGES.INTERNAL_SERVER_ERROR) + } +} + +//Delete Order +exports.deleteOrder = async (req,res) => { + try{ + const id = req.params.id + + const order = await Order.findByIdAndDelete(id) + + if(!order){ + res.status(HTTP_STATUS_CODES.NOT_FOUND).json(RESPONSE_MESSAGES.ORDER_NOT_FOUND) + } + + res.status(HTTP_STATUS_CODES.OK).json(RESPONSE_MESSAGES.ORDER_CANCELED_SUCCESS) + }catch(error){ + res.status(HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR).json(RESPONSE_MESSAGES.INTERNAL_SERVER_ERROR) + } +} + +//Track orders page +exports.trackOrder = async (req,res) => { + try{ + const {id} = req.query + const order = await Order.findById(id) + if(!order){ + res.status(HTTP_STATUS_CODES.NOT_FOUND).json(RESPONSE_MESSAGES.ORDER_NOT_FOUND) + } + const trackingInfo = { + status : order.status + } + res.status(HTTP_STATUS_CODES.OK).json(trackingInfo) + }catch(error){ + res.status(HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR).send(RESPONSE_MESSAGES.INTERNAL_SERVER_ERROR) + } +} \ No newline at end of file diff --git a/Controllers/Profile.Controllers.js b/Controllers/Profile.Controllers.js new file mode 100644 index 0000000..bdeda06 --- /dev/null +++ b/Controllers/Profile.Controllers.js @@ -0,0 +1,84 @@ +const User = require('../Modals/Schema/UserSch') +const {HTTP_STATUS_CODES,RESPONSE_MESSAGES} = require('../config/constants') +const {hashPassword,comparePassword} = require('../Helpers/Hashing') + +exports.profileUser = async (req,res) => { + try{ + const id = req.user.user._id + const user = await User.findById(id) + if(!user) return res.status(HTTP_STATUS_CODES.NOT_FOUND).json(RESPONSE_MESSAGES.USER_NOT_FOUND) + + const fields = { + username : user.username, + email : user.email, + age : user.age, + country : user.country, + sex : user.sex, + phoneNumber : user.phoneNumber, + bio : user.bio + } + res.status(HTTP_STATUS_CODES.OK).json(fields) + + }catch(error){ + res.status(HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR).send(RESPONSE_MESSAGES.INTERNAL_SERVER_ERROR); + } +} + + +exports.UpdateProfile = async (req, res) => { + try { + const id = req.user.user._id; + const { username, email, password, age, country, sex, phoneNumber, bio } = req.body; + + const userfound = await User.findById(id); + if (!userfound) { + return res.status(HTTP_STATUS_CODES.NOT_FOUND).json(RESPONSE_MESSAGES.USER_NOT_FOUND); + } + + if (password) { + const check = await comparePassword(password, userfound.password); + if (check) { + return res.json({ message: 'Same password' }); + } + } + + let hashedPassword; + if (password) { + hashedPassword = await hashPassword(password); + } + + const updateFields = { + username, + email, + age, + country, + sex, + phoneNumber, + bio + }; + + if (hashedPassword) { + updateFields.password = hashedPassword; + } + + const updatedUser = await User.findByIdAndUpdate(id, updateFields, { new: true }); + + res.json({ + message: 'Profile updated successfully', + }); + } catch (error) { + res.status(HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR).json(RESPONSE_MESSAGES.INTERNAL_SERVER_ERROR); + } +} + +exports.deleteProfile = async (req,res) => { + try { + const id= req.user.user._id + const user = await User.deleteOne({_id : id}) + res.json({ + message: 'Profile deleted successfully', + }); + }catch (err) { + res.status(HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR).json(RESPONSE_MESSAGES.INTERNAL_SERVER_ERROR); + } +} \ No newline at end of file diff --git a/Helpers/Hashing.js b/Helpers/Hashing.js index e69de29..096ee6c 100644 --- a/Helpers/Hashing.js +++ b/Helpers/Hashing.js @@ -0,0 +1,12 @@ +const bcrypt = require('bcrypt') + +class Hashing{ + async hashPassword(password,salt=10){ + return await bcrypt.hash(password,salt); + } + async comparePassword(password,hashedPassword){ + return await bcrypt.compare(password,hashedPassword); + } +} + +module.exports = new Hashing \ No newline at end of file diff --git a/Helpers/JWT.js b/Helpers/JWT.js index e69de29..1c2e633 100644 --- a/Helpers/JWT.js +++ b/Helpers/JWT.js @@ -0,0 +1,9 @@ +const jwt = require('jsonwebtoken'); + +exports.generateToken = async (data)=>{ + return jwt.sign(data, process.env.SECRETKEY, {expiresIn: '1h'}) +} + +exports.verifyToken = (token)=>{ + return jwt.verify(token, process.env.SECRETKEY) +} \ No newline at end of file diff --git a/Helpers/mailer.js b/Helpers/mailer.js new file mode 100644 index 0000000..b438eb9 --- /dev/null +++ b/Helpers/mailer.js @@ -0,0 +1,30 @@ +const nodemailer = require('nodemailer') +require('dotenv').config() + +exports.sendEmail = async (mail,username) => { + const transporter = nodemailer.createTransport({ + service: "gmail", + auth: { + user: process.env.EMAIL, + pass: process.env.PASSWORD + } + }); + + const mailOptions = { + from: process.env.EMAIL, + to: mail, + subject: "Welcome To our platform", + html : `

Welcome ${username}


Thank you for registering

` + }; + + transporter.sendMail(mailOptions, function(error, info) { + if (error) { + console.log(error); + res.send('Error'); + } else { + console.log("Email sent: " + info.response); + res.send("Success"); + } + }); +} + \ No newline at end of file diff --git a/Helpers/mailverify.js b/Helpers/mailverify.js new file mode 100644 index 0000000..306d8d5 --- /dev/null +++ b/Helpers/mailverify.js @@ -0,0 +1,31 @@ +const nodemailer = require('nodemailer') +require('dotenv').config() + +exports.sendEmailVerification = async (mail,userId,token) => { + const transporter = nodemailer.createTransport({ + service: "gmail", + auth: { + user: process.env.EMAIL, + pass: process.env.PASSWORD + } + }); + + const mailOptions = { + from: process.env.EMAIL, + to: mail, + subject: "Verify your email address", + text : `Please click the confirmation link. + http://localhost:3000/client/verify/:${userId}/:${token}` + }; + + transporter.sendMail(mailOptions, function(error, info) { + if (error) { + console.log(error); + res.send('Error'); + } else { + console.log("Email sent: " + info.response); + res.send("Success"); + } + }); +} + \ No newline at end of file diff --git a/Middlwares/auth.middleware.js b/Middlwares/auth.middleware.js index e69de29..fe6ff70 100644 --- a/Middlwares/auth.middleware.js +++ b/Middlwares/auth.middleware.js @@ -0,0 +1,20 @@ +const {verifyToken} = require('../Helpers/JWT'); // Assuming verifyToken function is imported correctly + +exports.isAuthenticated = (req, res, next) => { + try { + const token = req.cookies.tokenAuth || null; + if (!token) { + return res.status(401).json({ message: 'Empty token. Please login to access this resource.' }); + } + + const verify = verifyToken(token); + + if (!verify) { + return res.status(401).json({ message: 'Session not found. Please login again.' }); + } + req.user= verify; + next(); + } catch (err) { + return res.status(500).json({ message: 'Internal Server Error' }); + } +} \ No newline at end of file diff --git a/Middlwares/sanitize.js b/Middlwares/sanitize.js index e69de29..9e7d4bd 100644 --- a/Middlwares/sanitize.js +++ b/Middlwares/sanitize.js @@ -0,0 +1,10 @@ +const xss = require("xss") +exports.Sanitize = (req, res, next) => { + const fields = ['username','email','age','country','sex','phoneNumber','bio'] + for(i in fields) { + if(req.body[fields[i]]) { + req.body[fields[i]] = xss(req.body[fields[i]]) + } + } + next() +} \ No newline at end of file diff --git a/Middlwares/validate.js b/Middlwares/validate.js index e69de29..d3be6d4 100644 --- a/Middlwares/validate.js +++ b/Middlwares/validate.js @@ -0,0 +1,60 @@ +const {body,validationResult} = require('express-validator') + +exports.Validate = [ + // username + body('username') + .notEmpty() + .withMessage('username is required') + .isLength({min:3}) + .withMessage('username must be at least 3 characters long'), + // email + body('email') + .notEmpty() + .withMessage('email is required') + .isLength({min:5}) + .withMessage('email must be at least 5 characters long'), + //password + body('password') + .notEmpty() + .withMessage('password is required') + .isLength({min:5}) + .withMessage('password must be at least 5 characters long'), + // age + body('age') + .notEmpty() + .withMessage('age is required') + .isNumeric() + .withMessage('age must be a boolean'), + //country + body('country') + .notEmpty() + .withMessage('country is required') + .isLength({min:3}) + .withMessage('country must be at least 3 characters long'), + //sex + body('sex') + .notEmpty() + .withMessage('sex is required') + .isLength({min:3}) + .withMessage('sex must be at least 3 characters long'), + //phoneNumber + body('phoneNumber') + .notEmpty() + .withMessage('phoneNumber is required') + .isNumeric() + .withMessage('phoneNumber must be at least 3 characters long'), + //bio + body('bio') + .notEmpty() + .withMessage('bio is required') + .isLength({min:5}) + .withMessage('bio must be at least 5 characters long'), + (req,res,next) => { + const errors = validationResult(req) + if(!errors.isEmpty()) { + return res.status(400).json({errors: errors.array()}) + } + next() + } +] +//add password \ No newline at end of file diff --git a/Modals/Schema/OrderSchema.js b/Modals/Schema/OrderSchema.js new file mode 100644 index 0000000..a1aac77 --- /dev/null +++ b/Modals/Schema/OrderSchema.js @@ -0,0 +1,73 @@ +const mongoose = require('mongoose'); + +// Define schema for order items +const orderItemSchema = new mongoose.Schema({ + name: { + type: String, + required: true + }, + quantity: { + type: Number, + required: true + }, + price: { + type: Number, + required: true + } +}); + +// Define schema for orders +const Order = new mongoose.Schema({ + client: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', // Reference to the User model + required: true + }, + restaurant: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Restaurant', // Reference to the Restaurant model + required: true + }, + menu : { + type: mongoose.Schema.Types.ObjectId, + ref: 'Menu', // Reference to the Menu model + required: true + }, + items: [orderItemSchema], // Array of order items + deliveryAddress: { + street: { type: String, required: true }, + city: { type: String, required: true }, + state: { type: String }, + postalCode: { type: String, required: true }, + country: { type: String, required: true } + }, + status: { + type: String, + enum: ['pending', 'processed', 'cancelled'], + default: 'Pending' + }, + totalPrice: { + type: Number, + required: true + }, + paymentMethod: { + type: String, + enum: ['Credit Card', 'Debit Card', 'PayPal', 'Cash on Delivery'], + required: true + }, + notes: { + type: String + }, + date: { + type: Date, + default: Date.now + }, + createdAt: { + type: Date, + default: Date.now + } +}); + +// export the Order model + +module.exports = mongoose.model('Order', Order); diff --git a/Modals/Schema/UserSch.js b/Modals/Schema/UserSch.js index e69de29..c285ff4 100644 --- a/Modals/Schema/UserSch.js +++ b/Modals/Schema/UserSch.js @@ -0,0 +1,84 @@ +const mongoose = require('mongoose') +const {hashPassword} = require('../../Helpers/Hashing') + +const User = mongoose.Schema({ + username : { + type : 'string' , + required : true + }, + email : { + type: 'string' , + required : true , + unique : true + }, + password : { + type : 'string' , + required : true + }, + age : { + type: 'Number' + }, + clientAddress: { + type : 'String', + required : true + }, + sex : { + type: 'String' , + enum: ['male', 'female'] + }, + phoneNumber : { + type: 'Number' + }, + bio : { + type: 'String' + }, + verified : { + type : 'Boolean' , + default : false + } , + googleId: { + type: String // Stocker l'ID Google de l'utilisateur + }, + googleAccessToken: { + type: String // Stocker le jeton d'authentification Google de l'utilisateur + }, + createdAt: { + type: Date, + default: Date.now + } +}) + +/* +User.pre('save', async function(next) { + // Check if password field is modified and is new + if (!this.isModified('password') || !this.isNew) { + return next(); + } + + try { + const hashedPassword = await hashPassword(this.password, 10); + this.password = hashedPassword; + next(); + } catch (error) { + next(error); + } +}); + +/* +//pre-save to hash password before saving +User.pre('save', async function(next) { + // Only hash the password if it's modified or new + if (!this.isModified('password')) { + return next(); + } + + try { + const hashedPassword = await hashPassword(this.password, 10); + this.password = hashedPassword; + next(); + } catch (error) { + next(error); + } +}) +*/ +module.exports = mongoose.model('users',User) \ No newline at end of file diff --git a/Modals/Schema/token.js b/Modals/Schema/token.js new file mode 100644 index 0000000..69d402d --- /dev/null +++ b/Modals/Schema/token.js @@ -0,0 +1,18 @@ +const mongoose = require('mongoose') +const Schema = mongoose.Schema + + +const tokenSchema = new Schema({ + userId : { + type : Schema.Types.ObjectId, + ref : 'users' , + required : true + }, + token : { + type : 'String' , + required : true + } +}) + +const Token = mongoose.model('token',tokenSchema) +module.exports = Token \ No newline at end of file diff --git a/README.md b/README.md index 2fd5a59..4b890ba 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -🍽️ Foody: Triple Registry Triple Login Website -Introduction +# 🍽️ Foody: Triple Registry Triple Login Website +## Introduction We have developed Foody, a triple registry triple login website, as a challenge to test our skills gained from a bootcamp and to create a fully functional MERN stack project. This website facilitates connections between clients, businesses, and delivery personnel, offering three distinct user experiences and corresponding code sections. Our platform integrates diverse technologies, ensuring scalability and adaptability across various industries. Website Overview @@ -33,19 +33,19 @@ Technologies Used Compression (for compressing responses) And more... -Endpoints/Routes +## Endpoints/Routes Client Pages Restaurant/Business Pages Delivery Personnel Pages General Pages -Entities/Schemas +## Entities/Schemas Client User Entity Delivery Personnel Entity Restaurant Entity -Conclusion +## Conclusion Foody provides a comprehensive solution for connecting clients, businesses, and delivery personnel in a seamless and efficient manner. With its intuitive interfaces, robust features, and scalable architecture, Foody is poised to revolutionize the food delivery industry. diff --git a/Routes/UserRoute.js b/Routes/UserRoute.js index e69de29..531e96c 100644 --- a/Routes/UserRoute.js +++ b/Routes/UserRoute.js @@ -0,0 +1,75 @@ +const express = require('express') +const passport = require('passport') + +const { + registerUser, + verifyEmail, + userLogin, + userLogout + } = require('../Controllers/Clients.Controllers') + +const {profileUser, + UpdateProfile, + deleteProfile + } = require('../Controllers/Profile.Controllers') + +const { + allOrders, + orderDetails, + updateOrder, + deleteOrder, + trackOrder +} = require('../Controllers/Orders.Controllers') + +const {Sanitize} = require('../Middlwares/sanitize') +const {Validate} = require('../Middlwares/validate') +const {isAuthenticated} = require('../Middlwares/auth.middleware') +const routes = express.Router() + +// Cette route ne sera accessible qu'aux utilisateurs authentifiés via Google OAuth 2.0 +routes.get('/profile', passport.authenticate('google', { session: false }), (req, res) => { + res.send('Bienvenue dans votre profil'); +}) + +// Route de callback pour l'authentification Google OAuth 2.0 +routes.get('/auth/google/callback', passport.authenticate('google', { failureRedirect: '/login' }), (req, res) => { + // Rediriger l'utilisateur vers la page de profil après une authentification réussie + res.redirect('/profile'); +}) + +//Registration +routes.route('/register') +.post(Sanitize,Validate,registerUser) + +//Verify email +routes.route('/verify/:id/:token') +.get(verifyEmail) +//Login +routes.route('/login') +.post(userLogin) + +//Logout +routes.route('/logout') +.post(userLogout) + +//Profile +routes.route('/profile') +.get(isAuthenticated,profileUser) +//Update +routes.route('/profile/edit') +.patch(Sanitize,Validate,isAuthenticated,UpdateProfile) +//Delete +routes.route('/profile/delete') +.delete(isAuthenticated,deleteProfile) + +//All Orders +routes.get('/orders',allOrders) +//Order details, update and delete +routes.route('/orders/:id') +.get(orderDetails) +.patch(updateOrder) +.delete(deleteOrder) +//Tracking Order +routes.get('/orders/track',trackOrder) + +module.exports = routes \ No newline at end of file diff --git a/arkx.js b/arkx.js deleted file mode 100644 index e69de29..0000000 diff --git a/config/config.js b/config/config.js index e69de29..7e50c97 100644 --- a/config/config.js +++ b/config/config.js @@ -0,0 +1,30 @@ +require('dotenv').config(); +const cloudinary = require('cloudinary').v2; +module.exports = { + server: { + // Configuration for the server + PORT: process.env.PORT || 3000, + DefaultViewEngine:'jade' ,// Define the port number for your server + }, + database: { + // Configuration for the database + uri: process.env.MONGO_URI, // URI for connecting to your MongoDB database + options: { + timestamps: true, // Enable automatic timestamps (createdAt and updatedAt) + versionKey: false, // Disable version key (e.g., __v) + useNewUrlParser: true, // Use the new URL parser for MongoDB + }, + }, + color: { + // Define color codes for console output + green: "\x1b[32m", // Green color + red: "\x1b[31m", // Red color + }, + notFoundTemplate: 404, // A template or value for "not found" responses + jwtSecretKey: process.env.SECRETKEY || 'testsecret', // Secret key for JWT authentication + }; + cloudinary.config({ + cloud_name: process.env.CLOUDINARY_CLOUD_NAME, + api_key: process.env.CLOUDINARY_API_KEY, + api_secret: process.env.CLOUDINARY_API_SECRET, + }); \ No newline at end of file diff --git a/config/constants.js b/config/constants.js index e69de29..0f3656b 100644 --- a/config/constants.js +++ b/config/constants.js @@ -0,0 +1,76 @@ +// HTTP Status Codes +exports.HTTP_STATUS_CODES = { + OK: 200, // Successful response + CREATED: 201, // Resource created + BAD_REQUEST: 400, // Invalid request data + UNAUTHORIZED: 401, // Authentication required + FORBIDDEN: 403, // Access denied + NOT_FOUND: 404, // Resource not found + CONFLICT: 409, // Conflicting resource + INTERNAL_SERVER_ERROR: 500, // Server error + }; + + // Response Messages + exports.RESPONSE_MESSAGES = { + SUCCESS: 'Success', // Generic success message + FAILURE: 'Failure', // Generic failure message + + // Menu + MENU_ITEM_NOT_FOUND: 'Menu item not found', // When a menu item is not found + NO_MENU_ITEMS_AVAILABLE : 'There are currently no menu items available', + MENU_CREATED_SUCCESS: 'Menu item created successfully', // When a menu item is created successfully + MENU_UPDATED_SUCCESS: 'Menu item updated successfully', // When a menu item is updated successfully + MENU_DELETED_SUCCESS: 'Menu item deleted successfully', // When a menu item is deleted successfully + MENU_CREATION_FAILED: 'Failed to create menu item', // When creating a menu item fails + MENU_UPDATE_FAILED: 'Failed to update menu item', // When updating a menu item fails + MENU_DELETION_FAILED: 'Failed to delete menu item', // When deleting a menu item fails + + // Order + ORDER_NOT_FOUND: 'Order not found', // When an order is not found + ORDER_CREATED_SUCCESS: 'Order placed successfully', // When an order is created successfully + ORDER_UPDATED_SUCCESS: 'Order updated successfully', // When an order is updated successfully + ORDER_CANCELED_SUCCESS: 'Order canceled successfully', // When an order is canceled successfully + ORDER_CREATION_FAILED: 'Failed to place order', // When creating an order fails + ORDER_UPDATE_FAILED: 'Failed to update order', // When updating an order fails + ORDER_CANCELLATION_FAILED: 'Failed to cancel order', // When canceling an order fails + + // User + USER_NOT_FOUND: 'User not found', // When a user is not found + USER_CREATED_SUCCESS: 'User registered successfully', // When a user is created successfully + USER_UPDATED_SUCCESS: 'User updated successfully', // When a user is updated successfully + USER_CREATION_FAILED: 'Failed to register user', // When creating a user fails + USER_UPDATE_FAILED: 'Failed to update user', // When updating a user fails + INVALID_CREDENTIALS: 'Invalid credentials', // When credentials (username/email and password) are invalid + + // General + INTERNAL_SERVER_ERROR: 'Internal server error', // When an unexpected server error occurs + VALIDATION_ERROR: 'Validation error', // When input data fails validation + EMPTY_REQUEST: 'Empty request', // When the request body is empty + NOT_FOUND: 'Not found', // When a resource is not found + }; + + // Validation Messages + exports.VALIDATION_MESSAGES = { + // Menu + MENU_NAME_REQUIRED: 'Menu item name is required', // When the menu item name is missing + MENU_DESCRIPTION_REQUIRED: 'Menu item description is required', // When the menu item description is missing + MENU_PRICE_REQUIRED: 'Menu item price is required', // When the menu item price is missing + MENU_CATEGORY_REQUIRED: 'Menu item category is required', // When the menu item category is missing + MENU_IMAGE_REQUIRED: 'Menu item image is required', // When the menu item image is missing + + // Order + ORDER_ITEMS_REQUIRED: 'At least one order item is required', // When an order does not have any items + ORDER_CUSTOMER_REQUIRED: 'Customer information is required', // When customer information is missing for an order + ORDER_PAYMENT_REQUIRED: 'Payment information is required', // When payment information is missing for an order + + // User + USER_NAME_REQUIRED: 'User name is required', // When the user name is missing + USER_EMAIL_REQUIRED: 'User email is required', // When the user email is missing + USER_PASSWORD_REQUIRED: 'User password is required', // When the user password is missing + USER_ADDRESS_REQUIRED: 'User address is required', // When the user address is missing + }; + + // Other Constants + exports.MAX_FILE_SIZE = 5 * 1024 * 1024; // Maximum allowed file size for uploads (5MB) + exports.ALLOWED_IMAGE_FORMATS = ['image/jpeg', 'image/png', 'image/gif']; // Allowed image formats for uploads + \ No newline at end of file diff --git a/config/database.js b/config/database.js index e69de29..a32ff6c 100644 --- a/config/database.js +++ b/config/database.js @@ -0,0 +1,47 @@ +//mongodb+srv://sara:M6QL7ZgULnEjtWRm@cluster0.qfvpv3o.mongodb.net/ +const { color, database } = require('./config'); // Import configuration settings +const mongoose = require('mongoose'); + +exports.connection = () => { + function connectToMongo() { + // Attempt to connect to the MongoDB database + mongoose.connect(database.uri).then( + () => { + // Connection successful + }, + (err) => { + // Connection error + console.info(color.red, 'Mongodb error', err); + } + ).catch((err) => { + console.log(color.red, 'ERROR:', err); + }); + } + + mongoose.connection.on('connected', () => { + // Event: Connected to MongoDB + console.info(color.green, 'Connected to MongoDB ✓'); + + }); + + mongoose.connection.on('reconnected', () => { + // Event: MongoDB reconnected + console.info('MongoDB reconnected!'); + }); + + mongoose.connection.on('error', (error) => { + // Event: Error in MongoDB connection + console.error(color.red, `Error in MongoDB connection: ${error}`); + mongoose.disconnect(); + }); + + mongoose.connection.on('disconnected', () => { + // Event: MongoDB disconnected + console.error(color.red, `MongoDB disconnected! Reconnecting in ${2000 / 1000}s...`); + setTimeout(() => connectToMongo(), 2000); + }); + + return { + connectToMongo, + }; +}; \ No newline at end of file diff --git a/package.json b/package.json index 21162d4..dbb4cdb 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "server.js", "scripts": { "test": "/", - "start": "node server.js" + "start": "nodemon server.js" }, "repository": { "type": "git", @@ -19,31 +19,27 @@ "homepage": "https://github.com/othmanordaski/Foody#readme", "dependencies": { "bcrypt": "^5.1.1", - "bcrypt-nodejs": "^0.0.3", - "bcryptjs": "^2.4.3", - "compression": "^1.7.4", - "connect-mongo": "^5.1.0", + "cloudinary": "^2.1.0", "cookie-parser": "^1.4.6", - "cors": "^2.8.5", + "crypto": "^1.0.1", "dotenv": "^16.4.5", "emailjs": "^4.0.3", "express": "^4.19.2", - "express-fileupload": "^1.5.0", - "express-rate-limit": "^7.2.0", "express-session": "^1.18.0", "express-validator": "^7.0.1", "helmet": "^7.1.0", - "helmet-csp": "^3.4.0", "jsonwebtoken": "^9.0.2", - "lodash": "^4.17.21", - "moment": "^2.30.1", "mongoose": "^8.2.3", - "morgan": "^1.10.0", "multer": "^1.4.5-lts.1", - "nodemailer-sendgrid-transport": "^0.2.0", - "sanitize-html": "^2.13.0", + "nodemailer": "^6.9.13", + "passport": "^0.7.0", + "passport-google-oauth20": "^2.0.0", + "uuid": "^9.0.1", "validator": "^13.11.0", - "winston": "^3.13.0", "xss": "^1.0.15" + }, + "keywords": [], + "devDependencies": { + "nodemon": "^3.1.0" } } diff --git a/server.js b/server.js index ef6d305..1e07492 100644 --- a/server.js +++ b/server.js @@ -1,18 +1,42 @@ -const express = require('express'); -require('dotenv').config(); +require('dotenv').config() -const app = express(); -const port = process.env.PORT || 3000; +const {server} = require('./config/config'); +const passport = require('./config/passport-config') +const PORT = server.PORT +const express = require('express') +const app = express() -app.use(express.json()); +const cookieParser = require('cookie-parser') -app.get('/', (req, res) => { - res.send('Hello, World!'); -}); +//Import the database connection function +const {connection} = require('./config/database') +const database = connection() + +//Middleware setup +app.use(cookieParser()) +app.use(express.json()) +app.use(express.urlencoded({extended : true})) + +// Initialize Passport +app.use(passport.initialize()) +//Connect to the MongoDb databse +database.connectToMongo() -app.listen(port, () => { - console.log('Server is running on http://localhost:'+ PORT); +const userRoutes = require('./Routes/UserRoute') +const {default : mongoose} = require('mongoose') + +app.use('/client',userRoutes) + +app.use((err, req, res, next) => { + // Handle errors and respond accordingly + console.error(err); + res.status(strings.SERVER_HTTP_VERSION_NOT_SUPPORTED_HTTP_CODE).json({ error: strings.EMPTY_REQUEST_FOR_UPDATE }); }); + +// Start the Express server and listen on port 3000 +app.listen(PORT, () => { + console.log('Listening on port' + PORT); +}); \ No newline at end of file