Skip to content

Commit 832756e

Browse files
Merge branch 'main' into enhance-footer-ui
2 parents 1419df7 + 06a5665 commit 832756e

21 files changed

Lines changed: 884 additions & 486 deletions

.env.example

Lines changed: 0 additions & 1 deletion
This file was deleted.

backend/.env.sample

Lines changed: 0 additions & 3 deletions
This file was deleted.

backend/models/User.js

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,33 @@ const mongoose = require("mongoose");
22
const bcrypt = require("bcryptjs");
33

44
const UserSchema = new mongoose.Schema({
5-
username: {
6-
type: String,
7-
required: true,
8-
unique: true,
9-
},
10-
email: {
11-
type: String,
12-
required: true,
13-
unique: true,
14-
},
15-
password: {
16-
type: String,
17-
required: true,
18-
},
5+
username: {
6+
type: String,
7+
required: true,
8+
unique: true,
9+
},
10+
email: {
11+
type: String,
12+
required: true,
13+
unique: true,
14+
},
15+
password: {
16+
type: String,
17+
required: true,
18+
},
1919
});
2020

21+
// ✅ FIXED: no next()
2122
UserSchema.pre('save', async function () {
22-
if (!this.isModified('password'))
23-
return;
23+
if (!this.isModified('password')) return;
2424

25-
const salt = await bcrypt.genSalt(10);
26-
this.password = await bcrypt.hash(this.password, salt);
25+
const salt = await bcrypt.genSalt(10);
26+
this.password = await bcrypt.hash(this.password, salt);
2727
});
2828

29-
// Compare passwords during login
29+
// ✅ password comparison
3030
UserSchema.methods.comparePassword = async function (enteredPassword) {
31-
return await bcrypt.compare(enteredPassword, this.password);
31+
return bcrypt.compare(enteredPassword, this.password);
3232
};
3333

34-
module.exports = mongoose.model("User", UserSchema);
34+
module.exports = mongoose.model("User", UserSchema);

package.json

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"test:backend": "jasmine spec/**/*.spec.cjs",
1212
"preview": "vite preview",
1313
"docker:dev": "docker compose --profile dev up --build",
14-
"docker:prod": "docker compose --profile prod up -d --build"
14+
"docker:prod": "docker compose --profile prod up -d --build",
15+
"test": "jasmine"
1516
},
1617
"dependencies": {
1718
"@emotion/react": "^11.11.3",
@@ -21,15 +22,18 @@
2122
"@primer/octicons-react": "^19.25.0",
2223
"@vitejs/plugin-react": "^4.3.3",
2324
"axios": "^1.7.7",
25+
"express": "^5.2.1",
2426
"framer-motion": "^12.23.12",
2527
"lucide-react": "^0.525.0",
28+
"mongoose": "^9.6.2",
2629
"octokit": "^4.0.2",
2730
"postcss": "^8.4.47",
2831
"react": "^18.3.1",
2932
"react-dom": "^18.3.1",
3033
"react-hot-toast": "^2.4.1",
3134
"react-icons": "^5.3.0",
3235
"react-router-dom": "^6.28.0",
36+
"recharts": "^3.8.1",
3337
"tailwindcss": "^3.4.14"
3438
},
3539
"devDependencies": {
@@ -45,13 +49,21 @@
4549
"@types/react-router-dom": "^5.3.3",
4650
"@vitejs/plugin-react-swc": "^3.5.0",
4751
"autoprefixer": "^10.4.20",
48-
"bcryptjs": "^3.0.2",
52+
"bcryptjs": "^3.0.3",
4953
"eslint": "^9.13.0",
5054
"eslint-plugin-react": "^7.37.2",
5155
"eslint-plugin-react-hooks": "^5.0.0",
5256
"eslint-plugin-react-refresh": "^0.4.14",
57+
"express": "^5.2.1",
5358
"express-session": "^1.18.2",
5459
"globals": "^15.11.0",
60+
"jasmine": "^5.9.0",
61+
"mongoose": "^9.6.2",
62+
"passport": "^0.7.0",
63+
"passport-local": "^1.0.0",
64+
"supertest": "^7.1.4",
65+
"typescript-eslint": "^8.59.3",
66+
"vite": "^5.4.10",
5567
"jasmine": "^5.13.0",
5668
"jasmine-spec-reporter": "^7.0.0",
5769
"jsdom": "^29.1.1",

spec/auth.routes.spec.cjs

Lines changed: 79 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,32 @@ const express = require('express');
33
const request = require('supertest');
44
const session = require('express-session');
55
const passport = require('passport');
6+
67
const User = require('../backend/models/User');
78
const authRoutes = require('../backend/routes/auth');
89

9-
// Setup Express app for testing
10+
// Create test app
1011
function createTestApp() {
1112
const app = express();
13+
1214
app.use(express.json());
13-
app.use(session({ secret: 'test', resave: false, saveUninitialized: false }));
15+
16+
app.use(
17+
session({
18+
secret: 'test-secret',
19+
resave: false,
20+
saveUninitialized: false,
21+
})
22+
);
23+
1424
app.use(passport.initialize());
1525
app.use(passport.session());
26+
27+
// Load passport config AFTER initializing passport
1628
require('../backend/config/passportConfig');
29+
1730
app.use('/auth', authRoutes);
31+
1832
return app;
1933
}
2034

@@ -27,76 +41,107 @@ describe('Auth Routes', () => {
2741
});
2842

2943
afterAll(async () => {
30-
await mongoose.connection.db.dropDatabase();
31-
await mongoose.disconnect();
44+
if (mongoose.connection.readyState === 1) {
45+
await mongoose.connection.db.dropDatabase();
46+
await mongoose.disconnect();
47+
}
3248
});
3349

3450
afterEach(async () => {
3551
await User.deleteMany({});
3652
});
3753

54+
// ---------------- SIGNUP ----------------
3855
it('should sign up a new user', async () => {
3956
const res = await request(app)
4057
.post('/auth/signup')
41-
.send({ username: 'testuser', email: 'test@example.com', password: 'password123' });
58+
.send({
59+
username: 'testuser',
60+
email: 'test@example.com',
61+
password: 'password123',
62+
});
63+
4264
expect(res.status).toBe(201);
4365
expect(res.body.message).toBe('User created successfully');
66+
4467
const user = await User.findOne({ email: 'test@example.com' });
4568
expect(user).toBeTruthy();
4669
});
4770

4871
it('should not sign up a user with existing email', async () => {
49-
await new User({ username: 'testuser', email: 'test@example.com', password: 'password123' }).save();
50-
const res = await request(app)
51-
.post('/auth/signup')
52-
.send({ username: 'testuser2', email: 'test@example.com', password: 'password456' });
53-
expect(res.status).toBe(400);
54-
expect(res.body.message).toBe('User already exists');
55-
});
72+
await User.create({
73+
username: 'testuser',
74+
email: 'test@example.com',
75+
password: 'password123',
76+
});
5677

57-
it('should not sign up a user with existing username', async () => {
58-
await new User({ username: 'testuser', email: 'test@example.com', password: 'password123' }).save();
5978
const res = await request(app)
6079
.post('/auth/signup')
61-
.send({ username: 'testuser', email: 'test2@example.com', password: 'password456' });
80+
.send({
81+
username: 'testuser2',
82+
email: 'test@example.com',
83+
password: 'password456',
84+
});
85+
6286
expect(res.status).toBe(400);
6387
expect(res.body.message).toBe('User already exists');
6488
});
6589

90+
// ---------------- LOGIN ----------------
6691
it('should login a user with correct credentials', async () => {
67-
await request(app)
68-
.post('/auth/signup')
69-
.send({ username: 'testuser', email: 'test@example.com', password: 'password123' });
92+
await User.create({
93+
username: 'testuser',
94+
email: 'test@example.com',
95+
password: 'password123',
96+
});
97+
7098
const agent = request.agent(app);
71-
const res = await agent
72-
.post('/auth/login')
73-
.send({ email: 'test@example.com', password: 'password123' });
99+
100+
const res = await agent.post('/auth/login').send({
101+
email: 'test@example.com',
102+
password: 'password123',
103+
});
104+
74105
expect(res.status).toBe(200);
75106
expect(res.body.message).toBe('Login successful');
76107
expect(res.body.user.email).toBe('test@example.com');
77108
});
78109

79110
it('should not login a user with wrong password', async () => {
80-
await request(app)
81-
.post('/auth/signup')
82-
.send({ username: 'testuser', email: 'test@example.com', password: 'password123' });
111+
await User.create({
112+
username: 'testuser',
113+
email: 'test@example.com',
114+
password: 'password123',
115+
});
116+
83117
const agent = request.agent(app);
84-
const res = await agent
85-
.post('/auth/login')
86-
.send({ email: 'test@example.com', password: 'wrongpassword' });
118+
119+
const res = await agent.post('/auth/login').send({
120+
email: 'test@example.com',
121+
password: 'wrongpassword',
122+
});
123+
87124
expect(res.status).toBe(401);
88125
});
89126

127+
// ---------------- LOGOUT ----------------
90128
it('should logout a logged-in user', async () => {
91-
await request(app)
92-
.post('/auth/signup')
93-
.send({ username: 'testuser', email: 'test@example.com', password: 'password123' });
94129
const agent = request.agent(app);
95-
await agent
96-
.post('/auth/login')
97-
.send({ email: 'test@example.com', password: 'password123' });
130+
131+
await agent.post('/auth/signup').send({
132+
username: 'testuser',
133+
email: 'test@example.com',
134+
password: 'password123',
135+
});
136+
137+
await agent.post('/auth/login').send({
138+
email: 'test@example.com',
139+
password: 'password123',
140+
});
141+
98142
const res = await agent.get('/auth/logout');
143+
99144
expect(res.status).toBe(200);
100145
expect(res.body.message).toBe('Logged out successfully');
101146
});
102-
});
147+
});

spec/user.model.spec.cjs

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,61 @@ describe('User Model', () => {
88
});
99

1010
afterAll(async () => {
11-
await mongoose.connection.db.dropDatabase();
12-
await mongoose.disconnect();
11+
if (mongoose.connection.readyState === 1) {
12+
await mongoose.connection.db.dropDatabase();
13+
await mongoose.disconnect();
14+
}
1315
});
1416

1517
afterEach(async () => {
1618
await User.deleteMany({});
1719
});
1820

19-
it('should create a user with hashed password', async () => {
20-
const userData = { username: 'testuser', email: 'test@example.com', password: 'password123' };
21-
const user = new User(userData);
21+
// -------- CREATE USER --------
22+
it('should hash password before saving user', async () => {
23+
const user = new User({
24+
username: 'testuser',
25+
email: 'test@example.com',
26+
password: 'password123',
27+
});
28+
2229
await user.save();
23-
expect(user.password).not.toBe(userData.password);
30+
31+
// password should not be plain text
32+
expect(user.password).not.toBe('password123');
33+
2434
const isMatch = await bcrypt.compare('password123', user.password);
25-
expect(isMatch).toBeTrue();
35+
expect(isMatch).toBe(true);
2636
});
2737

28-
it('should not hash password again if not modified', async () => {
29-
const userData = { username: 'testuser2', email: 'test2@example.com', password: 'password123' };
30-
const user = new User(userData);
31-
await user.save();
38+
// -------- PASSWORD NOT RE-HASHED --------
39+
it('should not re-hash password if not modified', async () => {
40+
const user = await User.create({
41+
username: 'testuser2',
42+
email: 'test2@example.com',
43+
password: 'password123',
44+
});
45+
3246
const originalHash = user.password;
47+
3348
user.username = 'updateduser';
3449
await user.save();
50+
3551
expect(user.password).toBe(originalHash);
3652
});
3753

38-
it('should compare passwords correctly', async () => {
39-
const userData = { username: 'testuser3', email: 'test3@example.com', password: 'password123' };
40-
const user = new User(userData);
41-
await user.save();
54+
// -------- COMPARE PASSWORD --------
55+
it('should correctly compare passwords', async () => {
56+
const user = await User.create({
57+
username: 'testuser3',
58+
email: 'test3@example.com',
59+
password: 'password123',
60+
});
61+
4262
const isMatch = await user.comparePassword('password123');
43-
expect(isMatch).toBeTrue();
4463
const isNotMatch = await user.comparePassword('wrongpassword');
45-
expect(isNotMatch).toBeFalse();
64+
65+
expect(isMatch).toBe(true);
66+
expect(isNotMatch).toBe(false);
4667
});
47-
});
68+
});

src/Routes/Router.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Signup from "../pages/Signup/Signup.tsx";
77
import Login from "../pages/Login/Login.tsx";
88
import ContributorProfile from "../pages/ContributorProfile/ContributorProfile.tsx";
99
import Home from "../pages/Home/Home.tsx";
10+
import Activity from "../pages/Activity.tsx";
1011

1112
const Router = () => {
1213
return (
@@ -19,8 +20,11 @@ const Router = () => {
1920
<Route path="/contact" element={<Contact />} />
2021
<Route path="/contributors" element={<Contributors />} />
2122
<Route path="/contributor/:username" element={<ContributorProfile />} />
23+
24+
{/* ✅ new route */}
25+
<Route path="/activity" element={<Activity />} />
2226
</Routes>
2327
);
2428
};
2529

26-
export default Router;
30+
export default Router;

0 commit comments

Comments
 (0)