Skip to content

Commit eb963d2

Browse files
Merge branch 'main' into fix-filter-persistence
2 parents 494453d + 9d34c19 commit eb963d2

35 files changed

Lines changed: 1671 additions & 415 deletions

.env.example

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

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/.env.sample

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

backend/logger.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const { createLogger, format, transports } = require('winston');
2+
const { combine, timestamp, printf, colorize, errors } = format;
3+
4+
const logFormat = printf(({ level, message, timestamp, stack }) => {
5+
const stackTrace = stack ? `\n${stack}` : '';
6+
return `${timestamp} ${level}: ${message}${stackTrace}`;
7+
});
8+
9+
const logger = createLogger({
10+
level: process.env.LOG_LEVEL || (process.env.NODE_ENV === 'production' ? 'info' : 'debug'),
11+
format: combine(errors({ stack: true }), timestamp(), logFormat),
12+
transports: [
13+
new transports.Console({ format: combine(errors({ stack: true }), colorize(), timestamp(), logFormat) }),
14+
],
15+
});
16+
17+
module.exports = logger;

backend/models/User.js

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +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-
UserSchema.pre('save', async function (next) {
21+
// ✅ FIXED: no next()
22+
UserSchema.pre('save', async function () {
23+
if (!this.isModified('password')) return;
2224

23-
if (!this.isModified('password'))
24-
return next();
25-
26-
try {
27-
const salt = await bcrypt.genSalt(10);
28-
this.password = await bcrypt.hash(this.password, salt);
29-
next();
30-
} catch (err) {
31-
return next(err);
32-
}
25+
const salt = await bcrypt.genSalt(10);
26+
this.password = await bcrypt.hash(this.password, salt);
3327
});
3428

35-
// Compare passwords during login
29+
// ✅ password comparison
3630
UserSchema.methods.comparePassword = async function (enteredPassword) {
37-
return await bcrypt.compare(enteredPassword, this.password);
31+
return bcrypt.compare(enteredPassword, this.password);
3832
};
3933

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

backend/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
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"
99
},
1010
"keywords": [],
1111
"author": "",
@@ -20,7 +20,9 @@
2020
"express-session": "^1.18.1",
2121
"mongoose": "^8.8.2",
2222
"passport": "^0.7.0",
23-
"passport-local": "^1.0.0"
23+
"passport-local": "^1.0.0",
24+
"winston": "^3.19.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

backend/server.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ const cors = require('cors');
99
// Passport configuration
1010
require('./config/passportConfig');
1111

12+
const logger = require('./logger');
13+
1214
const app = express();
1315

1416
// CORS configuration
@@ -30,10 +32,10 @@ app.use('/api/auth', authRoutes);
3032

3133
// Connect to MongoDB
3234
mongoose.connect(process.env.MONGO_URI, {}).then(() => {
33-
console.log('Connected to MongoDB');
35+
logger.info('Connected to MongoDB');
3436
app.listen(process.env.PORT, () => {
35-
console.log(`Server running on port ${process.env.PORT}`);
37+
logger.info(`Server running on port ${process.env.PORT}`);
3638
});
3739
}).catch((err) => {
38-
console.log('MongoDB connection error:', err);
40+
logger.error('MongoDB connection error', err);
3941
});
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 };

0 commit comments

Comments
 (0)