diff --git a/backend/api-gateway/handlers/constants.go b/backend/api-gateway/handlers/constants.go index 20c293a..260f499 100644 --- a/backend/api-gateway/handlers/constants.go +++ b/backend/api-gateway/handlers/constants.go @@ -21,6 +21,7 @@ const ( SUCCESS_USER_FOUND = "User Found!" SUCCESS_USER_CREATED = "User Created Successfully!" SUCCESS_USER_DELETED = "User Deleted Successfully!" + SUCCESS_USER_UPDATED = "User Updated Successfully!" SUCCESS_LOGIN = "Login Successfully!" SUCCESS_LOGOUT = "Logout Successfully!" SUCCESS_ROLE_UPGRADED = "User Role Upgraded Successfully!" diff --git a/backend/api-gateway/handlers/user.go b/backend/api-gateway/handlers/user.go index ad66b2b..9ccb09c 100644 --- a/backend/api-gateway/handlers/user.go +++ b/backend/api-gateway/handlers/user.go @@ -6,7 +6,11 @@ import ( "api-gateway/utils/client" "api-gateway/utils/cookie" "api-gateway/utils/message" + "bytes" + "encoding/json" "net/http" + "os" + "strconv" "github.com/go-playground/validator/v10" "github.com/labstack/echo/v4" @@ -140,3 +144,34 @@ func CreateUser(c echo.Context) error { return c.JSON(http.StatusCreated, message.CreateSuccessUserMessage(SUCCESS_USER_CREATED, newUser)) } + +func UpdateUser(c echo.Context) error { + tokenClaims := c.Get(TOKEN_CLAIMS_CONTEXT_KEY).(*models.Claims) + + currentUser := tokenClaims.User + authId := currentUser.ID + + requestBody := new(models.UpdateUser) + if err := c.Bind(requestBody); err != nil { + return c.JSON(http.StatusBadRequest, message.CreateErrorMessage(INVALID_JSON_REQUEST)) + } + + requestBodyJSON, err := json.Marshal(requestBody) + if err != nil { + return c.JSON(http.StatusInternalServerError, message.CreateErrorMessage("Error marshalling request body")) + } + + resp, err := http.Post(os.Getenv("USER_SERVICE_URL") + "/users/" + strconv.FormatUint(uint64(authId), 10), "application/json", bytes.NewBuffer(requestBodyJSON)) + if err != nil { + return c.JSON(http.StatusInternalServerError, message.CreateErrorMessage("Error creating request")) + } + defer resp.Body.Close() + + var updateUserResponse models.UpdateUserResponse + err = json.NewDecoder(resp.Body).Decode(&updateUserResponse) + if err != nil { + return c.JSON(http.StatusInternalServerError, message.CreateErrorMessage(INVALID_DB_ERROR)) + } + + return c.JSON(http.StatusOK, message.CreateSuccessUupdateUserMessage(SUCCESS_USER_UPDATED, updateUserResponse.User)) +} diff --git a/backend/api-gateway/main.go b/backend/api-gateway/main.go index db6d1a3..ea448e3 100755 --- a/backend/api-gateway/main.go +++ b/backend/api-gateway/main.go @@ -38,6 +38,7 @@ func main() { API_GATEWAY.GET(path.AUTH_USER, handlers.GetUser) API_GATEWAY.DELETE(path.AUTH_USER, handlers.DeleteUser) + API_GATEWAY.PUT(path.AUTH_USER_UPDATE, handlers.UpdateUser) API_GATEWAY.POST(path.AUTH_USER_UPGRADE, handlers.UpgradeUser) API_GATEWAY.POST(path.AUTH_USER_DOWNGRADE, handlers.DowngradeUser) API_GATEWAY.GET(path.AUTH_USER_UPGRADE_SUPER_ADMIN, handlers.UpgradeSuperAdmin) diff --git a/backend/api-gateway/models/message.go b/backend/api-gateway/models/message.go index 73a408c..6916881 100644 --- a/backend/api-gateway/models/message.go +++ b/backend/api-gateway/models/message.go @@ -17,3 +17,8 @@ type SuccessUsersMessage struct { type SuccessMessage struct { Message string `json:"message"` } + +type SuccessUpdateUserMessage struct { + Message string `json:"message"` + User UserServiceUser `json:"user,omitempty"` +} diff --git a/backend/api-gateway/models/user.go b/backend/api-gateway/models/user.go index 840acc2..9ecde40 100644 --- a/backend/api-gateway/models/user.go +++ b/backend/api-gateway/models/user.go @@ -28,3 +28,22 @@ type UserServiceCreateUserRequestBody struct { PhotoUrl string `json:"photo_url"` PreferredLanguage string `json:"preferred_language"` } + +type UpdateUser struct { + Username string `json:"username"` + PhotoUrl string `json:"photo_url"` + PreferredLanguage string `json:"preferred_language"` +} + +type UserServiceUser struct { + gorm.Model + AuthUserID uint `json:"auth_user_id"` + Username string `json:"username"` + PhotoUrl string `json:"photo_url"` + PreferredLanguage string `json:"preferred_language"` +} + +type UpdateUserResponse struct { + Message string `json:"message"` + User UserServiceUser `json:"user"` +} diff --git a/backend/api-gateway/utils/message/message.go b/backend/api-gateway/utils/message/message.go index ddb47b7..7533b32 100644 --- a/backend/api-gateway/utils/message/message.go +++ b/backend/api-gateway/utils/message/message.go @@ -33,3 +33,10 @@ func CreateSuccessUsersMessage(message string, users []models.User) models.Succe Users: users, } } + +func CreateSuccessUupdateUserMessage(message string, user models.UserServiceUser) models.SuccessUpdateUserMessage { + return models.SuccessUpdateUserMessage{ + Message: message, + User: user, + } +} diff --git a/backend/api-gateway/utils/path/path.go b/backend/api-gateway/utils/path/path.go index ace6315..8ab5d9f 100644 --- a/backend/api-gateway/utils/path/path.go +++ b/backend/api-gateway/utils/path/path.go @@ -11,6 +11,7 @@ const ( AUTH_USER_UPGRADE = "/auth/user/upgrade" AUTH_USER_UPGRADE_SUPER_ADMIN = "/auth/user/upgrade-super-admin" AUTH_USER_DOWNGRADE = "/auth/user/downgrade" + AUTH_USER_UPDATE = "/auth/user/update" ALL_USER_SERVICE = "/users*" ALL_QUESTION_SERVICE = "/questions*" ALL_MATCHING_SERVICE = "/match*" diff --git a/backend/user-service/handlers/users_handlers.go b/backend/user-service/handlers/users_handlers.go index 74e915b..92f06e9 100644 --- a/backend/user-service/handlers/users_handlers.go +++ b/backend/user-service/handlers/users_handlers.go @@ -79,7 +79,7 @@ func CreateUser(c echo.Context) error { } func UpdateUser(c echo.Context) error { - authUserID := c.Param("id") + authUserID := c.Param("authId") intAuthUserId, err := strconv.Atoi(authUserID) if err != nil { @@ -87,7 +87,6 @@ func UpdateUser(c echo.Context) error { } uintAuthUserId := uint(intAuthUserId) - var user model.User if err := config.DB.Where("auth_user_id = ?", authUserID).First(&user).Error; err != nil { return c.JSON(http.StatusNotFound, message.CreateErrorMessage(INVALID_USER_NOT_FOUND)) diff --git a/backend/user-service/main.go b/backend/user-service/main.go index 60382df..caa31cd 100755 --- a/backend/user-service/main.go +++ b/backend/user-service/main.go @@ -26,7 +26,7 @@ func main() { e.POST("/users", handlers.CreateUser) e.GET("/users", handlers.GetUsers) e.GET("/users/:id", handlers.GetUser) - e.PUT("/users/:id", handlers.UpdateUser) + e.POST("/users/:authId", handlers.UpdateUser) e.DELETE("/users/:id", handlers.DeleteUser) e.Logger.Fatal(e.Start(":8080")) diff --git a/frontend/app/profile/page.tsx b/frontend/app/profile/page.tsx index 4b7e142..b241aa5 100644 --- a/frontend/app/profile/page.tsx +++ b/frontend/app/profile/page.tsx @@ -1,177 +1,183 @@ -"use client"; +'use client'; -import { Divider } from "@nextui-org/divider"; -import { Input } from "@nextui-org/input"; -import { Select, SelectItem } from "@nextui-org/select"; -import { LANGUAGES } from "../constants/languages"; -import { useForm, Controller } from "react-hook-form"; -import { Button } from "@nextui-org/button"; -import { UserState, updateUser } from "../libs/redux/slices/userSlice"; -import { Avatar } from "@nextui-org/avatar"; -import ImageUpload from "../components/form/ImageUpload"; -import useAuth from "../hooks/useAuth"; -import { GET, PUT } from "../libs/axios/axios"; -import { useDispatch } from "react-redux"; -import { AxiosResponse } from "axios"; -import { notifyError, notifySuccess } from "../components/toast/notifications"; -import { UserResponse } from "../(auth)/login/page"; -import DeleteAccountModal from "../components/modal/deleteAccountModal"; +import { Divider } from '@nextui-org/divider'; +import { Input } from '@nextui-org/input'; +import { Select, SelectItem } from '@nextui-org/select'; +import { LANGUAGES } from '../constants/languages'; +import { useForm, Controller } from 'react-hook-form'; +import { Button } from '@nextui-org/button'; +import { UserState, updateUser } from '../libs/redux/slices/userSlice'; +import { Avatar } from '@nextui-org/avatar'; +import ImageUpload from '../components/form/ImageUpload'; +import useAuth from '../hooks/useAuth'; +import { GET, PUT } from '../libs/axios/axios'; +import { useDispatch } from 'react-redux'; +import { AxiosResponse } from 'axios'; +import { notifyError, notifySuccess } from '../components/toast/notifications'; +import { UserResponse } from '../(auth)/login/page'; +import DeleteAccountModal from '../components/modal/deleteAccountModal'; interface UpdateUserRequest { - username: string; - photo_url: string; - preferred_language: string; + username: string; + photo_url: string; + preferred_language: string; } export default function Profile() { - const { authId, role } = useAuth(); - const dispatch = useDispatch(); + const { authId, role } = useAuth(); + const dispatch = useDispatch(); - const getUser = async () => { - try { - const userResponse: AxiosResponse = await GET(`/users/${authId}`) - const { user } = userResponse.data - dispatch(updateUser(user)); - return { - username: user.username, - photoUrl: user.photo_url, - preferredLanguage: user.preferred_language - } - } catch (error) { - const message = error.message.data.message; - notifyError(message); - } + const getUser = async () => { + try { + const userResponse: AxiosResponse = await GET(`/users/${authId}`); + const { user } = userResponse.data; + dispatch(updateUser(user)); + return { + username: user.username, + photoUrl: user.photo_url, + preferredLanguage: user.preferred_language, + }; + } catch (error) { + const message = error.message.data.message; + notifyError(message); } + }; - const { - control, - handleSubmit, - setValue, - watch, - reset, - formState: { isDirty, errors }, - } = useForm({ - defaultValues: getUser - }); + const { + control, + handleSubmit, + setValue, + watch, + reset, + formState: { isDirty, errors }, + } = useForm({ + defaultValues: getUser, + }); - const onSubmit = handleSubmit(async (data: UserState) => { - const requestBody: UpdateUserRequest = { - username: data.username, - photo_url: data.photoUrl, - preferred_language: data.preferredLanguage - } - try { - const response: AxiosResponse = await PUT(`/users/${authId}`, requestBody); - const {message, user} = response.data; - dispatch(updateUser(user)); - notifySuccess(message); - reset({}, {keepValues: true}) - } catch (error) { - const message = error.message.data.message; - notifyError(message); - } - }) + const onSubmit = handleSubmit(async (data: UserState) => { + const requestBody: UpdateUserRequest = { + username: data.username, + photo_url: data.photoUrl, + preferred_language: data.preferredLanguage, + }; + try { + const response: AxiosResponse = await PUT(`/auth/user/update`, requestBody); + const { message, user } = response.data; + dispatch(updateUser(user)); + notifySuccess(message); + reset({}, { keepValues: true }); + } catch (error) { + const message = error.message.data.message; + notifyError(message); + } + }); + + const photo = watch('photoUrl'); - const photo = watch('photoUrl'); - - const languages = LANGUAGES + const languages = LANGUAGES; - return ( - <> -
-
-
-

Profile Settings

- - You can update your profile here! - -
- -
- -
-

Username

- value.trim() !== '' - || 'Username cannot be empty or contain only whitespace', - } - }} - name="username" - control={control} - render={({field}) => ( - - )} - /> -
- -
-

Preferred Language

- ( - <> - - - )} - /> -
- -
-

Photo

-
- -
- ( - { - return setValue('photoUrl', value, { shouldDirty: true }); - }} - {...field} - /> - )} - /> -
-
-
- -
-
-

Delete Account

-

- Deleting your account will remove all your information from our database. -

-

- This cannot be undone. -

-
-
- -
-
+ return ( + <> +
+
+
+

Profile Settings

+ You can update your profile here! +
+ +
+ +
+

Username

+ + value.trim() !== '' || 'Username cannot be empty or contain only whitespace', + }, + }} + name="username" + control={control} + render={({ field }) => ( + + )} + /> +
+ +
+

Preferred Language

+ ( + <> + + + )} + /> +
+ +
+

Photo

+
+ +
+ ( + { + return setValue('photoUrl', value, { shouldDirty: true }); + }} + {...field} + /> + )} + />
- - ) +
+
+ +
+
+

Delete Account

+

+ Deleting your account will remove all your information from our database. +

+

This cannot be undone.

+
+
+ +
+
+
+ + ); }