Skip to content

Commit 90535e8

Browse files
committed
Add additional information to User schema
1 parent 622d15c commit 90535e8

File tree

8 files changed

+200
-12
lines changed

8 files changed

+200
-12
lines changed

backend/user-service/controller/auth-controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export async function handleLogin(req: AuthenticatedRequest, res: Response): Pro
2525
},
2626
process.env.JWT_SECRET as string,
2727
{
28-
expiresIn: "1d",
28+
expiresIn: "7d",
2929
}
3030
);
3131
return res

backend/user-service/controller/user-controller.ts

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,31 @@ import {
1212
updateUserById as _updateUserById,
1313
updateUserPrivilegeById as _updateUserPrivilegeById,
1414
} from "../model/repository";
15+
import { validateEmail, validateUsername, validatePassword } from "../utils/validators";
1516
import { IUser } from "../model/user-model";
1617

1718
export async function createUser(req: Request, res: Response): Promise<Response> {
1819
try {
1920
const { username, email, password } = req.body;
21+
const existingUser = await _findUserByUsernameOrEmail(username, email);
22+
if (existingUser) {
23+
return res.status(409).json({ message: "username or email already exists" });
24+
}
25+
2026
if (username && email && password) {
21-
const existingUser = await _findUserByUsernameOrEmail(username, email);
22-
if (existingUser) {
23-
return res.status(409).json({ message: "username or email already exists" });
27+
const { isValid: isValidUsername, message: usernameMessage } = validateUsername(username);
28+
if (!isValidUsername) {
29+
return res.status(400).json({ message: usernameMessage });
30+
}
31+
32+
const { isValid: isValidEmail, message: emailMessage } = validateEmail(email);
33+
if (!isValidEmail) {
34+
return res.status(400).json({ message: emailMessage });
35+
}
36+
37+
const { isValid: isValidPassword, message: passwordMessage } = validatePassword(password);
38+
if (!isValidPassword) {
39+
return res.status(400).json({ message: passwordMessage });
2440
}
2541

2642
const salt = bcrypt.genSaltSync(10);
@@ -71,8 +87,17 @@ export async function getAllUsers(req: Request, res: Response): Promise<Response
7187

7288
export async function updateUser(req: Request, res: Response): Promise<Response> {
7389
try {
74-
const { username, email, password } = req.body;
75-
if (username || email || password) {
90+
const { username, email, password, profile_picture_url, first_name, last_name, biography } =
91+
req.body;
92+
if (
93+
username ||
94+
email ||
95+
password ||
96+
profile_picture_url ||
97+
first_name ||
98+
last_name ||
99+
biography
100+
) {
76101
const userId = req.params.id;
77102
if (!isValidObjectId(userId)) {
78103
return res.status(404).json({ message: `User ${userId} not found` });
@@ -92,12 +117,21 @@ export async function updateUser(req: Request, res: Response): Promise<Response>
92117
}
93118
}
94119

95-
let hashedPassword: string = "";
120+
let hashedPassword: string | undefined;
96121
if (password) {
97122
const salt = bcrypt.genSaltSync(10);
98123
hashedPassword = bcrypt.hashSync(password, salt);
99124
}
100-
const updatedUser = await _updateUserById(userId, username, email, hashedPassword);
125+
const updatedUser = await _updateUserById(
126+
userId,
127+
username,
128+
email,
129+
hashedPassword,
130+
profile_picture_url,
131+
first_name,
132+
last_name,
133+
biography
134+
);
101135
return res.status(200).json({
102136
message: `Updated data for user ${userId}`,
103137
data: formatUserResponse(updatedUser as IUser),
@@ -168,5 +202,10 @@ export function formatUserResponse(user: IUser) {
168202
email: user.email,
169203
isAdmin: user.isAdmin,
170204
createdAt: user.createdAt,
205+
206+
profile_picture_url: user.profile_picture_url,
207+
first_name: user.first_name,
208+
last_name: user.last_name,
209+
biography: user.biography,
171210
};
172211
}

backend/user-service/model/repository.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import UserModel, { IUser } from "./user-model";
22
import "dotenv/config";
33
import { connect } from "mongoose";
4+
import { faker } from "@faker-js/faker";
45

56
export async function connectToDB() {
67
const mongoDBUri: string | undefined = process.env.DB_CLOUD_URI;
@@ -17,7 +18,13 @@ export async function createUser(
1718
email: string,
1819
password: string
1920
): Promise<IUser> {
20-
return new UserModel({ username, email, password }).save();
21+
return new UserModel({
22+
username,
23+
email,
24+
password,
25+
first_name: faker.person.firstName(),
26+
last_name: faker.person.lastName(),
27+
}).save();
2128
}
2229

2330
export async function findUserByEmail(email: string): Promise<IUser | null> {
@@ -49,7 +56,11 @@ export async function updateUserById(
4956
userId: string,
5057
username: string,
5158
email: string,
52-
password: string
59+
password: string | undefined,
60+
profile_picture_url: string,
61+
first_name: string,
62+
last_name: string,
63+
biography: string
5364
): Promise<IUser | null> {
5465
return UserModel.findByIdAndUpdate(
5566
userId,
@@ -58,6 +69,10 @@ export async function updateUserById(
5869
username,
5970
email,
6071
password,
72+
profile_picture_url,
73+
first_name,
74+
last_name,
75+
biography,
6176
},
6277
},
6378
{ new: true } // return the updated user

backend/user-service/model/user-model.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ export interface IUser extends Document {
66
password: string;
77
createdAt?: Date;
88
isAdmin: boolean;
9+
10+
profile_picture_url?: string;
11+
first_name?: string;
12+
last_name?: string;
13+
biography?: string;
914
}
1015

1116
const UserModelSchema: Schema<IUser> = new mongoose.Schema({
@@ -32,6 +37,23 @@ const UserModelSchema: Schema<IUser> = new mongoose.Schema({
3237
required: true,
3338
default: false,
3439
},
40+
profile_picture_url: {
41+
type: String,
42+
required: false,
43+
},
44+
first_name: {
45+
type: String,
46+
required: false,
47+
},
48+
last_name: {
49+
type: String,
50+
required: false,
51+
},
52+
biography: {
53+
type: String,
54+
required: false,
55+
default: "Hello World!",
56+
},
3557
});
3658

3759
const UserModel = mongoose.model<IUser>("UserModel", UserModelSchema);

backend/user-service/package-lock.json

Lines changed: 33 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/user-service/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,19 @@
1717
"@types/express": "^4.17.21",
1818
"@types/jsonwebtoken": "^9.0.7",
1919
"@types/node": "^22.5.5",
20+
"@types/validator": "^13.12.2",
2021
"nodemon": "^3.1.4",
2122
"tsx": "^4.19.1",
2223
"typescript": "^5.6.2"
2324
},
2425
"dependencies": {
26+
"@faker-js/faker": "^9.0.1",
2527
"bcrypt": "^5.1.1",
2628
"cors": "^2.8.5",
2729
"dotenv": "^16.4.5",
2830
"express": "^4.19.2",
2931
"jsonwebtoken": "^9.0.2",
30-
"mongoose": "^8.5.4"
32+
"mongoose": "^8.5.4",
33+
"validator": "^13.12.0"
3134
}
3235
}

backend/user-service/types/request.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,9 @@ export interface AuthenticatedRequest extends Request {
66
username: string;
77
email: string;
88
isAdmin: boolean;
9+
profile_picture_url?: string;
10+
first_name?: string;
11+
last_name?: string;
12+
biography?: string;
913
};
1014
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import validator from "validator";
2+
3+
export function validateEmail(email: string): { isValid: boolean; message: string | null } {
4+
if (!validator.isEmail(email)) {
5+
return { isValid: false, message: "Email format is invalid" };
6+
}
7+
return { isValid: true, message: null };
8+
}
9+
10+
export function validatePassword(password: string): { isValid: boolean; message: string | null } {
11+
if (password.length < 8) {
12+
return { isValid: false, message: "Password must be at least 8 characters long" };
13+
}
14+
15+
if (!/[a-z]/.test(password)) {
16+
return { isValid: false, message: "Password must contain at least 1 lowercase letter" };
17+
}
18+
19+
if (!/[A-Z]/.test(password)) {
20+
return { isValid: false, message: "Password must contain at least 1 uppercase letter" };
21+
}
22+
23+
if (!/\d/.test(password)) {
24+
return { isValid: false, message: "Password must contain at least 1 digit" };
25+
}
26+
27+
if (!/[ `!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?~]/.test(password)) {
28+
return { isValid: false, message: "Password must contain at least 1 special character" };
29+
}
30+
31+
return { isValid: true, message: null };
32+
}
33+
34+
export function validateUsername(username: string): { isValid: boolean; message: string | null } {
35+
if (!validator.isLength(username, { min: 6, max: 30 })) {
36+
return { isValid: false, message: "Username must be between 6 and 30 characters long" };
37+
}
38+
39+
if (!/^[a-zA-Z0-9._]+$/.test(username)) {
40+
return {
41+
isValid: false,
42+
message: "Username must only contain alphanumeric characters, underscores, and full stops",
43+
};
44+
}
45+
46+
return { isValid: true, message: null };
47+
}
48+
49+
export function validateName(
50+
name: string,
51+
type: "first name" | "last name"
52+
): { isValid: boolean; message: string | null } {
53+
if (!validator.isLength(name, { max: 50 })) {
54+
return { isValid: false, message: `${type} must be at most 50 characters long` };
55+
}
56+
57+
if (!/^[a-zA-Z0-9\s]+$/.test(name)) {
58+
return {
59+
isValid: false,
60+
message: `${type} must only contain alphanumeric characters and white spaces`,
61+
};
62+
}
63+
64+
return { isValid: true, message: null };
65+
}
66+
67+
export function validateBiography(biography: string): { isValid: boolean; message: string | null } {
68+
if (!validator.isLength(biography, { max: 255 })) {
69+
return { isValid: false, message: "Biography must be at most 255 characters long" };
70+
}
71+
72+
return { isValid: true, message: null };
73+
}

0 commit comments

Comments
 (0)