From 5440ea91638a41a09eba3a7df92e0ab69ed1bfab Mon Sep 17 00:00:00 2001 From: NOOB-3301 Date: Sun, 2 Feb 2025 02:12:03 +0530 Subject: [PATCH 1/5] initialized backend & added authentication --- server/.env.sample | 2 + server/api/app.js | 21 ++++++ server/api/dbconnect.js | 24 +++++++ server/api/index.js | 20 ++++++ server/controllers/auth.controller.js | 92 +++++++++++++++++++++++++++ server/model/user.model.js | 45 +++++++++++++ server/package.json | 23 +++++++ server/routes/auth.routes.js | 10 +++ 8 files changed, 237 insertions(+) create mode 100644 server/.env.sample create mode 100644 server/api/app.js create mode 100644 server/api/dbconnect.js create mode 100644 server/api/index.js create mode 100644 server/controllers/auth.controller.js create mode 100644 server/model/user.model.js create mode 100644 server/package.json create mode 100644 server/routes/auth.routes.js diff --git a/server/.env.sample b/server/.env.sample new file mode 100644 index 0000000..ba17c15 --- /dev/null +++ b/server/.env.sample @@ -0,0 +1,2 @@ +MONGO_URI="mongodb+srv://:@cluster0.i3bju.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0" +SECRET_KEY="THIS_IS_A_JWT_SECRET" \ No newline at end of file diff --git a/server/api/app.js b/server/api/app.js new file mode 100644 index 0000000..3dc5b5c --- /dev/null +++ b/server/api/app.js @@ -0,0 +1,21 @@ +import express from "express" +import cors from 'cors' +import bodyParser from "body-parser" + + +const app = express() + + +app.use(cors()) +app.use(express.json({ limit: '16kb' })); +app.use(express.urlencoded({ extended: true, limit: '16kb' })); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: true })); + +//import roouter +import { authRouter } from "../routes/auth.routes.js" + + +//use router +app.use("/api/v1/auth", authRouter) +export {app} \ No newline at end of file diff --git a/server/api/dbconnect.js b/server/api/dbconnect.js new file mode 100644 index 0000000..d055485 --- /dev/null +++ b/server/api/dbconnect.js @@ -0,0 +1,24 @@ +import mongoose from 'mongoose'; +import dotenv from 'dotenv'; + +dotenv.config(); +// Database connection +export const dbConnect = async () => { + const url = process.env.MONGO_URI; + + if (!url) { + console.error('No URL received from env. Check .env file path.'); + process.exit(1); + } + + try { + await mongoose.connect(url, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + console.log('MongoDB connected'); + } catch (err) { + console.error('Database connection error:', err); + process.exit(1); // Exit process with failure + } +}; diff --git a/server/api/index.js b/server/api/index.js new file mode 100644 index 0000000..023ac5e --- /dev/null +++ b/server/api/index.js @@ -0,0 +1,20 @@ +import dotenv from 'dotenv'; +import { dbConnect } from './dbconnect.js'; +import { app } from './app.js'; + +// Load environment variables +dotenv.config(); + +console.log('Starting app. MONGO_URI:', process.env.MONGO_URI); + +// Connect to the database and start the server +dbConnect() + .then(() => { + const PORT = process.env.PORT || 3000; + app.listen(PORT, () => { + console.log(`App is running on port ${PORT} and DB connected`); + }); + }) + .catch((err) => { + console.error('Database connection failed:', err); + }); diff --git a/server/controllers/auth.controller.js b/server/controllers/auth.controller.js new file mode 100644 index 0000000..76a4443 --- /dev/null +++ b/server/controllers/auth.controller.js @@ -0,0 +1,92 @@ +import { User } from "../model/user.model.js"; +import jwt from "jsonwebtoken" +import dotenv from "dotenv" + +dotenv.config() + + +const secretKey = process.env.SECRET_KEY + +if (!secretKey) { + console.log("No secretKey defined check env path") + process.exit() +} + +const registerUser = async (req, res) => { + const { username, email, password, isOrganiser } = req.body + + if (!username || !email || !password) { + return res.status(400).send({ error: "username , email, password are required fields" }) + } + + try { + const fetchUser = await User.findOne({ email: email }) + + if (fetchUser) { + return res.status(400).send({ message: 'User already exists' }); + } + + const newUser = new User({ + email: email, + username: username, + password: password + }) + + if (isOrganiser) newUser.isOrganiser = true; + + await newUser.save() + console.log('user saved successfully') + + + const token = jwt.sign({ id: newUser._id }, secretKey, { + expiresIn: '1h', + }) + + // Respond with success message and token + res.status(201).send({ + message: 'User created successfully', + token, + createdUser:newUser + }); + + + } catch (error) { + console.error('Error during user registration:', error); + res.status(500).send({ message: 'Server error', error: error.message }); + } +} + +const loginUser = async (req, res) => { + const { email, password } = req.body + + try { + const user = await User.findOne({ email }); + + if (!user) { + console.log("User does not exist"); + return res.status(400).send({ message: 'Invalid credentials user' }); + } + + const isPasswordMatch = await user.matchPassword(password); + + if (!isPasswordMatch) { + console.log("Password doesn't match"); + return res.status(400).send({ message: 'Invalid credentials password' }); + } + + const token = jwt.sign({ id: user._id }, secretKey, { + expiresIn: '1h', + }); + + res.status(201).send({ + message: 'Login successful', + token, + LoggedInUser: user + }); + } catch (error) { + console.error('Error during user login:', error); + res.status(500).send({ message: 'Server error', error: error.message }); + } +} + +export { registerUser,loginUser } \ No newline at end of file diff --git a/server/model/user.model.js b/server/model/user.model.js new file mode 100644 index 0000000..bf22b6e --- /dev/null +++ b/server/model/user.model.js @@ -0,0 +1,45 @@ +import mongoose from "mongoose"; +import bcrypt from "bcryptjs" + + +const userSchema = new mongoose.Schema({ + username: { + type: String, + required: true, + unique: true, + }, + email: { + type: String, + required: true, + unique: true, + }, + password: { + type: String, + required: true, + }, + isOrganiser: { + type: Boolean, + default: false, + required: true + } + +}, { timestamps: true }) + + +userSchema.pre("save", async function (next) { + if (!this.isModified('password')) { + return next(); + } + const salt = await bcrypt.genSalt(10); + this.password = await bcrypt.hash(this.password, salt); + next(); +}) + +// Method to compare password for login +userSchema.methods.matchPassword = async function (enteredPassword) { + return await bcrypt.compare(enteredPassword, this.password); + }; + +const User = mongoose.model("User", userSchema) + +export {User} \ No newline at end of file diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..b141ed4 --- /dev/null +++ b/server/package.json @@ -0,0 +1,23 @@ +{ + "name": "server", + "version": "1.0.0", + "main": "index.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "bcrypt": "^5.1.1", + "bcryptjs": "^2.4.3", + "body-parser": "^1.20.3", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "jsonwebtoken": "^9.0.2", + "mongoose": "^8.9.6" + } +} diff --git a/server/routes/auth.routes.js b/server/routes/auth.routes.js new file mode 100644 index 0000000..8e8393b --- /dev/null +++ b/server/routes/auth.routes.js @@ -0,0 +1,10 @@ +import express from "express"; +import { loginUser, registerUser } from "../controllers/auth.controller.js"; + + +const authRouter = express.Router() + +authRouter.post("/signup", registerUser) +authRouter.post("/login", loginUser) + +export {authRouter} \ No newline at end of file From ef95b036ec1691dd28d863a16e42823d326bc9e7 Mon Sep 17 00:00:00 2001 From: NOOB-3301 Date: Sat, 8 Feb 2025 13:21:17 +0530 Subject: [PATCH 2/5] added profile controller --- server/api/app.js | 10 +- server/controllers/profile.controller.js | 158 +++++++++++++++++++++++ server/model/user.model.js | 17 ++- server/routes/profile.routes.js | 13 ++ 4 files changed, 192 insertions(+), 6 deletions(-) create mode 100644 server/controllers/profile.controller.js create mode 100644 server/routes/profile.routes.js diff --git a/server/api/app.js b/server/api/app.js index 3dc5b5c..c58ab33 100644 --- a/server/api/app.js +++ b/server/api/app.js @@ -6,16 +6,22 @@ import bodyParser from "body-parser" const app = express() +app.get('/', async (req,res) => { + res.status(200).send("express and mongodb, eventica server") +}) + app.use(cors()) app.use(express.json({ limit: '16kb' })); app.use(express.urlencoded({ extended: true, limit: '16kb' })); -app.use(bodyParser.json()); -app.use(bodyParser.urlencoded({ extended: true })); +// app.use(bodyParser.json()); +// app.use(bodyParser.urlencoded({ extended: true })); //import roouter import { authRouter } from "../routes/auth.routes.js" +import { profileRouter } from "../routes/profile.routes.js" //use router app.use("/api/v1/auth", authRouter) +app.use('/api/v1/profile', profileRouter) export {app} \ No newline at end of file diff --git a/server/controllers/profile.controller.js b/server/controllers/profile.controller.js new file mode 100644 index 0000000..7e3d4d6 --- /dev/null +++ b/server/controllers/profile.controller.js @@ -0,0 +1,158 @@ +import { User } from "../model/user.model.js"; +import jwt from "jsonwebtoken" +import dotenv from "dotenv" + +dotenv.config() + + +const secretKey = process.env.SECRET_KEY + +if (!secretKey) { + console.log("No secretKey defined check env path") + process.exit() +} + + +// Get user profile function +const getProfile = async (req, res) => { + try { + console.log("getProfile called"); + + // Step 1: Get the token from the authorization header + const authHeader = req.headers.authorization; + if (!authHeader) { + // If the header is missing, return an error + console.error("Authorization header is missing."); + return res.status(401).send({ error: "No token provided." }); + } + + // Step 2: Extract the token from the Authorization header + const token = authHeader.split(' ')[1]; + if (!token) { + // If the token is missing, return an error + console.error("Bearer token is missing."); + return res.status(401).send({ error: "Invalid token format." }); + } + + // Step 3: Verify the token + const decoded = jwt.verify(token, secretKey); + + // Step 4: Retrieve user information based on the decoded token's ID + const user = await User.findById(decoded.id); + if (!user) { + // If no user is found, return an error + console.error(`User not found for token with userId: ${decoded.userId}.`); + return res.status(404).send({ error: "User not found." }); + } + + // Step 5: Send back detailed user profile data + return res.status(200).send({message:"fetched successfully", fetchedUser: user}) + + console.log(`Profile fetched successfully for user ${user.username}.`); + } catch (error) { + console.error("Error during profile retrieval:", error); + if (error.name === "JsonWebTokenError") { + // Handle invalid JWT errors + return res.status(401).send({ error: "Invalid token." }); + } + // Handle unexpected errors + res.status(500).send({ error: "An error occurred while fetching the profile." }); + } +}; + + +//eedit profile +const editProfile = async (req,res) => { + try { + console.log("editProfile called"); + + // Step 1: Get the token from the authorization header + const authHeader = req.headers.authorization; + if (!authHeader) { + // If the header is missing, return an error + console.error("Authorization header is missing."); + return res.status(401).json({ error: "No token provided." }); + } + + // Step 2: Extract the token from the Authorization header + const token = authHeader.split(' ')[1]; + if (!token) { + // If the token is missing, return an error + console.error("Bearer token is missing."); + return res.status(401).json({ error: "Invalid token format." }); + } + + // Step 3: Verify the token + const decoded = jwt.verify(token, secretKey); + + // Step 4: Find the user by ID from the decoded token + const user = await User.findById(decoded.id); + if (!user) { + // If no user is found, return an error + console.error(`User not found for token with userId: ${decoded.id}.`); + return res.status(404).json({ error: "User not found." }); + } + + // Step 5: Update the user's profile fields if provided in the request body + const { username, password, email, dob, location } = req.body; + if (username) user.username = username; + if (password) user.password = password; // Ensure to hash the password if implementing + if (email) user.email = email; + if (dob) user.dob = dob; + if (location) user.location = location; + + // Step 6: Save the updated user information + await user.save(); + + console.log(`Profile updated successfully for user ${user.username}.`); + res.json({ message: "Profile updated successfully.", user }); + } catch (error) { + console.error("Error during profile update:", error); + if (error.name === "JsonWebTokenError") { + // Handle invalid JWT errors + return res.status(401).json({ error: "Invalid token." }); + } + // Handle unexpected errors + res.status(500).json({ error: "An error occurred while updating the profile." }); + } +} + + +// Delete user profile function +const deleteProfile = async (req, res) => { + try { + // Step 1: Get the token from the authorization header + const authHeader = req.headers.authorization; + if (!authHeader) { + // If the header is missing, return an error + return res.status(401).json({ error: "No token provided." }); + } + + // Step 2: Extract the token from the Authorization header + const token = authHeader.split(' ')[1]; + // Step 3: Verify the token + const decoded = jwt.verify(token, secretKey); + + // Step 4: Find the user by ID from the decoded token + const user = await User.findById(decoded.id); + if (!user) { + // If no user is found, return an error + return res.status(404).json({ error: "User not found." }); + } + + // Step 5: Delete the user profile + await user.deleteOne(); + res.json({ message: "Profile deleted successfully." }); + } catch (error) { + // Handle unexpected errors + res.status(500).json({ error: "An error occurred while deleting the profile." }); + } +}; + + + +export { + getProfile, + editProfile, + deleteProfile +} \ No newline at end of file diff --git a/server/model/user.model.js b/server/model/user.model.js index bf22b6e..a94d523 100644 --- a/server/model/user.model.js +++ b/server/model/user.model.js @@ -17,10 +17,19 @@ const userSchema = new mongoose.Schema({ type: String, required: true, }, - isOrganiser: { - type: Boolean, - default: false, - required: true + role: { + type: String, + enum: ['user', 'organiser', 'admin'], + default: 'user' + }, + location:{ + type: String + }, + dob:{ + type: Date + }, + picture:{ + type: String } }, { timestamps: true }) diff --git a/server/routes/profile.routes.js b/server/routes/profile.routes.js new file mode 100644 index 0000000..f2ca8b4 --- /dev/null +++ b/server/routes/profile.routes.js @@ -0,0 +1,13 @@ +import express from 'express' +import { deleteProfile, editProfile, getProfile } from '../controllers/profile.controller.js' + + +const profileRouter = express.Router() + + +profileRouter.get('/getprofile',getProfile) +profileRouter.post('/editprofile', editProfile) +profileRouter.delete('/deleteProfile', deleteProfile) + + +export {profileRouter} \ No newline at end of file From a5556940687f5bd57e6c474c551e57786c4fd85d Mon Sep 17 00:00:00 2001 From: NOOB-3301 Date: Fri, 21 Feb 2025 10:15:39 +0530 Subject: [PATCH 3/5] added backend setup instruction --- README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/README.md b/README.md index a1cf449..33735a1 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,41 @@ git push origin - Celebrate 🥳 your success after your pull request is merged successfully.

(back to top)

+# Backend Setup Instructions + + +1. Ensure Node.js and npm are installed on your system. You can verify installation by running: +``` +node -v +npm -v +``` + +2. Navigate to the backend directory: +``` +cd server +``` + +3. Install the required dependencies: +``` +npm install +``` + +4. Create a .env file in the backend root directory and add your environment variables. Here's an example structure: +``` +MONGO_URI="mongodb+srv://.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0" +SECRET_KEY="THIS_IS_A_JWT_SECRET" +``` + +5. Start the backend server: +``` +npm run dev +``` + +This will start the server in development mode. By default, it will run on http://localhost:3000. + +Test the API endpoints using tools like Postman or Thunder Client. + +

Contributing Guidelines📑

From cce0a3ae35132ab96e20b9d18d4bc2c1d389229f Mon Sep 17 00:00:00 2001 From: NOOB-3301 Date: Fri, 21 Feb 2025 10:16:20 +0530 Subject: [PATCH 4/5] . --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 33735a1..68c27a9 100644 --- a/README.md +++ b/README.md @@ -149,9 +149,9 @@ MONGO_URI="mongodb+srv://.mongodb.net/?retryWrites=true&w=maj SECRET_KEY="THIS_IS_A_JWT_SECRET" ``` -5. Start the backend server: +5. Start the backend server using nodemon: ``` -npm run dev +nodemon api/index.js ``` This will start the server in development mode. By default, it will run on http://localhost:3000. From 15ce6b46c39cec0223ab77b918be6d428cfa6cb0 Mon Sep 17 00:00:00 2001 From: "02_Worshipper :)" <134721562+NOOB-3301@users.noreply.github.com> Date: Thu, 27 Feb 2025 23:39:08 +0530 Subject: [PATCH 5/5] Update README.md --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 68c27a9..c051da3 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,16 @@ This will start the server in development mode. By default, it will run on http: Test the API endpoints using tools like Postman or Thunder Client. +Now for vercel Deployment: +1. Login into vercel account +2. Click on add project +3. select `server` as root directory +4. add this add env vars +``` +MONGO_URI="mongodb+srv://.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0" +SECRET_KEY="THIS_IS_A_JWT_SECRET" +``` +And server will deployed on vercel