Skip to content

Commit 27d5ed6

Browse files
Merge branch 'main' into feat/navbar-profile
2 parents 6094af4 + b4fa30e commit 27d5ed6

58 files changed

Lines changed: 4670 additions & 691 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ Welcome to **GitHub Tracker**, a web app designed to help you monitor and analyz
4343
## 🚀 Setup Guide
4444
1. Clone the repository to your local machine:
4545
```bash
46-
$ git clone https://github.com/yourusername/github-tracker.git
46+
$ git clone https://github.com/GitMetricsLab/github_tracker.git
4747
```
4848

4949
2. Navigate to the project directory:
@@ -63,6 +63,16 @@ $ npm i
6363
$ npm start
6464
```
6565

66+
### Backend Environment Variables
67+
68+
Create `backend/.env` for local backend runs:
69+
70+
| Variable | Required | Purpose |
71+
| --- | --- | --- |
72+
| `MONGO_URI` | Yes | MongoDB connection string used by Mongoose and the persistent session store |
73+
| `SESSION_SECRET` | Yes | Secret used to sign Express session cookies |
74+
| `NODE_ENV` | No | Set to `production` to send session cookies only over HTTPS |
75+
6676
## 🧪 Backend Unit & Integration Testing with Jasmine
6777

6878
This project uses the Jasmine framework for backend unit and integration tests. The tests cover:

backend/config/passportConfig.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ passport.use(
99
try {
1010
const user = await User.findOne( {email} );
1111
if (!user) {
12-
return done(null, false, { message: 'Email is invalid '});
12+
return done(null, false, { message: 'Invalid email or password' });
1313
}
1414

1515
const isMatch = await user.comparePassword(password);
1616
if (!isMatch) {
17-
return done(null, false, { message: 'Invalid password' });
17+
return done(null, false, { message: 'Invalid email or password' });
1818
}
1919

2020
return done(null, {
@@ -35,9 +35,13 @@ passport.serializeUser((user, done) => {
3535
});
3636

3737
// Deserialize user (retrieve user from session)
38+
// .select('-password -__v') excludes the bcrypt hash from req.user so it
39+
// cannot be accidentally serialized into an API response.
40+
// .lean() returns a plain object instead of a Mongoose document, preventing
41+
// model methods from being accessible on req.user.
3842
passport.deserializeUser(async (id, done) => {
3943
try {
40-
const user = await User.findById(id);
44+
const user = await User.findById(id).select('-password -__v').lean();
4145
done(null, user);
4246
} catch (err) {
4347
done(err, null);

backend/config/session.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const MongoStore = require('connect-mongo');
2+
3+
const SESSION_TTL_SECONDS = 14 * 24 * 60 * 60;
4+
5+
function createSessionConfig({
6+
mongoUrl = process.env.MONGO_URI,
7+
sessionSecret = process.env.SESSION_SECRET,
8+
nodeEnv = process.env.NODE_ENV,
9+
storeFactory = MongoStore,
10+
} = {}) {
11+
if (!mongoUrl) {
12+
throw new Error('MONGO_URI is required to configure the session store');
13+
}
14+
15+
return {
16+
secret: sessionSecret,
17+
resave: false,
18+
saveUninitialized: false,
19+
store: storeFactory.create({
20+
mongoUrl,
21+
ttl: SESSION_TTL_SECONDS,
22+
}),
23+
cookie: {
24+
secure: nodeEnv === 'production',
25+
},
26+
};
27+
}
28+
29+
module.exports = {
30+
SESSION_TTL_SECONDS,
31+
createSessionConfig,
32+
};

backend/data/discussions.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"discussions": [
3+
{
4+
"id": "b3808b42-91e4-4d81-991a-07a96d7a549f",
5+
"title": "Test discussion from shell",
6+
"body": "This is a sufficiently long discussion body to pass validation and save.",
7+
"category": "Help",
8+
"tags": [
9+
"test",
10+
"api"
11+
],
12+
"authorId": "8d706914-51d9-40ca-981c-ac9dcb6a1881",
13+
"authorName": "Guest",
14+
"likes": [
15+
"00000000-0000-0000-0000-000000000000"
16+
],
17+
"comments": [
18+
{
19+
"id": "d1b1bcad-6989-49ff-a587-667f8426198e",
20+
"text": "Yes",
21+
"authorId": "00000000-0000-0000-0000-000000000000",
22+
"authorName": "demo-user",
23+
"createdAt": "2026-05-27T12:56:14.816Z"
24+
}
25+
],
26+
"createdAt": "2026-05-27T11:55:11.307Z",
27+
"updatedAt": "2026-05-27T13:00:24.816Z"
28+
}
29+
]
30+
}

backend/data/users.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"users": [
3+
{
4+
"id": "00000000-0000-0000-0000-000000000000",
5+
"username": "demo-user",
6+
"email": "user@example.com",
7+
"password": "$2a$10$00000000000000000000000000000000000000000000000000000"
8+
}
9+
]
10+
}

backend/models/Discussion.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
const mongoose = require('mongoose');
2+
3+
const CommentSchema = new mongoose.Schema(
4+
{
5+
text: {
6+
type: String,
7+
required: true,
8+
trim: true,
9+
maxlength: 1000,
10+
},
11+
authorId: {
12+
type: String,
13+
required: true,
14+
},
15+
authorName: {
16+
type: String,
17+
required: true,
18+
trim: true,
19+
},
20+
},
21+
{ timestamps: true }
22+
);
23+
24+
const DiscussionSchema = new mongoose.Schema(
25+
{
26+
title: {
27+
type: String,
28+
required: true,
29+
trim: true,
30+
minlength: 4,
31+
maxlength: 140,
32+
},
33+
body: {
34+
type: String,
35+
required: true,
36+
trim: true,
37+
minlength: 20,
38+
maxlength: 4000,
39+
},
40+
category: {
41+
type: String,
42+
required: true,
43+
trim: true,
44+
maxlength: 60,
45+
},
46+
tags: {
47+
type: [
48+
{
49+
type: String,
50+
trim: true,
51+
maxlength: 30,
52+
},
53+
],
54+
default: [],
55+
},
56+
authorId: {
57+
type: String,
58+
required: true,
59+
},
60+
authorName: {
61+
type: String,
62+
required: true,
63+
trim: true,
64+
},
65+
likes: {
66+
type: [
67+
{
68+
type: String,
69+
},
70+
],
71+
default: [],
72+
},
73+
comments: {
74+
type: [CommentSchema],
75+
default: [],
76+
},
77+
},
78+
{ timestamps: true }
79+
);
80+
81+
module.exports = mongoose.model('Discussion', DiscussionSchema);

backend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"dependencies": {
1515
"bcryptjs": "^2.4.3",
1616
"body-parser": "^1.20.3",
17+
"connect-mongo": "^5.1.0",
1718
"cors": "^2.8.5",
1819
"dotenv": "^16.4.5",
1920
"express": "^4.21.1",

0 commit comments

Comments
 (0)