Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 71 additions & 7 deletions backend/user/controller/user-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,18 @@ export async function createUser(req, res) {
try {
const { username, email, password } = req.body;
if (username && email && password) {
const existingUser = await _findUserByUsernameOrEmail(username, email);
if (existingUser) {
const existingUsername = await _findUserByUsername(username);
if (existingUsername) {
return res
.status(409)
.json({ message: "username or email already exists" });
.json({ message: "username already exists" });
}

const existingEmail = await _findUserByEmail(email);
if (existingEmail) {
return res
.status(409)
.json({ message: "email already exists" });
}

const salt = bcrypt.genSaltSync(10);
Expand Down Expand Up @@ -166,6 +173,11 @@ export async function updateUser(req, res) {
hashedPassword = bcrypt.hashSync(password, salt);
}

let isVerified = user.isVerified;
if (email && email !== user.email) {
isVerified = false;
}

const updatedUser = await _updateUserById(
userId,
username,
Expand All @@ -174,7 +186,9 @@ export async function updateUser(req, res) {
bio,
linkedin,
github,
profilePictureUrl
profilePictureUrl,
isVerified,
user.verificationCode,
);
return res.status(200).json({
message: `Updated data for user ${userId}`,
Expand All @@ -183,7 +197,7 @@ export async function updateUser(req, res) {
} else {
return res.status(400).json({
message:
"No field to update: username and email and password are all missing!",
"No field to update!",
});
}
} catch (err) {
Expand Down Expand Up @@ -251,7 +265,54 @@ export async function deleteUser(req, res) {
}
}

// send email to user
export async function requestVerificationEmail(req, res) {
try {
const { email } = req.body;
if (email) {
const user = await _findUserByEmail(email);
if (!user) {
return res
.status(404)
.json({ message: `User not found with email ${email}` });
}

// For email verification, you can generate a random verification code and save it in the database
const verificationCode =
Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15);

const updatedUser = await _updateUserById(
user.id,
user.username,
user.email,
user.password,
user.bio,
user.linkedin,
user.github,
user.profilePictureUrl,
user.isVerified,
verificationCode
);

// Send email to user
await sendVerificationEmail(email, user.username, verificationCode);

return res
.status(200)
.json({
message: `Sent verification email to user ${user.username}`,
});
} else {
return res.status(400).json({ message: "email is missing!" });
}
} catch (err) {
console.error(err);
return res
.status(500)
.json({ message: "Unknown error when requesting verification email!" });
}
}

export async function verifyUser(req, res) {
try {
const { code } = req.query;
Expand Down Expand Up @@ -367,7 +428,10 @@ export async function resetPasswordUsingCode(req, res) {
hashedPassword,
user.bio,
user.linkedin,
user.github
user.github,
user.profilePictureUrl,
user.isVerified,
user.verificationCode
);
return res.status(200).json({
message: `Reset password for user ${user.username}`,
Expand Down
6 changes: 5 additions & 1 deletion backend/user/model/repository.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ export async function updateUserById(
bio,
linkedin,
github,
profilePictureUrl
profilePictureUrl,
isVerified,
verificationCode
) {
return UserModel.findByIdAndUpdate(
userId,
Expand All @@ -66,6 +68,8 @@ export async function updateUserById(
linkedin,
github,
profilePictureUrl,
isVerified,
verificationCode
},
},
{ new: true } // return the updated user
Expand Down
3 changes: 3 additions & 0 deletions backend/user/routes/user-routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
updateUser,
updateUserPrivilege,
getFileUrl,
requestVerificationEmail,
verifyUser,
} from "../controller/user-controller.js";
import {
Expand All @@ -30,6 +31,8 @@ router.patch(
updateUserPrivilege
);

router.post("/request-verification-email", requestVerificationEmail);

router.get("/verify", verifyUser);

router.post("/request-password-reset", requestPasswordReset);
Expand Down
41 changes: 41 additions & 0 deletions frontend/src/api/user.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -330,3 +330,44 @@ export const resetPasswordWithCode = async (code: string, password: string) => {

return true;
};

export const requestVerificationEmail = async (email: string) => {
if (!email) {
toast.fire({
icon: "error",
title: "Please provide an email",
});
return false;
}

toast.fire({
icon: "info",
title: "Requesting verification email...",
});
const response = await fetch(
`${NEXT_PUBLIC_IAM_USER_SERVICE}/request-verification-email`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ email }),
}
);
const data = await response.json();

if (response.status !== 200) {
toast.fire({
icon: "error",
title: data.message,
});
return false;
}

toast.fire({
icon: "success",
title: "Verification email sent. Please check your email.",
});

return true;
}
38 changes: 25 additions & 13 deletions frontend/src/app/(user)/user/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,38 @@ const ProfilePage = () => {
};

return (
<div className="mx-auto max-w-3xl my-10 p-4">
<div className="mx-auto max-w-xl my-10 p-4">
<h1 className="text-white font-extrabold text-h1">{user?.username}'s Profile!</h1>
<div className="flex flex-col gap-y-4 mt-4">
<h2 className="text-yellow-500 font-bold text-h3">Profile Age</h2>
<span className="text-white font-light text-lg">{calculateAge(user?.createdAt)} days</span>
<img src={user?.profilePictureUrl} alt="Profile Picture" className="w-32 h-32 rounded-full mx-auto" />

<h2 className="text-yellow-500 font-bold text-h3">Email</h2>
<div className="flex my-2 gap-2">
<p className="text-white">{user?.email} </p>
<VerificationSymbol isVerified={user?.isVerified || false}/>
<div className="grid grid-cols-2 my-2 gap-2">
<h2 className="text-yellow-500 font-bold text-lg">PROFILE AGE</h2>
<span className="text-white font-light text-lg">{calculateAge(user?.createdAt)} days</span>
</div>

<h2 className="text-yellow-500 font-bold text-h3">Bio</h2>
{user?.bio && <span className="text-white font-light text-lg">{user?.bio}</span>}
<div className="grid grid-cols-2 my-2 gap-2">
<h2 className="text-yellow-500 font-bold text-lg">EMAIL</h2>
<div className="flex gap-2">
<p className="text-white my-auto text-lg">{user?.email} </p>
<VerificationSymbol isVerified={user?.isVerified || false}/>
</div>
</div>

<h2 className="text-yellow-500 font-bold text-h3">GitHub</h2>
{user?.github && <Link href={user?.github} className="text-white font-light text-lg underline hover:text-yellow-500">{user?.github}</Link>}
<div className="grid grid-cols-2 my-2 gap-2">
<h2 className="text-yellow-500 font-bold text-lg">BIO</h2>
{user?.bio && <span className="text-white font-light text-lg">{user?.bio}</span>}
</div>

<h2 className="text-yellow-500 font-bold text-h3">LinkedIn</h2>
{user?.linkedin && <Link href={user?.linkedin} className="text-white font-light text-lg underline hover:text-yellow-500">{user?.linkedin}</Link>}
<div className="grid grid-cols-2 my-2 gap-2">
<h2 className="text-yellow-500 font-bold text-lg">GITHUB URL</h2>
{user?.github && <Link href={user?.github} className="text-white font-light text-lg underline hover:text-yellow-500">{user?.github}</Link>}
</div>

<div className="grid grid-cols-2 my-2 gap-2">
<h2 className="text-yellow-500 font-bold text-lg">LINKEDIN URL</h2>
{user?.linkedin && <Link href={user?.linkedin} className="text-white font-light text-lg underline hover:text-yellow-500">{user?.linkedin}</Link>}
</div>
</div>
</div>
);
Expand Down
15 changes: 14 additions & 1 deletion frontend/src/app/(user)/user/me/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
getUserId,
updateUser,
getFileUrl,
requestVerificationEmail
} from "@/api/user";
import { useEffect, useRef, useState } from "react";
import { User } from "@/types/user";
Expand Down Expand Up @@ -131,6 +132,18 @@ const ProfilePage = () => {
}
};

// must not trigger form submission
const onVerify = async (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
const res = await requestVerificationEmail(form.getValues("email") || "");
if (res) {
Swal.fire({
icon: "success",
title: "Verification email sent!",
});
}
}

return (
!!token && (
<div className="mx-auto max-w-xl my-10 p-4">
Expand Down Expand Up @@ -216,7 +229,7 @@ const ProfilePage = () => {
render={({ field }) => (
<FormItem>
<FormLabel className="text-yellow-500 text-lg">
EMAIL
EMAIL ({user?.isVerified ? "VERIFIED" : <button className="hover:underline" onClick={onVerify}>VERIFY NOW</button>})
</FormLabel>
<FormControl>
<Input
Expand Down