Skip to content

Commit 46b7f5f

Browse files
Sitarasdabouledidiasitarass
authored
Profile (#5)
* draft implementation * refactor: profile controller corrections * feat: profile page * refactor: fix cookies and add favicon * chore: remove unsed code --------- Co-authored-by: dabouledidia <dimitrisdim8900@gmail.com> Co-authored-by: sitaras <sitaras@thinkdesquared.com>
1 parent d2a7618 commit 46b7f5f

Some content is hidden

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

45 files changed

+1247
-419
lines changed

back-end/src/controllers/auth.controller.ts

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,26 @@ export class AuthController {
2424
lastName,
2525
dateOfBirth,
2626
} = req.body;
27-
27+
2828
if (password !== verifyPassword) {
2929
res.error("Passwords do not match");
3030
return;
3131
}
32-
32+
3333
const existingUser = await User.findOne({
3434
$or: [{ email }, { phoneNumber }],
3535
});
36-
36+
3737
if (existingUser) {
3838
res.error("User already exists");
3939
return;
4040
}
41-
41+
4242
const user = new User({
4343
email,
4444
password,
4545
phoneNumber,
4646
});
47-
4847
const profile = new Profile({
4948
user: user._id,
5049
firstName,
@@ -53,13 +52,14 @@ export class AuthController {
5352
email,
5453
phoneNumber,
5554
});
56-
55+
5756
user.profile = profile._id as any;
58-
57+
5958
await Promise.all([user.save(), profile.save()]);
60-
59+
6160
res.success(req.body, "User registered successfully");
6261
} catch (error) {
62+
console.error("Registration error:", error);
6363
res.error("An error occurred", 500);
6464
}
6565
}
@@ -70,30 +70,32 @@ export class AuthController {
7070
): Promise<void> {
7171
try {
7272
const { email, password } = req.body;
73-
73+
7474
const user = await User.findOne({ email });
75+
7576
if (!user) {
7677
res.error("INVALID_CREDENTIALS");
7778
return;
7879
}
79-
80+
8081
const isValidPassword = await user.comparePassword(password);
82+
8183
if (!isValidPassword) {
8284
res.error("INVALID_CREDENTIALS");
8385
return;
8486
}
85-
87+
8688
const tokenPayload = {
8789
userId: user._id as string,
8890
roles: user.roles,
8991
};
90-
92+
9193
const accessToken = tokenUtils.generateAccessToken(tokenPayload);
9294
const refreshToken = tokenUtils.generateRefreshToken(tokenPayload);
93-
95+
9496
user.refreshTokens.push({ token: refreshToken, createdAt: new Date() });
9597
await user.save();
96-
98+
9799
res.success(
98100
{
99101
tokens: {
@@ -114,51 +116,50 @@ export class AuthController {
114116
): Promise<void> {
115117
try {
116118
const { refreshToken } = req.body;
117-
119+
118120
const user = await User.findOne({
119121
"refreshTokens.token": refreshToken,
120122
});
121-
123+
122124
if (!user) {
123125
res.error("Unauthorized", 401);
124126
return;
125127
}
126-
128+
127129
try {
128130
const decoded = verifyRefreshToken(refreshToken);
129-
131+
130132
if (
131133
typeof decoded === "string" ||
132134
(user._id as string).toString() !== decoded.userId
133135
) {
134136
res.error("Unauthorized", 401);
135137
return;
136138
}
137-
139+
138140
user.refreshTokens = user.refreshTokens.filter((token) => {
139141
const tokenAge = Date.now() - token.createdAt.getTime();
140142
return tokenAge < 7 * 24 * 60 * 60 * 1000; // 7 days
141143
});
142-
144+
143145
const tokenPayload = {
144146
userId: user._id as string,
145147
roles: user.roles,
146148
};
147-
149+
148150
const newAccessToken = tokenUtils.generateAccessToken(tokenPayload);
149151
const newRefreshToken = tokenUtils.generateRefreshToken(tokenPayload);
150-
152+
151153
user.refreshTokens = user.refreshTokens.filter(
152154
(token) => token.token !== refreshToken
153155
);
154-
155156
user.refreshTokens.push({
156157
token: newRefreshToken,
157158
createdAt: new Date(),
158159
});
159-
160+
160161
await user.save();
161-
162+
162163
res.success(
163164
{
164165
accessToken: newAccessToken,
@@ -174,4 +175,4 @@ export class AuthController {
174175
res.error("An error occurred", 500);
175176
}
176177
}
177-
}
178+
}

back-end/src/controllers/user.controller.ts

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { Response } from "express";
22
import { User } from "../models/user.model";
3+
import { Profile } from "../models/profile.model";
34
import { AuthRequest } from "@/middleware/auth.middleware";
5+
import { StatusCodes } from "http-status-codes";
6+
import { UpdateProfilePayload } from "@/schemas/profileSchema";
47

58
export class UserController {
69
static async getUserInfo(req: AuthRequest, res: Response) {
@@ -11,17 +14,63 @@ export class UserController {
1114
.select("-password -refreshTokens -__v -_id")
1215
.populate({
1316
path: "profile",
14-
select: "-__v -createdAt -updatedAt -_id"
17+
select: "-__v -createdAt -updatedAt -_id",
1518
});
1619

1720
if (!user) {
18-
res.error("Not found", 404);
21+
res.error("Not found", StatusCodes.NOT_FOUND);
1922
return;
2023
}
2124

2225
res.success(user);
23-
} catch (error: Error) {
24-
res.error("An error occurred", 500);
26+
} catch (error: any) {
27+
res.error("An error occurred", StatusCodes.INTERNAL_SERVER_ERROR);
28+
return;
29+
}
30+
}
31+
32+
static async getProfile(req: AuthRequest, res: Response) {
33+
try {
34+
const userId = req.user?.userId;
35+
36+
const profile = await Profile.findOne({ user: userId });
37+
38+
if (!profile) {
39+
res.error("Profile not found", StatusCodes.NOT_FOUND);
40+
return;
41+
}
42+
43+
res.success(profile);
44+
} catch (error) {
45+
console.error("Get profile error:", error);
46+
res.error("An error occurred", StatusCodes.INTERNAL_SERVER_ERROR);
47+
return;
48+
}
49+
}
50+
51+
static async updateProfile(
52+
req: AuthRequest<{}, {}, UpdateProfilePayload>,
53+
res: Response
54+
) {
55+
try {
56+
const userId = req.user?.userId;
57+
const updateData = req.body;
58+
59+
const profile = await Profile.findOneAndUpdate(
60+
{ user: userId },
61+
{ $set: updateData },
62+
{ new: true, runValidators: true }
63+
);
64+
65+
if (!profile) {
66+
res.error("Profile not found", StatusCodes.NOT_FOUND);
67+
return;
68+
}
69+
70+
res.success(profile);
71+
} catch (error: any) {
72+
console.error("Profile update error:", error);
73+
res.error("An error occurred", StatusCodes.INTERNAL_SERVER_ERROR);
2574
return;
2675
}
2776
}

back-end/src/models/profile.model.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,32 +32,44 @@ const profileSchema = new Schema<IProfileDocument>(
3232
},
3333
avatar: {
3434
type: String,
35+
default: "",
3536
},
3637
bio: {
3738
type: String,
3839
maxlength: 500,
40+
default: "",
3941
},
4042
socials: {
4143
facebook: {
4244
type: String,
45+
default: "",
4346
},
4447
twitter: {
4548
type: String,
49+
default: "",
4650
},
4751
linkedIn: {
4852
type: String,
53+
default: "",
4954
},
5055
},
56+
5157
verificationDocuments: {
5258
idCard: {
53-
url: String,
59+
url: {
60+
type: String,
61+
default: "",
62+
},
5463
verified: {
5564
type: Boolean,
5665
default: false,
5766
},
5867
},
5968
drivingLicense: {
60-
url: String,
69+
url: {
70+
type: String,
71+
default: "",
72+
},
6173
verified: {
6274
type: Boolean,
6375
default: false,

back-end/src/routes/user.routes.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
import { Router } from "express";
22
import { authenticateToken } from "../middleware/auth.middleware";
33
import { UserController } from "../controllers/user.controller";
4+
import { validateData } from "../middleware/validationMiddleware";
5+
import { updateProfileSchema } from "../schemas/profileSchema";
46

57
const router = Router();
68

79
router.use(authenticateToken);
810

911
router.get("/user-info", UserController.getUserInfo);
1012

13+
router.get("/profile", UserController.getProfile);
14+
router.patch(
15+
"/profile",
16+
validateData(updateProfileSchema),
17+
UserController.updateProfile
18+
);
19+
1120
export const userRoutes = router;
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { z } from "zod";
22

33
export const loginSchema = z.object({
4-
email: z.string().min(1, { message: "required" }),
4+
email: z
5+
.string()
6+
.min(1, { message: "required" })
7+
.email({ message: "emailError" }),
58
password: z.string().min(1, { message: "required" }),
69
});

back-end/src/schemas/auth/registerSchema.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,7 @@ const passwordSchema = z
2222
});
2323

2424
export const registerSchema = z.object({
25-
email: z
26-
.string()
27-
.min(1, { message: "required" })
28-
.email({ message: "Invalid format" }),
25+
email: z.string().trim().email({ message: "emailError" }),
2926
firstName: z.string().min(1, { message: "required" }),
3027
lastName: z.string().min(1, { message: "required" }),
3128
phoneNumber: z
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { z } from "zod";
2+
import { isoDateSchema } from "./isoDateSchema";
3+
4+
const greekPhoneNumber = new RegExp(/^(?:[0-9]{10})$/);
5+
6+
export const updateProfileSchema = z.object({
7+
firstName: z.string().trim().min(1, { message: "required" }).optional(),
8+
9+
lastName: z.string().trim().min(1, { message: "required" }).optional(),
10+
11+
dateOfBirth: isoDateSchema.optional(),
12+
13+
email: z.string().trim().email({ message: "emailError" }).optional(),
14+
15+
phoneNumber: z
16+
.string()
17+
.trim()
18+
.regex(greekPhoneNumber, {
19+
message: "Invalid format",
20+
})
21+
.optional(),
22+
23+
bio: z.string().trim().max(500, "Maximum 500 characters").optional(),
24+
25+
socials: z
26+
.object({
27+
facebook: z.string().url("Invalid URL").or(z.literal("")).optional(),
28+
twitter: z.string().url("Invalid URL").or(z.literal("")).optional(),
29+
linkedIn: z.string().url("Invalid URL").or(z.literal("")).optional(),
30+
})
31+
.optional(),
32+
});
33+
34+
export type UpdateProfilePayload = z.infer<typeof updateProfileSchema>;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { z } from "zod";
2+
3+
export const uploadAvatarSchema = z.object({
4+
avatar: z
5+
.instanceof(File)
6+
.refine((file) => file.size <= 2 * 1024 * 1024, {
7+
message: "File size must be less than 2MB",
8+
})
9+
.refine(
10+
(file) =>
11+
["image/jpeg", "image/jpg", "image/png", "image/webp"].includes(
12+
file.type
13+
),
14+
{
15+
message: "Only JPEG, PNG, and WebP images are allowed",
16+
}
17+
),
18+
});
19+
20+
export type UploadAvatarSchemaPayload = z.infer<typeof uploadAvatarSchema>;
14.8 KB
Loading
39 KB
Loading

0 commit comments

Comments
 (0)