meti-backend/internal/handler/user_handler.go

394 lines
13 KiB
Go
Raw Normal View History

2025-08-09 15:08:26 +07:00
package handler
import (
"net/http"
"strconv"
"eslogad-be/internal/appcontext"
"eslogad-be/internal/constants"
"eslogad-be/internal/contract"
"eslogad-be/internal/logger"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type UserHandler struct {
userService UserService
userValidator UserValidator
}
func NewUserHandler(userService UserService, userValidator UserValidator) *UserHandler {
return &UserHandler{
userService: userService,
userValidator: userValidator,
}
}
func (h *UserHandler) CreateUser(c *gin.Context) {
var req contract.CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::CreateUser -> request binding failed")
h.sendValidationErrorResponse(c, "Invalid request body", constants.MissingFieldErrorCode)
return
}
validationError, validationErrorCode := h.userValidator.ValidateCreateUserRequest(&req)
if validationError != nil {
logger.FromContext(c).WithError(validationError).Error("UserHandler::CreateUser -> request validation failed")
h.sendValidationErrorResponse(c, validationError.Error(), validationErrorCode)
return
}
userResponse, err := h.userService.CreateUser(c.Request.Context(), &req)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::CreateUser -> Failed to create user from service")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
logger.FromContext(c).Infof("UserHandler::CreateUser -> Successfully created user = %+v", userResponse)
c.JSON(http.StatusCreated, userResponse)
}
2025-08-15 21:17:19 +07:00
func (h *UserHandler) BulkCreateUsers(c *gin.Context) {
var req contract.BulkCreateUsersRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::BulkCreateUsers -> request binding failed")
h.sendValidationErrorResponse(c, "Invalid request body", constants.MissingFieldErrorCode)
return
}
if len(req.Users) == 0 {
h.sendValidationErrorResponse(c, "Users list cannot be empty", constants.MissingFieldErrorCode)
return
}
if len(req.Users) > 100 {
h.sendValidationErrorResponse(c, "Cannot create more than 100 users at once", constants.MissingFieldErrorCode)
return
}
response, err := h.userService.BulkCreateUsers(c.Request.Context(), &req)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::BulkCreateUsers -> Failed to bulk create users")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
statusCode := http.StatusCreated
if response.Summary.Failed > 0 && response.Summary.Succeeded == 0 {
statusCode = http.StatusBadRequest
} else if response.Summary.Failed > 0 {
statusCode = http.StatusMultiStatus
}
logger.FromContext(c).Infof("UserHandler::BulkCreateUsers -> Successfully processed bulk creation: %d succeeded, %d failed",
response.Summary.Succeeded, response.Summary.Failed)
c.JSON(statusCode, contract.BuildSuccessResponse(response))
}
2025-08-09 15:08:26 +07:00
func (h *UserHandler) UpdateUser(c *gin.Context) {
userIDStr := c.Param("id")
userID, err := uuid.Parse(userIDStr)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::UpdateUser -> Invalid user ID")
h.sendValidationErrorResponse(c, "Invalid user ID", constants.MalformedFieldErrorCode)
return
}
validationError, validationErrorCode := h.userValidator.ValidateUserID(userID)
if validationError != nil {
logger.FromContext(c).WithError(validationError).Error("UserHandler::UpdateUser -> user ID validation failed")
h.sendValidationErrorResponse(c, validationError.Error(), validationErrorCode)
return
}
var req contract.UpdateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::UpdateUser -> request binding failed")
h.sendValidationErrorResponse(c, "Invalid request body", constants.MissingFieldErrorCode)
return
}
validationError, validationErrorCode = h.userValidator.ValidateUpdateUserRequest(&req)
if validationError != nil {
logger.FromContext(c).WithError(validationError).Error("UserHandler::UpdateUser -> request validation failed")
h.sendValidationErrorResponse(c, validationError.Error(), validationErrorCode)
return
}
userResponse, err := h.userService.UpdateUser(c.Request.Context(), userID, &req)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::UpdateUser -> Failed to update user from service")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
logger.FromContext(c).Infof("UserHandler::UpdateUser -> Successfully updated user = %+v", userResponse)
c.JSON(http.StatusOK, userResponse)
}
func (h *UserHandler) DeleteUser(c *gin.Context) {
userIDStr := c.Param("id")
userID, err := uuid.Parse(userIDStr)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::DeleteUser -> Invalid user ID")
h.sendValidationErrorResponse(c, "Invalid user ID", constants.MalformedFieldErrorCode)
return
}
validationError, validationErrorCode := h.userValidator.ValidateUserID(userID)
if validationError != nil {
logger.FromContext(c).WithError(validationError).Error("UserHandler::DeleteUser -> user ID validation failed")
h.sendValidationErrorResponse(c, validationError.Error(), validationErrorCode)
return
}
err = h.userService.DeleteUser(c.Request.Context(), userID)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::DeleteUser -> Failed to delete user from service")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
logger.FromContext(c).Info("UserHandler::DeleteUser -> Successfully deleted user")
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "User deleted successfully"})
}
func (h *UserHandler) GetUser(c *gin.Context) {
userIDStr := c.Param("id")
userID, err := uuid.Parse(userIDStr)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::GetUser -> Invalid user ID")
h.sendValidationErrorResponse(c, "Invalid user ID", constants.MalformedFieldErrorCode)
return
}
validationError, validationErrorCode := h.userValidator.ValidateUserID(userID)
if validationError != nil {
logger.FromContext(c).WithError(validationError).Error("UserHandler::GetUser -> user ID validation failed")
h.sendValidationErrorResponse(c, validationError.Error(), validationErrorCode)
return
}
userResponse, err := h.userService.GetUserByID(c.Request.Context(), userID)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::GetUser -> Failed to get user from service")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
logger.FromContext(c).Infof("UserHandler::GetUser -> Successfully retrieved user = %+v", userResponse)
c.JSON(http.StatusOK, userResponse)
}
func (h *UserHandler) ListUsers(c *gin.Context) {
ctx := c.Request.Context()
req := &contract.ListUsersRequest{
Page: 1,
Limit: 10,
}
if page := c.Query("page"); page != "" {
if p, err := strconv.Atoi(page); err == nil {
req.Page = p
}
}
if limit := c.Query("limit"); limit != "" {
if l, err := strconv.Atoi(limit); err == nil {
req.Limit = l
}
}
2025-08-09 23:44:03 +07:00
var roleParam *string
2025-08-09 15:08:26 +07:00
if role := c.Query("role"); role != "" {
2025-08-09 23:44:03 +07:00
roleParam = &role
2025-08-09 15:08:26 +07:00
req.Role = &role
}
2025-08-09 23:44:03 +07:00
if roleCode := c.Query("role_code"); roleCode != "" {
req.RoleCode = &roleCode
}
if req.RoleCode == nil && roleParam != nil {
req.RoleCode = roleParam
}
if search := c.Query("search"); search != "" {
req.Search = &search
}
2025-08-09 15:08:26 +07:00
if isActiveStr := c.Query("is_active"); isActiveStr != "" {
if isActive, err := strconv.ParseBool(isActiveStr); err == nil {
req.IsActive = &isActive
}
}
validationError, validationErrorCode := h.userValidator.ValidateListUsersRequest(req)
if validationError != nil {
logger.FromContext(c).WithError(validationError).Error("UserHandler::ListUsers -> request validation failed")
h.sendValidationErrorResponse(c, validationError.Error(), validationErrorCode)
return
}
usersResponse, err := h.userService.ListUsers(ctx, req)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::ListUsers -> Failed to list users from service")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
logger.FromContext(c).Infof("UserHandler::ListUsers -> Successfully listed users = %+v", usersResponse)
c.JSON(http.StatusOK, contract.BuildSuccessResponse(usersResponse))
}
func (h *UserHandler) ChangePassword(c *gin.Context) {
userIDStr := c.Param("id")
userID, err := uuid.Parse(userIDStr)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::ChangePassword -> Invalid user ID")
h.sendValidationErrorResponse(c, "Invalid user ID", constants.MalformedFieldErrorCode)
return
}
validationError, validationErrorCode := h.userValidator.ValidateUserID(userID)
if validationError != nil {
logger.FromContext(c).WithError(validationError).Error("UserHandler::ChangePassword -> user ID validation failed")
h.sendValidationErrorResponse(c, validationError.Error(), validationErrorCode)
return
}
var req contract.ChangePasswordRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::ChangePassword -> request binding failed")
h.sendValidationErrorResponse(c, "Invalid request body", constants.MissingFieldErrorCode)
return
}
validationError, validationErrorCode = h.userValidator.ValidateChangePasswordRequest(&req)
if validationError != nil {
logger.FromContext(c).WithError(validationError).Error("UserHandler::ChangePassword -> request validation failed")
h.sendValidationErrorResponse(c, validationError.Error(), validationErrorCode)
return
}
err = h.userService.ChangePassword(c.Request.Context(), userID, &req)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::ChangePassword -> Failed to change password from service")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
logger.FromContext(c).Info("UserHandler::ChangePassword -> Successfully changed password")
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "Password changed successfully"})
}
func (h *UserHandler) GetProfile(c *gin.Context) {
appCtx := appcontext.FromGinContext(c.Request.Context())
if appCtx.UserID == uuid.Nil {
h.sendErrorResponse(c, "Unauthorized", http.StatusUnauthorized)
return
}
profile, err := h.userService.GetProfile(c.Request.Context(), appCtx.UserID)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::GetProfile -> Failed to get profile")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
c.JSON(http.StatusOK, contract.BuildSuccessResponse(profile))
}
func (h *UserHandler) UpdateProfile(c *gin.Context) {
appCtx := appcontext.FromGinContext(c.Request.Context())
if appCtx.UserID == uuid.Nil {
h.sendErrorResponse(c, "Unauthorized", http.StatusUnauthorized)
return
}
var req contract.UpdateUserProfileRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.sendValidationErrorResponse(c, "Invalid request body", constants.MissingFieldErrorCode)
return
}
updated, err := h.userService.UpdateProfile(c.Request.Context(), appCtx.UserID, &req)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::UpdateProfile -> Failed to update profile")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
c.JSON(http.StatusOK, contract.BuildSuccessResponse(updated))
}
func (h *UserHandler) ListTitles(c *gin.Context) {
2025-08-15 21:17:19 +07:00
titles, err := h.userService.ListTitles(c.Request.Context())
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::ListTitles -> Failed to get titles from service")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
logger.FromContext(c).Infof("UserHandler::ListTitles -> Successfully retrieved titles = %+v", titles)
c.JSON(http.StatusOK, titles)
}
func (h *UserHandler) GetActiveUsersForMention(c *gin.Context) {
search := c.Query("search")
limitStr := c.DefaultQuery("limit", "50")
limit, err := strconv.Atoi(limitStr)
if err != nil || limit <= 0 {
limit = 50
}
if limit > 100 {
limit = 100
}
var searchPtr *string
if search != "" {
searchPtr = &search
}
users, err := h.userService.GetActiveUsersForMention(c.Request.Context(), searchPtr, limit)
2025-08-09 15:08:26 +07:00
if err != nil {
2025-08-15 21:17:19 +07:00
logger.FromContext(c).WithError(err).Error("UserHandler::GetActiveUsersForMention -> Failed to get active users from service")
2025-08-09 15:08:26 +07:00
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
2025-08-15 21:17:19 +07:00
response := contract.MentionUsersResponse{
Users: users,
Count: len(users),
}
logger.FromContext(c).Infof("UserHandler::GetActiveUsersForMention -> Successfully retrieved %d active users", len(users))
c.JSON(http.StatusOK, response)
2025-08-09 15:08:26 +07:00
}
func (h *UserHandler) sendErrorResponse(c *gin.Context, message string, statusCode int) {
errorResponse := &contract.ErrorResponse{
Error: message,
Code: statusCode,
Details: map[string]interface{}{},
}
c.JSON(statusCode, errorResponse)
}
func (h *UserHandler) sendValidationErrorResponse(c *gin.Context, message string, errorCode string) {
statusCode := constants.HttpErrorMap[errorCode]
if statusCode == 0 {
statusCode = http.StatusBadRequest
}
errorResponse := &contract.ErrorResponse{
Error: message,
Code: statusCode,
Details: map[string]interface{}{
"error_code": errorCode,
"entity": constants.UserValidatorEntity,
},
}
c.JSON(statusCode, errorResponse)
}