Skip to content

Commit df719ab

Browse files
authored
Merge branch 'main' into fix/auth-user-tests
2 parents 856baf6 + 8d17610 commit df719ab

22 files changed

Lines changed: 901 additions & 305 deletions

.github/workflows/auto-label-gssoc.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,6 @@ jobs:
2525
with:
2626
github_token: ${{ secrets.GITHUB_TOKEN }} # Use GITHUB_TOKEN for PRs
2727
labels: |
28-
level:intermediate quality:clean gssoc:approved
28+
level:intermediate
29+
quality:clean
30+
gssoc:approved

Dockerfile.prod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ WORKDIR /app
77
# Copy package.json
88
COPY package.json .
99

10-
# Install production dependencies using Yarn
11-
RUN npm install --production
10+
# Install dependencies (dev deps needed for the build step)
11+
RUN npm install
1212
# Copy the rest of the application files
1313
COPY . .
1414

15-
# Build the frontend using Yarn
15+
# Build the frontend
1616
RUN npm run build
1717

1818
# Stage 2: Serve the application with Nginx

backend/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"scripts": {
66
"dev": "nodemon server.js",
77
"start": "node server.js",
8-
"test": "echo \"Error: no test specified\" && exit 1"
8+
"test": "jasmine spec/**/*.spec.cjs"
9+
910
},
1011
"keywords": [],
1112
"author": "",
@@ -20,7 +21,8 @@
2021
"express-session": "^1.18.1",
2122
"mongoose": "^8.8.2",
2223
"passport": "^0.7.0",
23-
"passport-local": "^1.0.0"
24+
"passport-local": "^1.0.0",
25+
"zod": "^4.4.3"
2426
},
2527
"devDependencies": {
2628
"nodemon": "^3.1.9"

backend/routes/auth.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,37 @@
11
const express = require("express");
22
const passport = require("passport");
33
const User = require("../models/User");
4+
const { signupSchema, loginSchema } = require("../validators/authValidator");
5+
const { validateRequest } = require("../validators/validationRequest");
46
const router = express.Router();
57

68
// Signup route
7-
router.post("/signup", async (req, res) => {
9+
router.post("/signup", validateRequest(signupSchema), async (req, res) => {
810

911
const { username, email, password } = req.body;
1012

1113
try {
12-
const existingUser = await User.findOne( {email} );
14+
const existingUser = await User.findOne({
15+
$or: [{ email }, { username }],
16+
});
1317

1418
if (existingUser)
15-
return res.status(400).json( {message: 'User already exists'} );
19+
return res.status(400).json({ message: 'User already exists' });
1620

17-
const newUser = new User( {username, email, password} );
21+
const newUser = new User({ username, email, password });
1822
await newUser.save();
19-
res.status(201).json( {message: 'User created successfully'} );
23+
res.status(201).json({ message: 'User created successfully' });
2024
} catch (err) {
25+
if (err && err.code === 11000) {
26+
return res.status(400).json({ message: 'User already exists' });
27+
}
28+
2129
res.status(500).json({ message: 'Error creating user', error: err.message });
2230
}
2331
});
2432

2533
// Login route
26-
router.post("/login", passport.authenticate('local'), (req, res) => {
34+
router.post("/login", validateRequest(loginSchema), passport.authenticate('local'), (req, res) => {
2735
res.status(200).json( { message: 'Login successful', user: req.user } );
2836
});
2937

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const { z } = require("zod");
2+
3+
const signupSchema = z.object({
4+
username: z.string()
5+
.trim()
6+
.min(3, "Username must be at least 3 characters long")
7+
.max(30, "Username must be at most 30 characters long")
8+
.regex(/^[a-zA-Z0-9_]+$/, "Username can only contain letters, numbers, and underscores")
9+
,
10+
11+
email: z.string()
12+
.trim()
13+
.toLowerCase()
14+
.email("Invalid email address"),
15+
16+
17+
password: z.string()
18+
.min(8, "Password must be at least 8 characters long")
19+
.max(100, "Password must be at most 100 characters long")
20+
.regex(
21+
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/,
22+
'Password must contain uppercase, lowercase, number, and special character'
23+
),
24+
});
25+
26+
27+
const loginSchema = z.object({
28+
email: z.string()
29+
.trim()
30+
.toLowerCase()
31+
.email("Invalid email address"),
32+
password: z.string()
33+
.min(8, "Password must be at least 8 characters long")
34+
.max(100, "Password must be at most 100 characters long")
35+
});
36+
37+
38+
module.exports = { signupSchema, loginSchema };
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const validateRequest = (schema) => (req, res, next) => {
2+
const result = schema.safeParse(req.body);
3+
4+
if(!result.success) {
5+
return res.status(400).json({
6+
success: false,
7+
message: 'Validation failed',
8+
errors: result.error.issues.map((err) => ({
9+
field: err.path.join('.'),
10+
message: err.message,
11+
})),
12+
});
13+
}
14+
15+
req.validated = result.data;
16+
req.body = result.data;
17+
next();
18+
}
19+
20+
module.exports = { validateRequest };

package.json

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
"dev": "vite --host",
88
"build": "vite build",
99
"lint": "eslint .",
10+
"test": "vitest",
11+
"test:backend": "jasmine spec/**/*.spec.cjs",
1012
"preview": "vite preview",
1113
"docker:dev": "docker compose --profile dev up --build",
1214
"docker:prod": "docker compose --profile prod up -d --build",
@@ -17,7 +19,7 @@
1719
"@emotion/styled": "^11.11.0",
1820
"@mui/icons-material": "^5.15.6",
1921
"@mui/material": "^5.15.6",
20-
"@primer/octicons-react": "^19.15.5",
22+
"@primer/octicons-react": "^19.25.0",
2123
"@vitejs/plugin-react": "^4.3.3",
2224
"axios": "^1.7.7",
2325
"express": "^5.2.1",
@@ -35,6 +37,9 @@
3537
},
3638
"devDependencies": {
3739
"@eslint/js": "^9.13.0",
40+
"@testing-library/jest-dom": "^6.9.1",
41+
"@testing-library/react": "^16.3.2",
42+
"@testing-library/user-event": "^14.6.1",
3843
"@types/jasmine": "^5.1.8",
3944
"@types/node": "^22.10.1",
4045
"@types/react": "^18.3.23",
@@ -50,10 +55,13 @@
5055
"eslint-plugin-react-refresh": "^0.4.14",
5156
"express-session": "^1.18.2",
5257
"globals": "^15.11.0",
53-
"jasmine": "^5.9.0",
58+
"jasmine": "^5.13.0",
59+
"jasmine-spec-reporter": "^7.0.0",
60+
"jsdom": "^29.1.1",
5461
"passport": "^0.7.0",
5562
"passport-local": "^1.0.0",
56-
"supertest": "^7.1.4",
57-
"vite": "^5.4.10"
63+
"supertest": "^7.2.2",
64+
"vite": "^5.4.10",
65+
"vitest": "^4.1.6"
5866
}
5967
}

src/App.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
1+
import { useLocation } from "react-router-dom";
12
import Navbar from "./components/Navbar";
23
import Footer from "./components/Footer";
34
import ScrollProgressBar from "./components/ScrollProgressBar";
45
import { Toaster } from "react-hot-toast";
56
import Router from "./Routes/Router";
6-
import ThemeWrapper from "./context/ThemeContext";
7+
8+
const FULLSCREEN_ROUTES = ["/signup", "/login"];
79

810
function App() {
11+
const location = useLocation();
12+
const isFullscreen = FULLSCREEN_ROUTES.includes(location.pathname);
13+
914
return (
10-
<ThemeWrapper>
1115
<div className="relative flex flex-col min-h-screen">
12-
<ScrollProgressBar />
16+
{!isFullscreen && <ScrollProgressBar />}
1317

14-
<Navbar />
18+
{!isFullscreen && <Navbar />}
1519

16-
<main className="flex-grow bg-gray-50 dark:bg-gray-800 flex justify-center items-center">
20+
<main className={`flex justify-center items-center ${isFullscreen ? "flex-1" : "flex-grow bg-gray-50 dark:bg-gray-800"}`}>
1721
<Router />
1822
</main>
1923

20-
<Footer />
24+
{!isFullscreen && <Footer />}
2125

2226
<Toaster
2327
position="top-center"
@@ -37,7 +41,6 @@ function App() {
3741
}}
3842
/>
3943
</div>
40-
</ThemeWrapper>
4144
);
4245
}
4346

src/components/Features.tsx

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,54 @@ const Features = () => {
77
title: 'Activity Analytics',
88
description: 'Comprehensive charts and graphs showing commit patterns, contribution streaks, and repository activity over time.',
99
bgColor: 'bg-blue-100',
10-
iconColor: 'text-blue-600'
10+
iconColor: 'text-blue-600',
11+
hoverColor: 'hover:bg-blue-400/50 dark:hover:bg-blue-900/30',
12+
borderColor: 'hover:border-blue-200 dark:hover:border-blue-700'
1113
},
1214
{
1315
icon: Users,
1416
title: 'Multi-User Tracking',
1517
description: 'Monitor multiple GitHub users simultaneously and compare their activity levels and contribution patterns.',
1618
bgColor: 'bg-green-100',
17-
iconColor: 'text-green-600'
19+
iconColor: 'text-green-600',
20+
hoverColor: 'hover:bg-green-400/50 dark:hover:bg-green-900/30',
21+
borderColor: 'hover:border-green-200 dark:hover:border-green-700'
1822
},
1923
{
2024
icon: Search,
2125
title: 'Smart Search',
2226
description: 'Quickly find and add users to your tracking list with intelligent search and auto-suggestions.',
2327
bgColor: 'bg-purple-100',
24-
iconColor: 'text-purple-600'
28+
iconColor: 'text-purple-600',
29+
hoverColor: 'hover:bg-purple-400/50 dark:hover:bg-purple-900/30',
30+
borderColor: 'hover:border-purple-200 dark:hover:border-purple-700'
2531
},
2632
{
2733
icon: Zap,
2834
title: 'Real-time Updates',
2935
description: 'Get instant notifications and updates when tracked users make new contributions or repositories.',
3036
bgColor: 'bg-orange-100',
31-
iconColor: 'text-orange-600'
37+
iconColor: 'text-orange-600',
38+
hoverColor: 'hover:bg-orange-400/50 dark:hover:bg-orange-900/30',
39+
borderColor: 'hover:border-orange-200 dark:hover:border-orange-700'
3240
},
3341
{
3442
icon: Shield,
3543
title: 'Privacy First',
3644
description: 'All data is fetched from public GitHub APIs. We don\'t store personal information or require GitHub access.',
3745
bgColor: 'bg-red-100',
38-
iconColor: 'text-red-600'
46+
iconColor: 'text-red-600',
47+
hoverColor: 'hover:bg-red-400/50 dark:hover:bg-red-900/30',
48+
borderColor: 'hover:border-red-200 dark:hover:border-red-700'
3949
},
4050
{
4151
icon: Globe,
4252
title: 'Export & Share',
4353
description: 'Export activity reports and share insights with your team through various formats and integrations.',
4454
bgColor: 'bg-indigo-100',
45-
iconColor: 'text-indigo-600'
55+
iconColor: 'text-indigo-600',
56+
hoverColor: 'hover:bg-indigo-400/50 dark:hover:bg-indigo-900/30',
57+
borderColor: 'hover:border-indigo-200 dark:hover:border-indigo-700'
4658
}
4759
];
4860

@@ -60,12 +72,12 @@ const Features = () => {
6072
{features.map((feature, index) => {
6173
const IconComponent = feature.icon;
6274
return (
63-
<div key={index} className="bg-gray-50 dark:bg-gray-800 p-8 rounded-2xl hover:shadow-lg transition-all duration-300">
75+
<div key={index} className={`group h-72 w-full bg-gray-100 dark:bg-gray-800 ${feature.hoverColor} ${feature.borderColor} rounded-2xl shadow-md hover:shadow-2xl border dark:border-gray-800 transform hover:-translate-y-2 hover:scale-[1.02] transition-all duration-300 ease-linear p-6`}>
6476
<div className={`${feature.bgColor} w-12 h-12 rounded-lg flex items-center justify-center mb-6`}>
6577
<IconComponent className={`h-6 w-6 ${feature.iconColor}`} />
6678
</div>
67-
<h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-3">{feature.title}</h3>
68-
<p className="text-gray-600 dark:text-gray-300 leading-relaxed">
79+
<h3 className=" text-2xl font-bold text-gray-900 dark:text-gray-100 group-hover:text-black dark:group-hover:text-white transition-colors duration-300">{feature.title}</h3>
80+
<p className="text-gray-600 dark:text-gray-300 text-base font-semibold leading-relaxed group-hover:text-gray-700 dark:group-hover:text-gray-200 transition-colors duration-300">
6981
{feature.description}
7082
</p>
7183
</div>

0 commit comments

Comments
 (0)