diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..dd0ab5c6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ + +FROM node:18 AS frontend-build + +WORKDIR /app/frontend +COPY frontend/package*.json ./ + +RUN npm install + +COPY frontend/ ./ + +FROM node:18 AS backend-build + +WORKDIR /app/backend +COPY backend/package*.json ./ + +RUN npm install + +COPY backend/ ./ + +FROM node:18 + +WORKDIR /app + +COPY --from=backend-build /app/backend ./backend +COPY --from=frontend-build /app/frontend ./frontend + +COPY frontend/package*.json ./frontend/ +COPY backend/package*.json ./backend/ +RUN npm install --prefix frontend && npm install --prefix backend + + +COPY start.sh ./ + +RUN chmod +x start.sh + +EXPOSE 5173 3000 + + +CMD ["sh", "start.sh"] diff --git a/README.md b/README.md index 3754393d..f81fd975 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,22 @@ Make sure you follow our contributing guidlines:- [here](https://github.com/Ram npm run dev 4. Open your browser at `http://localhost:3000` to see the project running! 🌟 +Set-up using Dockerfile:- +1. **Build Docker Image**: + ```bash + docker build -t playcafe . +2. **Run Docker Image** + ```bash + docker run -p 5173:5173 -p 3000:3000 playcafe +3. Open your browser at `http://localhost:5173` to see the project running! 🌟 + +Set-up using docker-compose :- + +1. **Build Docker Image and Run the Application**: + ```bash + docker compose up --build + ## 🤝 Contributing We love contributions! 💙 Whether you're a participant in **GSSoC** or an open-source enthusiast, we welcome your input. Here's how you can contribute: @@ -118,6 +133,13 @@ Special thanks to our amazing mentors who are guiding this project! 🙌 Tejas Benibagde + + + haseebzaki-07 +
+ Haseeb Zaki +
+ Suhas-Koheda @@ -125,6 +147,8 @@ Special thanks to our amazing mentors who are guiding this project! 🙌 Suhas Koheda + + sajalbatra @@ -132,8 +156,6 @@ Special thanks to our amazing mentors who are guiding this project! 🙌 Sajal Batra - - vishnuprasad2004 @@ -169,6 +191,8 @@ Special thanks to our amazing mentors who are guiding this project! 🙌 Tanishi Rai + + Shiva-Bajpai @@ -176,8 +200,6 @@ Special thanks to our amazing mentors who are guiding this project! 🙌 Shiva Bajpai - - Sawan-Kushwah @@ -213,6 +235,8 @@ Special thanks to our amazing mentors who are guiding this project! 🙌 Jai Dhingra + + Jay-1409 @@ -220,8 +244,6 @@ Special thanks to our amazing mentors who are guiding this project! 🙌 Jay shah - - Mohitranag18 @@ -236,6 +258,13 @@ Special thanks to our amazing mentors who are guiding this project! 🙌 Bashua Mutiat + + + NilanchalaPanda +
+ Nilanchal +
+ Sapna127 @@ -250,6 +279,8 @@ Special thanks to our amazing mentors who are guiding this project! 🙌 Stuti + + Syed-Farazuddin diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 00000000..98ff58ae --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,5 @@ +node_modules +npm-debug.log +.git +.gitignore +README.md diff --git a/backend/.env.example b/backend/.env.example index b547b64e..42b74fd4 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,3 +1,4 @@ MONGO_URI=enter_your_mongo_uri EMAIL_USER=your_gmail -EMAIL_PASS=your_16_digit_pass \ No newline at end of file +EMAIL_PASS=your_16_digit_pass +JWT_SECRET=secret \ No newline at end of file diff --git a/backend/config/secret.js b/backend/config/secret.js new file mode 100644 index 00000000..031c6a28 --- /dev/null +++ b/backend/config/secret.js @@ -0,0 +1,11 @@ +const JWT_SECRET = process.env.JWT_SECRET; +const MONGO_URI = process.env.MONGO_URI; +const PORT = process.env.PORT; +const CORS_ORIGIN = process.env.CORS_ORIGIN; + +module.exports = { + JWT_SECRET, + MONGO_URI, + PORT, + CORS_ORIGIN, +}; diff --git a/backend/controller/admin.controller.js b/backend/controller/admin.controller.js index 347d1e7c..408ce12c 100644 --- a/backend/controller/admin.controller.js +++ b/backend/controller/admin.controller.js @@ -2,6 +2,7 @@ const bcrypt = require("bcrypt"); const { z } = require("zod"); const Admin = require("../models/admin.model"); const logger = require("../config/logger"); +const jwt = require("jsonwebtoken"); // Define the schema const adminSchema = z.object({ @@ -18,8 +19,10 @@ async function createAdmin(req, res) { return res.status(400).json({ error: validation.error.errors }); } const existingAdmin = await Admin.findOne({ email: req.body.email }); + const existingAdmin = await Admin.findOne({ email: req.body.email }); if (existingAdmin) { return res.status(409).json({ error: "Email is already registered" }); + return res.status(409).json({ error: "Email is already registered" }); } try { @@ -34,7 +37,6 @@ async function createAdmin(req, res) { } catch (error) { logger.error("Error creating admin:", { message: error.message, - stack: error.stack, }); res.status(500).json({ error: "Internal server error" }); } @@ -50,6 +52,15 @@ async function loginAdmin(req, res) { if (!validation.success) { return res.status(400).json({ error: validation.error.errors }); } + const adminLoginSchema = z.object({ + email: z.string().email("Invalid email address"), + password: z.string().min(6, "Password must be at least 6 characters long"), + }); + // Validate the request body + const validation = adminLoginSchema.safeParse(req.body); + if (!validation.success) { + return res.status(400).json({ error: validation.error.errors }); + } try { const admin = await Admin.findOne({ email: req.body.email }); @@ -58,16 +69,27 @@ async function loginAdmin(req, res) { } const validPassword = await bcrypt.compare( req.body.password, - admin.password, + admin.password ); if (!validPassword) { return res.status(401).json({ error: "Invalid email or password" }); } - res.json({ message: "Login successful" }); + const token = jwt.sign( + { id: admin._id, role: "admin" }, + process.env.JWT_SECRET, + { + expiresIn: "1h", + } + ); + res.json({ + message: "Login successful", + token, + role: "admin", + admin: { id: admin._id, name: admin.name, email: admin.email }, + }); } catch (error) { logger.error("Error logging in admin:", { message: error.message, - stack: error.stack, }); res.status(500).json({ error: "Internal server error" }); } diff --git a/backend/controller/customer.controller.js b/backend/controller/customer.controller.js index 0a65f271..34ede912 100644 --- a/backend/controller/customer.controller.js +++ b/backend/controller/customer.controller.js @@ -1,6 +1,7 @@ const bcrypt = require("bcrypt"); const { z } = require("zod"); const Customer = require("../models/customer.model"); +const jwt = require("jsonwebtoken"); // Define the schema const customerSchema = z.object({ @@ -59,7 +60,21 @@ async function loginCustomer(req, res) { if (!validPassword) { return res.status(401).json({ error: "Invalid email or password" }); } - res.json({ message: "Login successful" }); + const token = jwt.sign( + { id: customer._id }, + process.env.JWT_SECRET, + { expiresIn: "1h" } // Expires in 1 hour + ); + res.json({ + message: "Login successful", + token, + role: "customer", + user: { + id: customer._id, + name: customer.name, + email: customer.email, + }, + }); } catch (error) { res.status(500).json({ error: "Internal server error" }); } diff --git a/backend/middlewares/authAdmin.js b/backend/middlewares/authAdmin.js new file mode 100644 index 00000000..39dc2baa --- /dev/null +++ b/backend/middlewares/authAdmin.js @@ -0,0 +1,25 @@ +const jwt = require("jsonwebtoken"); +const logger = require("../config/logger"); + +const authenticateAdmin = (req, res, next) => { + const token = req.header("Authorization")?.split(" ")[1]; // Expecting "Bearer " + + if (token) { + jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { + if (err) { + return res.sendStatus(403); // Forbidden + } + if (decoded.role !== "admin") { + return res.sendStatus(403); // Forbidden + } + + req.user = decoded; + logger.info(`Admin authenticated: ${JSON.stringify(decoded.id)}`); + next(); + }); + } else { + res.sendStatus(401); // Unauthorized + } +}; + +module.exports = authenticateAdmin; diff --git a/backend/middlewares/authCustomer.js b/backend/middlewares/authCustomer.js new file mode 100644 index 00000000..eb175092 --- /dev/null +++ b/backend/middlewares/authCustomer.js @@ -0,0 +1,25 @@ +const jwt = require("jsonwebtoken"); +const logger = require("../config/logger"); +const config = require("../config/secret"); + +const authenticateCustomer = (req, res, next) => { + const token = req.header("Authorization")?.split(" ")[1]; // Expecting "Bearer " + + if (token) { + jwt.verify(token, config.JWT_SECRET, (err, user) => { + if (err) { + if (err.name === "TokenExpiredError") { + return res.status(401).json({ message: "Token expired" }); + } + return res.status(403).json({ message: "Invalid token" }); + } + req.user = user; + logger.info(`Customer authenticated: ${JSON.stringify(user.username)}`); + next(); + }); + } else { + res.sendStatus(401); // Unauthorized + } +}; + +module.exports = authenticateCustomer; diff --git a/backend/routes/adminRouter.js b/backend/routes/adminRouter.js index e6800543..197f5556 100644 --- a/backend/routes/adminRouter.js +++ b/backend/routes/adminRouter.js @@ -1,9 +1,10 @@ const express = require("express"); const { createAdmin, loginAdmin } = require("../controller/admin.controller"); +const authenticateAdmin = require("../middlewares/authAdmin"); const router = express.Router(); require("dotenv").config(); -router.get("/", (req, res) => { +router.get("/", authenticateAdmin, (req, res) => { res.json({ message: "Welcome to the Admin API!", version: "1.0.0", diff --git a/backend/routes/customerRouter.js b/backend/routes/customerRouter.js index e54c6e0c..0b929a0b 100644 --- a/backend/routes/customerRouter.js +++ b/backend/routes/customerRouter.js @@ -4,10 +4,11 @@ const { createCustomer, resetPassword, } = require("../controller/customer.controller"); +const authenticateCustomer = require("../middlewares/authCustomer"); const router = express.Router(); require("dotenv").config(); -router.get("/", (req, res) => { +router.get("/", authenticateCustomer, (req, res) => { res.json({ message: "Welcome to the User API!", version: "1.0.0", diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..7339920e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,47 @@ +version: '3.8' + +services: + frontend: + build: + context: . + dockerfile: Dockerfile + target: frontend-build # Reference the frontend build stage + working_dir: /app/frontend + volumes: + - ./frontend:/app/frontend # Bind-mount the frontend code to support hot-reloading + ports: + - "5173:5173" + command: ["npm", "run", "dev"] + environment: + - NODE_ENV=development + + backend: + build: + context: . + dockerfile: Dockerfile + target: backend-build # Reference the backend build stage + working_dir: /app/backend + volumes: + - ./backend:/app/backend # Bind-mount the backend code to support hot-reloading + ports: + - "3000:3000" + command: ["npm", "run", "dev"] + environment: + - NODE_ENV=development + + full-app: + build: + context: . + dockerfile: Dockerfile + depends_on: + - frontend + - backend + ports: + - "5173:5173" + - "3000:3000" + command: ["sh", "./start.sh"] + environment: + - NODE_ENV=production + volumes: + - ./frontend:/app/frontend + - ./backend:/app/backend diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 00000000..98ff58ae --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,5 @@ +node_modules +npm-debug.log +.git +.gitignore +README.md diff --git a/frontend/src/components/Shared/Navbar.jsx b/frontend/src/components/Shared/Navbar.jsx index 1e16b0d7..d9c7ef10 100644 --- a/frontend/src/components/Shared/Navbar.jsx +++ b/frontend/src/components/Shared/Navbar.jsx @@ -11,11 +11,11 @@ const Navbar = () => { const navigate = useNavigate(); // Correctly initialize useNavigate const menuItems = [ - { name: 'Home', path: '/' }, - { name: 'Events', path: '/events' }, - { name: 'Menu', path: '/menu' }, - { name: 'Reservation', path: '/reservation' }, - { name: 'Boardgames', path: '/boardgame' }, + { name: 'HOME', path: '/' }, + { name: 'EVENTS', path: '/events' }, + { name: 'MENU', path: '/menu' }, + { name: 'RESERVATION', path: '/reservation' }, + { name: 'BOARDGAMES', path: '/boardgame' }, ]; useEffect(() => { @@ -40,7 +40,7 @@ const Navbar = () => { setIsModalOpen(false); // Close the modal }; - const isHomePage = location.pathname === "/"; + const isHomePage = location.pathname === '/'; let buttonTextClass; if (isScrolled) { buttonTextClass = 'text-gray-900'; @@ -60,8 +60,9 @@ const Navbar = () => { return (