diff --git a/README.md b/README.md index a747b53..ea2cbf8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # ๐ŸŒŸ **GitHub Tracker** ๐ŸŒŸ + **Track Activity of Users on GitHub** @@ -41,23 +42,28 @@ Welcome to **GitHub Tracker**, a web app designed to help you monitor and analyz --- ## ๐Ÿš€ Setup Guide + 1. Clone the repository to your local machine: + ```bash $ git clone https://github.com/yourusername/github-tracker.git ``` 2. Navigate to the project directory: + ```bash $ cd github-tracker ``` 3. Run the frontend + ```bash $ npm i $ npm run dev ``` 4. Run the backend + ```bash $ npm i $ npm start @@ -66,22 +72,27 @@ $ npm start ## ๐Ÿงช Backend Unit & Integration Testing with Jasmine This project uses the Jasmine framework for backend unit and integration tests. The tests cover: + - User model (password hashing, schema, password comparison) - Authentication routes (signup, login, logout) - Passport authentication logic (via integration tests) ### Prerequisites + - **Node.js** and **npm** installed - **MongoDB** running locally (default: `mongodb://127.0.0.1:27017`) ### Installation + Install all required dependencies: + ```sh npm install npm install --save-dev jasmine @types/jasmine supertest express-session passport passport-local bcryptjs ``` ### Running the Tests + 1. **Start MongoDB** (if not already running): ```sh mongod @@ -92,24 +103,26 @@ npm install --save-dev jasmine @types/jasmine supertest express-session passport ``` ### Test Files + - `spec/user.model.spec.cjs` โ€” Unit tests for the User model - `spec/auth.routes.spec.cjs` โ€” Integration tests for authentication routes ### Jasmine Configuration + The Jasmine config (`spec/support/jasmine.mjs`) is set to recognize `.cjs`, `.js`, and `.mjs` test files: + ```js -spec_files: [ - "**/*[sS]pec.?(m)js", - "**/*[sS]pec.cjs" -] +spec_files: ["**/*[sS]pec.?(m)js", "**/*[sS]pec.cjs"]; ``` ### Troubleshooting + - **No specs found:** Ensure your test files have the correct extension and are in the `spec/` directory. - **MongoDB connection errors:** Make sure MongoDB is running and accessible. - **Missing modules:** Install any missing dev dependencies with `npm install --save-dev `. ### What Was Covered + - Jasmine is set up and configured for backend testing. - All major backend modules are covered by unit/integration tests. - Tests are passing and verified. @@ -126,13 +139,11 @@ spec_files: [ - Make sure you show some love by giving โญ to our repository.
- - - + + +
- - ---

diff --git a/backend/middleware/authMiddleware.js b/backend/middleware/authMiddleware.js new file mode 100644 index 0000000..eca1d83 --- /dev/null +++ b/backend/middleware/authMiddleware.js @@ -0,0 +1,9 @@ +// Middleware to check if user is authenticated +const isAuthenticated = (req, res, next) => { + if (req.isAuthenticated()) { + return next(); + } + res.status(401).json({ message: 'Unauthorized - Please log in' }); +}; + +module.exports = { isAuthenticated }; diff --git a/backend/routes/auth.js b/backend/routes/auth.js index e26c7a9..9ffa01d 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -1,42 +1,67 @@ const express = require("express"); const passport = require("passport"); const User = require("../models/User"); +const { isAuthenticated } = require("../middleware/authMiddleware"); // I'm calling the Middleware I just created here const router = express.Router(); // Signup route router.post("/signup", async (req, res) => { + const { username, email, password } = req.body; - const { username, email, password } = req.body; + try { + const existingUser = await User.findOne({ email }); - try { - const existingUser = await User.findOne( {email} ); + if (existingUser) + return res.status(400).json({ message: "User already exists" }); - if (existingUser) - return res.status(400).json( {message: 'User already exists'} ); + const newUser = new User({ username, email, password }); + await newUser.save(); + res.status(201).json({ message: "User created successfully" }); + } catch (err) { + res + .status(500) + .json({ message: "Error creating user", error: err.message }); + } +}); - const newUser = new User( {username, email, password} ); - await newUser.save(); - res.status(201).json( {message: 'User created successfully'} ); - } catch (err) { - res.status(500).json({ message: 'Error creating user', error: err.message }); +// I'm writing a much more complex login route here because I want to handle all the possible errors that can happen during the login process, and I also want to return the user data if the login is successful. This way, the frontend can easily access the user information after logging in without needing to make an additional request to get the current user. +router.post("/login", (req, res, next) => { + passport.authenticate("local", (err, user, info) => { + if (err) { + return res + .status(500) + .json({ message: "Login error", error: err.message }); + } + if (!user) { + return res + .status(401) + .json({ message: info.message || "Invalid credentials" }); } + req.logIn(user, (err) => { + if (err) { + return res + .status(500) + .json({ message: "Session error", error: err.message }); + } + res.status(200).json({ message: "Login successful", user: req.user }); + }); + })(req, res, next); }); -// Login route -router.post("/login", passport.authenticate('local'), (req, res) => { - res.status(200).json( { message: 'Login successful', user: req.user } ); +// Get current authenticated user +router.get("/me", isAuthenticated, (req, res) => { + res.status(200).json({ user: req.user }); }); // Logout route router.get("/logout", (req, res) => { - - req.logout((err) => { - - if (err) - return res.status(500).json({ message: 'Logout failed', error: err.message }); - else - res.status(200).json({ message: 'Logged out successfully' }); - }); + req.logout((err) => { + if (err) + return res + .status(500) + .json({ message: "Logout failed", error: err.message }); + else res.status(200).json({ message: "Logged out successfully" }); + }); }); module.exports = router; diff --git a/backend/server.js b/backend/server.js index 3f19f00..41491a9 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,39 +1,51 @@ -const express = require('express'); -const mongoose = require('mongoose'); -const session = require('express-session'); -const passport = require('passport'); -const bodyParser = require('body-parser'); -require('dotenv').config(); -const cors = require('cors'); +const express = require("express"); +const mongoose = require("mongoose"); +const session = require("express-session"); +const passport = require("passport"); +const bodyParser = require("body-parser"); +require("dotenv").config(); +const cors = require("cors"); // Passport configuration -require('./config/passportConfig'); +require("./config/passportConfig"); const app = express(); // CORS configuration -app.use(cors('*')); +//Ok so this needs to be taken care of, Cannot expose the BE like that, only FE can call it, else you'll face issues +// For testing, if anyone else uses any other port, pls change the default vite port to that port, and also change the FRONTEND_URL in .env to that port, else you'll face CORS issues, and you won't be able to call the BE from the FE. +app.use( + cors({ + origin: process.env.FRONTEND_URL || "http://localhost:5173", + credentials: true, + }), +); // Middleware app.use(bodyParser.json()); -app.use(session({ +app.use( + session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, -})); + }), +); app.use(passport.initialize()); app.use(passport.session()); // Routes -const authRoutes = require('./routes/auth'); -app.use('/api/auth', authRoutes); +const authRoutes = require("./routes/auth"); +app.use("/api/auth", authRoutes); // Connect to MongoDB -mongoose.connect(process.env.MONGO_URI, {}).then(() => { - console.log('Connected to MongoDB'); +mongoose + .connect(process.env.MONGO_URI, {}) + .then(() => { + console.log("Connected to MongoDB"); app.listen(process.env.PORT, () => { - console.log(`Server running on port ${process.env.PORT}`); + console.log(`Server running on port ${process.env.PORT}`); }); -}).catch((err) => { - console.log('MongoDB connection error:', err); -}); + }) + .catch((err) => { + console.log("MongoDB connection error:", err); + }); diff --git a/src/Routes/Router.tsx b/src/Routes/Router.tsx index 40a7861..2dd839e 100644 --- a/src/Routes/Router.tsx +++ b/src/Routes/Router.tsx @@ -7,12 +7,20 @@ import Signup from "../pages/Signup/Signup.tsx"; import Login from "../pages/Login/Login.tsx"; import ContributorProfile from "../pages/ContributorProfile/ContributorProfile.tsx"; import Home from "../pages/Home/Home.tsx"; +import ProtectedRoute from "../components/ProtectedRoute"; const Router = () => { return ( } /> - } /> + + + + } + /> } /> } /> } /> diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index c6cc86d..bcdc28b 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,18 +1,33 @@ -import { Link } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; import { useState, useContext } from "react"; import { ThemeContext } from "../context/ThemeContext"; -import { Moon, Sun } from 'lucide-react'; - +import { Moon, Sun, LogOut } from "lucide-react"; +import { useAuth } from "../hooks/useAuth"; const Navbar: React.FC = () => { - const [isOpen, setIsOpen] = useState(false); + const [isLoggingOut, setIsLoggingOut] = useState(false); const themeContext = useContext(ThemeContext); + const { isAuthenticated, user, logout } = useAuth(); + const navigate = useNavigate(); - if (!themeContext) - return null; + if (!themeContext) return null; const { toggleTheme, mode } = themeContext; + //handling the logout process here, we set the isLoggingOut state to true, then we call the logout function from the AuthContext, and if it's successful, we navigate the user to the home page and close the mobile menu. If there's an error during logout, we log it to the console. Finally, we set isLoggingOut back to false regardless of the outcome. + + const handleLogout = async () => { + setIsLoggingOut(true); + try { + await logout(); + navigate("/"); + setIsOpen(false); + } catch (error) { + console.error("Logout failed:", error); + } finally { + setIsLoggingOut(false); + } + }; return (