Skip to content

Commit 2246f81

Browse files
authored
Merge pull request #22 from feliciagan/edit_profile
add edit profile and password functionality
2 parents 26edbd1 + adb23d6 commit 2246f81

File tree

11 files changed

+607
-58
lines changed

11 files changed

+607
-58
lines changed

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

Lines changed: 38 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ export async function createUser(
2626
res: Response
2727
): Promise<Response> {
2828
try {
29-
const { username, email, password } = req.body;
29+
const { username, email, password, firstName, lastName } = req.body;
3030
const existingUser = await _findUserByUsernameOrEmail(username, email);
3131
if (existingUser) {
3232
return res
3333
.status(409)
3434
.json({ message: "username or email already exists" });
3535
}
3636

37-
if (username && email && password) {
37+
if (username && email && password && firstName && lastName) {
3838
const { isValid: isValidUsername, message: usernameMessage } =
3939
validateUsername(username);
4040
if (!isValidUsername) {
@@ -55,15 +55,37 @@ export async function createUser(
5555

5656
const salt = bcrypt.genSaltSync(10);
5757
const hashedPassword = bcrypt.hashSync(password, salt);
58-
const createdUser = await _createUser(username, email, hashedPassword);
58+
59+
const { isValid: isValidFirstName, message: firstNameMessage } =
60+
validateName(firstName, "first name");
61+
if (!isValidFirstName) {
62+
return res.status(400).json({ message: firstNameMessage });
63+
}
64+
65+
const { isValid: isValidLastName, message: lastNameMessage } =
66+
validateName(lastName, "last name");
67+
if (!isValidLastName) {
68+
return res.status(400).json({ message: lastNameMessage });
69+
}
70+
71+
const createdUser = await _createUser(
72+
username,
73+
email,
74+
hashedPassword,
75+
firstName,
76+
lastName
77+
);
5978
return res.status(201).json({
6079
message: `Created new user ${username} successfully`,
6180
data: formatUserResponse(createdUser),
6281
});
6382
} else {
6483
return res
6584
.status(400)
66-
.json({ message: "username and/or email and/or password are missing" });
85+
.json({
86+
message:
87+
"username and/or email and/or password and/or first name and/or last name are missing",
88+
});
6789
}
6890
} catch (err) {
6991
console.error(err);
@@ -120,18 +142,15 @@ export async function updateUser(
120142
): Promise<Response> {
121143
try {
122144
const {
123-
username,
124-
email,
125-
password,
145+
oldPassword,
146+
newPassword,
126147
profilePictureUrl,
127148
firstName,
128149
lastName,
129150
biography,
130151
} = req.body;
131152
if (
132-
username ||
133-
email ||
134-
password ||
153+
(oldPassword && newPassword) ||
135154
profilePictureUrl ||
136155
firstName ||
137156
lastName ||
@@ -148,42 +167,23 @@ export async function updateUser(
148167
return res.status(404).json({ message: `User ${userId} not found` });
149168
}
150169

151-
if (username) {
152-
const { isValid: isValidUsername, message: usernameMessage } =
153-
validateUsername(username);
154-
if (!isValidUsername) {
155-
return res.status(400).json({ message: usernameMessage });
156-
}
157-
158-
const existingUser = await _findUserByUsername(username);
159-
if (existingUser && existingUser.id !== userId) {
160-
return res.status(409).json({ message: "username already exists" });
161-
}
162-
}
163-
164-
if (email) {
165-
const { isValid: isValidEmail, message: emailMessage } =
166-
validateEmail(email);
167-
if (!isValidEmail) {
168-
return res.status(400).json({ message: emailMessage });
169-
}
170-
171-
const existingUser = await _findUserByEmail(email);
172-
if (existingUser && existingUser.id !== userId) {
173-
return res.status(409).json({ message: "email already exists" });
170+
let hashedPassword: string | undefined;
171+
if (oldPassword && newPassword) {
172+
const match = await bcrypt.compare(oldPassword, user.password);
173+
if (!match) {
174+
return res
175+
.status(403)
176+
.json({ message: "Wrong current password given" });
174177
}
175-
}
176178

177-
let hashedPassword: string | undefined;
178-
if (password) {
179179
const { isValid: isValidPassword, message: passwordMessage } =
180-
validatePassword(password);
180+
validatePassword(newPassword);
181181
if (!isValidPassword) {
182182
return res.status(400).json({ message: passwordMessage });
183183
}
184184

185185
const salt = bcrypt.genSaltSync(10);
186-
hashedPassword = bcrypt.hashSync(password, salt);
186+
hashedPassword = bcrypt.hashSync(newPassword, salt);
187187
}
188188

189189
if (firstName) {
@@ -212,8 +212,6 @@ export async function updateUser(
212212

213213
const updatedUser = await _updateUserById(
214214
userId,
215-
username,
216-
email,
217215
hashedPassword,
218216
profilePictureUrl,
219217
firstName,

backend/user-service/model/repository.ts

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

65
export async function connectToDB() {
76
const mongoDBUri: string | undefined = process.env.DB_CLOUD_URI;
@@ -17,14 +16,16 @@ export async function createUser(
1716
username: string,
1817
email: string,
1918
password: string,
19+
firstName: string,
20+
lastName: string,
2021
isAdmin: boolean = false
2122
): Promise<IUser> {
2223
return new UserModel({
2324
username,
2425
email,
2526
password,
26-
firstName: faker.person.firstName(),
27-
lastName: faker.person.lastName(),
27+
firstName,
28+
lastName,
2829
isAdmin,
2930
}).save();
3031
}
@@ -58,8 +59,6 @@ export async function findAllUsers(): Promise<IUser[]> {
5859

5960
export async function updateUserById(
6061
userId: string,
61-
username: string,
62-
email: string,
6362
password: string | undefined,
6463
profilePictureUrl: string,
6564
firstName: string,
@@ -70,8 +69,6 @@ export async function updateUserById(
7069
userId,
7170
{
7271
$set: {
73-
username,
74-
email,
7572
password,
7673
profilePictureUrl,
7774
firstName,

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ export interface IUser extends Document {
88
isAdmin: boolean;
99

1010
profilePictureUrl?: string;
11-
firstName?: string;
12-
lastName?: string;
11+
firstName: string;
12+
lastName: string;
1313
biography?: string;
1414
}
1515

@@ -43,11 +43,11 @@ const UserModelSchema: Schema<IUser> = new mongoose.Schema({
4343
},
4444
firstName: {
4545
type: String,
46-
required: false,
46+
required: true,
4747
},
4848
lastName: {
4949
type: String,
50-
required: false,
50+
required: true,
5151
},
5252
biography: {
5353
type: String,

backend/user-service/swagger.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,58 @@ paths:
173173
required: true
174174
schema:
175175
type: string
176+
requestBody:
177+
content:
178+
application/json:
179+
schema:
180+
type: object
181+
properties:
182+
oldPassword:
183+
type: string
184+
required: false
185+
newPassword:
186+
type: string
187+
required: false
188+
firstName:
189+
type: string
190+
required: false
191+
lastName:
192+
type: string
193+
required: false
194+
biography:
195+
type: string
196+
required: false
197+
responses:
198+
200:
199+
description: Successful Response
200+
content:
201+
application/json:
202+
schema:
203+
$ref: "#/components/schemas/UserResponse"
204+
400:
205+
description: Bad Request
206+
content:
207+
application/json:
208+
schema:
209+
$ref: "#/components/schemas/ErrorResponse"
210+
403:
211+
description: Forbidden
212+
content:
213+
application/json:
214+
schema:
215+
$ref: "#/components/schemas/ErrorResponse"
216+
404:
217+
description: Not Found
218+
content:
219+
application/json:
220+
schema:
221+
$ref: "#/components/schemas/ErrorResponse"
222+
500:
223+
description: Internal Server Error
224+
content:
225+
application/json:
226+
schema:
227+
$ref: "#/components/schemas/ErrorResponse"
176228
delete:
177229
summary: Delete a user account
178230
tags:
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { forwardRef, useState } from 'react';
2+
import { Box, Button, Stack, Typography } from '@mui/material';
3+
import PasswordTextField from '../PasswordTextField';
4+
//import { userClient } from '../../utils/api';
5+
//import axios from 'axios';
6+
//import { FAILED_PW_UPDATE_MESSAGE, SUCCESS_PW_UPDATE_MESSAGE } from '../../utils/constants';
7+
8+
interface ChangePasswordModalProps {
9+
handleClose: () => void;
10+
userId: string;
11+
onUpdate: (message: string, isSuccess: boolean) => void;
12+
}
13+
14+
const ChangePasswordModal = forwardRef<HTMLDivElement, ChangePasswordModalProps>((props, ref) => {
15+
const { handleClose } = props;
16+
//const { handleClose, userId, onUpdate } = props;
17+
const [currPassword, setCurrPassword] = useState<string>('');
18+
const [newPassword, setNewPassword] = useState<string>('');
19+
const [confirmPassword, setConfirmPassword] = useState<string>('');
20+
21+
const [isCurrPasswordValid, setIsCurrPasswordValid] = useState<boolean>(false);
22+
const [isNewPasswordValid, setIsNewPasswordValid] = useState<boolean>(false);
23+
const [isConfirmPasswordValid, setIsConfirmPasswordValid] = useState<boolean>(false);
24+
25+
const isUpdateDisabled = !(isCurrPasswordValid && isNewPasswordValid && isConfirmPasswordValid);
26+
27+
const handleSubmit = async () => {
28+
//TODO: test with token (only tested without)
29+
/*const accessToken = localStorage.getItem("token");
30+
31+
try {
32+
await userClient.patch(
33+
`/users/${userId}`,
34+
{
35+
oldPassword: currPassword,
36+
newPassword: newPassword,
37+
},
38+
{
39+
headers: {
40+
Authorization: `Bearer ${accessToken}`,
41+
"Content-Type": "application/json",
42+
},
43+
});
44+
handleClose();
45+
onUpdate(SUCCESS_PW_UPDATE_MESSAGE, true);
46+
} catch (error) {
47+
if (axios.isAxiosError(error)) {
48+
const message =
49+
error.response?.data.message || FAILED_PW_UPDATE_MESSAGE;
50+
onUpdate(message, false);
51+
} else {
52+
onUpdate(FAILED_PW_UPDATE_MESSAGE, false);
53+
}
54+
}*/
55+
};
56+
57+
return (
58+
<Box
59+
ref={ref}
60+
sx={(theme) => ({
61+
backgroundColor: theme.palette.common.white,
62+
display: "flex",
63+
width: 600,
64+
flexDirection: "column",
65+
alignItems: "center",
66+
borderRadius: '16px',
67+
padding: "40px",
68+
})}
69+
>
70+
<Typography component="h1" variant="h3">
71+
Change Password
72+
</Typography>
73+
<PasswordTextField
74+
label="Current password"
75+
passwordVal={false}
76+
password={currPassword}
77+
setPassword={setCurrPassword}
78+
isMatch={false}
79+
setValidity={setIsCurrPasswordValid} />
80+
<PasswordTextField
81+
label="New password"
82+
passwordVal={true}
83+
password={newPassword}
84+
setPassword={setNewPassword}
85+
isMatch={true}
86+
passwordToMatch={confirmPassword}
87+
setValidity={setIsNewPasswordValid} />
88+
<PasswordTextField
89+
label="Confirm new password"
90+
passwordVal={false}
91+
password={confirmPassword}
92+
setPassword={setConfirmPassword}
93+
isMatch={true}
94+
passwordToMatch={newPassword}
95+
setValidity={setIsConfirmPasswordValid} />
96+
<Stack direction="row" spacing={2} sx={{marginTop: 2, width: '100%'}}>
97+
<Button variant="contained" color="secondary" onClick={handleClose} sx={{ flexGrow: 1 }}>Cancel</Button>
98+
<Button variant="contained" disabled={isUpdateDisabled} onClick={handleSubmit} sx={{ flexGrow: 1 }}>Update</Button>
99+
</Stack>
100+
</Box>
101+
);
102+
});
103+
104+
export default ChangePasswordModal;

0 commit comments

Comments
 (0)