383 lines
13 KiB
Go
383 lines
13 KiB
Go
package controller
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
// "github.com/Caknoooo/go-gin-clean-starter/database/entities"
|
|
authDto "github.com/Caknoooo/go-gin-clean-starter/modules/auth/dto"
|
|
"github.com/Caknoooo/go-gin-clean-starter/modules/user/dto"
|
|
"github.com/Caknoooo/go-gin-clean-starter/modules/user/query"
|
|
"github.com/Caknoooo/go-gin-clean-starter/modules/user/service"
|
|
"github.com/Caknoooo/go-gin-clean-starter/pkg/constants"
|
|
"github.com/Caknoooo/go-gin-clean-starter/pkg/utils"
|
|
"github.com/Caknoooo/go-pagination"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/samber/do"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type (
|
|
UserController interface {
|
|
Register(ctx *gin.Context)
|
|
Login(ctx *gin.Context)
|
|
Me(ctx *gin.Context)
|
|
GetUserById(ctx *gin.Context)
|
|
Refresh(ctx *gin.Context)
|
|
GetAllUser(ctx *gin.Context)
|
|
SendVerificationEmail(ctx *gin.Context)
|
|
VerifyEmail(ctx *gin.Context)
|
|
Update(ctx *gin.Context)
|
|
Delete(ctx *gin.Context)
|
|
Create(ctx *gin.Context)
|
|
}
|
|
|
|
userController struct {
|
|
userService service.UserService
|
|
db *gorm.DB
|
|
}
|
|
)
|
|
|
|
// Create implements UserController.
|
|
func (c *userController) Create(ctx *gin.Context) {
|
|
var req dto.UserCreateRequest
|
|
if err := ctx.ShouldBind(&req); err != nil {
|
|
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil)
|
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, res)
|
|
return
|
|
}
|
|
created, err := c.userService.Register(ctx.Request.Context(), req)
|
|
if err != nil {
|
|
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_REGISTER_USER, err.Error(), nil)
|
|
ctx.JSON(http.StatusInternalServerError, res)
|
|
return
|
|
}
|
|
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_REGISTER_USER, created)
|
|
ctx.JSON(http.StatusOK, res)
|
|
}
|
|
|
|
func NewUserController(injector *do.Injector, us service.UserService) UserController {
|
|
db := do.MustInvokeNamed[*gorm.DB](injector, constants.DB)
|
|
return &userController{
|
|
userService: us,
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
// Register godoc
|
|
// @Summary Register a new user
|
|
// @Description Create a new user under the authenticated client. Validates input and returns created user.
|
|
// @Tags Users
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param body body dto.UserCreateRequest true "Register payload"
|
|
// @Success 200 {object} utils.Response
|
|
// @Failure 400 {object} map[string]interface{}
|
|
// @Failure 500 {object} map[string]interface{}
|
|
// @Router /users/register [post]
|
|
func (c *userController) Register(ctx *gin.Context) {
|
|
var user dto.UserCreateRequest
|
|
if err := ctx.ShouldBind(&user); err != nil {
|
|
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil)
|
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, res)
|
|
return
|
|
}
|
|
|
|
result, err := c.userService.Register(ctx.Request.Context(), user)
|
|
if err != nil {
|
|
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_REGISTER_USER, err.Error(), nil)
|
|
ctx.JSON(http.StatusBadRequest, res)
|
|
return
|
|
}
|
|
|
|
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_REGISTER_USER, result)
|
|
ctx.JSON(http.StatusOK, res)
|
|
}
|
|
|
|
// GetAllUser godoc
|
|
// @Summary Get list of users
|
|
// @Description Get paginated list of users for the current client. Supports filtering by name and including related roles.
|
|
// @Tags Users
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param name query string false "Filter by name (partial match)"
|
|
// @Param page query int false "Page number (default: 1)"
|
|
// @Param page_size query int false "Page size (default: 10)"
|
|
// @Param sort query string false "Sort expression, e.g. created_at desc"
|
|
// @Security ApiKeyAuth
|
|
// @Success 200 {array} utils.ResponseWithPagination
|
|
// @Failure 400 {object} map[string]interface{}
|
|
// @Router /users [get]
|
|
func (c *userController) GetAllUser(ctx *gin.Context) {
|
|
// clientId := ctx.MustGet("client_id").(string)
|
|
var filter = &query.UserFilter{
|
|
ClientID: ctx.Query("client_id"),
|
|
Name: ctx.Query("name"),
|
|
Includes: []string{"Roles"},
|
|
}
|
|
filter.BindPagination(ctx)
|
|
|
|
ctx.ShouldBindQuery(filter)
|
|
users, total, err := pagination.PaginatedQueryWithIncludable[query.M_User](c.db, filter)
|
|
if err != nil {
|
|
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_USER, err.Error(), nil)
|
|
ctx.JSON(http.StatusBadRequest, res)
|
|
return
|
|
}
|
|
userResponses := ToUserResponses(users)
|
|
paginationResponse := pagination.CalculatePagination(filter.Pagination, total)
|
|
response := pagination.NewPaginatedResponse(http.StatusOK, dto.MESSAGE_SUCCESS_GET_LIST_USER, userResponses, paginationResponse)
|
|
ctx.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// Me godoc
|
|
// @Summary Get current user
|
|
// @Description Return the authenticated user's profile.
|
|
// @Tags Users
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security ApiKeyAuth
|
|
// @Success 200 {object} utils.Response
|
|
// @Failure 400 {object} map[string]interface{}
|
|
// @Router /users/me [get]
|
|
func (c *userController) Me(ctx *gin.Context) {
|
|
userId := ctx.MustGet("user_id").(string)
|
|
|
|
result, err := c.userService.GetUserById(ctx.Request.Context(), userId)
|
|
if err != nil {
|
|
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_USER, err.Error(), nil)
|
|
ctx.JSON(http.StatusBadRequest, res)
|
|
return
|
|
}
|
|
|
|
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_USER, result)
|
|
ctx.JSON(http.StatusOK, res)
|
|
}
|
|
|
|
// GetUserById godoc
|
|
// @Summary Get user by ID
|
|
// @Description Get details of a user by their ID. Requires appropriate permissions.
|
|
// @Tags Users
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "User ID"
|
|
// @Security ApiKeyAuth
|
|
// @Success 200 {object} utils.Response
|
|
// @Failure 400 {object} map[string]interface{}
|
|
// @Router /users/{id} [get]
|
|
func (c *userController) GetUserById(ctx *gin.Context) {
|
|
userId := ctx.Param("id")
|
|
|
|
result, err := c.userService.GetUserById(ctx.Request.Context(), userId)
|
|
if err != nil {
|
|
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_USER, err.Error(), nil)
|
|
ctx.JSON(http.StatusBadRequest, res)
|
|
return
|
|
}
|
|
|
|
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_GET_USER, result)
|
|
ctx.JSON(http.StatusOK, res)
|
|
}
|
|
|
|
// Login godoc
|
|
// @Summary User login
|
|
// @Description Authenticate user and return access & refresh tokens.
|
|
// @Tags Auth
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param body body dto.UserLoginRequest true "Login payload"
|
|
// @Success 200 {object} map[string]interface{}
|
|
// @Failure 400 {object} map[string]interface{}
|
|
// @Router /auth/login [post]
|
|
func (c *userController) Login(ctx *gin.Context) {
|
|
var req dto.UserLoginRequest
|
|
if err := ctx.ShouldBind(&req); err != nil {
|
|
response := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil)
|
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, response)
|
|
return
|
|
}
|
|
|
|
result, err := c.userService.Verify(ctx.Request.Context(), req)
|
|
if err != nil {
|
|
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_LOGIN, err.Error(), nil)
|
|
ctx.JSON(http.StatusBadRequest, res)
|
|
return
|
|
}
|
|
|
|
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_LOGIN, result)
|
|
ctx.JSON(http.StatusOK, res)
|
|
}
|
|
|
|
// SendVerificationEmail godoc
|
|
// @Summary Send verification email
|
|
// @Description Send email with verification code/link to a user's email address.
|
|
// @Tags Users
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param body body dto.SendVerificationEmailRequest true "Email request payload"
|
|
// @Success 200 {object} map[string]interface{}
|
|
// @Failure 400 {object} map[string]interface{}
|
|
// @Router /users/send-verification-email [post]
|
|
func (c *userController) SendVerificationEmail(ctx *gin.Context) {
|
|
var req dto.SendVerificationEmailRequest
|
|
if err := ctx.ShouldBind(&req); err != nil {
|
|
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil)
|
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, res)
|
|
return
|
|
}
|
|
|
|
err := c.userService.SendVerificationEmail(ctx.Request.Context(), req)
|
|
if err != nil {
|
|
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_PROSES_REQUEST, err.Error(), nil)
|
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, res)
|
|
return
|
|
}
|
|
|
|
res := utils.BuildResponseSuccess(dto.MESSAGE_SEND_VERIFICATION_EMAIL_SUCCESS, nil)
|
|
ctx.JSON(http.StatusOK, res)
|
|
}
|
|
|
|
// VerifyEmail godoc
|
|
// @Summary Verify user email
|
|
// @Description Verify a user's email using code or token sent via email.
|
|
// @Tags Users
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param body body dto.VerifyEmailRequest true "Verify email payload"
|
|
// @Success 200 {object} utils.Response
|
|
// @Failure 400 {object} map[string]interface{}
|
|
// @Router /users/verify-email [post]
|
|
func (c *userController) VerifyEmail(ctx *gin.Context) {
|
|
var req dto.VerifyEmailRequest
|
|
if err := ctx.ShouldBind(&req); err != nil {
|
|
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil)
|
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, res)
|
|
return
|
|
}
|
|
|
|
result, err := c.userService.VerifyEmail(ctx.Request.Context(), req)
|
|
if err != nil {
|
|
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_VERIFY_EMAIL, err.Error(), nil)
|
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, res)
|
|
return
|
|
}
|
|
|
|
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_VERIFY_EMAIL, result)
|
|
ctx.JSON(http.StatusOK, res)
|
|
}
|
|
|
|
// Update godoc
|
|
// @Summary Update current user
|
|
// @Description Update profile of the authenticated user. Use multipart/form-data if uploading files (photo).
|
|
// @Tags Users
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param body body dto.UserUpdateRequest true "Update payload"
|
|
// @Security ApiKeyAuth
|
|
// @Success 200 {object} utils.Response
|
|
// @Failure 400 {object} map[string]interface{}
|
|
// @Router /users [put]
|
|
func (c *userController) Update(ctx *gin.Context) {
|
|
var req dto.UserUpdateRequest
|
|
if err := ctx.ShouldBind(&req); err != nil {
|
|
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil)
|
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, res)
|
|
return
|
|
}
|
|
|
|
id := ctx.Param("id")
|
|
result, err := c.userService.Update(ctx.Request.Context(), req, id)
|
|
if err != nil {
|
|
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_UPDATE_USER, err.Error(), nil)
|
|
ctx.JSON(http.StatusBadRequest, res)
|
|
return
|
|
}
|
|
|
|
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_UPDATE_USER, result)
|
|
ctx.JSON(http.StatusOK, res)
|
|
}
|
|
|
|
// Delete godoc
|
|
// @Summary Delete current user
|
|
// @Description Delete the authenticated user's account (soft/hard depends on implementation).
|
|
// @Tags Users
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security ApiKeyAuth
|
|
// @Success 200 {object} map[string]interface{}
|
|
// @Failure 400 {object} map[string]interface{}
|
|
// @Router /users [delete]
|
|
func (c *userController) Delete(ctx *gin.Context) {
|
|
id := ctx.Param("id")
|
|
|
|
if err := c.userService.Delete(ctx.Request.Context(), id); err != nil {
|
|
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_DELETE_USER, err.Error(), nil)
|
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, res)
|
|
return
|
|
}
|
|
|
|
res := utils.BuildResponseSuccess(dto.MESSAGE_SUCCESS_DELETE_USER, nil)
|
|
ctx.JSON(http.StatusOK, res)
|
|
}
|
|
|
|
// Refresh godoc
|
|
// @Summary Refresh auth token
|
|
// @Description Exchange a refresh token for a new access token (and optionally a new refresh token).
|
|
// @Tags Auth
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body object true "Refresh token payload" example:{"refresh_token":"string"}
|
|
// @Success 200 {object} map[string]interface{}
|
|
// @Failure 401 {object} map[string]interface{}
|
|
// @Failure 400 {object} map[string]interface{}
|
|
// @Router /auth/refresh [post]
|
|
func (c *userController) Refresh(ctx *gin.Context) {
|
|
var req authDto.RefreshTokenRequest
|
|
if err := ctx.ShouldBind(&req); err != nil {
|
|
res := utils.BuildResponseFailed(dto.MESSAGE_FAILED_GET_DATA_FROM_BODY, err.Error(), nil)
|
|
ctx.AbortWithStatusJSON(http.StatusBadRequest, res)
|
|
return
|
|
}
|
|
|
|
result, err := c.userService.RefreshToken(ctx.Request.Context(), req)
|
|
if err != nil {
|
|
res := utils.BuildResponseFailed(authDto.MESSAGE_FAILED_REFRESH_TOKEN, err.Error(), nil)
|
|
ctx.JSON(http.StatusUnauthorized, res)
|
|
return
|
|
}
|
|
|
|
res := utils.BuildResponseSuccess(authDto.MESSAGE_SUCCESS_REFRESH_TOKEN, result)
|
|
ctx.JSON(http.StatusOK, res)
|
|
}
|
|
|
|
// Function untuk convert dari M_User ke UserResponse
|
|
func ToUserResponse(user query.M_User) dto.UserResponse {
|
|
var roles []dto.UserRolesResponse
|
|
for _, role := range user.Roles {
|
|
roles = append(roles, dto.UserRolesResponse{
|
|
ID: role.ID.String(), // pastikan role.ID bertipe uuid/atau string
|
|
Name: role.Name,
|
|
})
|
|
}
|
|
|
|
return dto.UserResponse{
|
|
ID: user.ID,
|
|
Name: user.Name,
|
|
Username: user.Username,
|
|
Gender: user.Gender,
|
|
Address: user.Address,
|
|
Phone: user.Phone,
|
|
Email: user.Email,
|
|
PhotoUrl: user.PhotoUrl,
|
|
Roles: roles,
|
|
}
|
|
}
|
|
|
|
// Function untuk convert slice of M_User
|
|
func ToUserResponses(users []query.M_User) []dto.UserResponse {
|
|
var responses []dto.UserResponse
|
|
for _, user := range users {
|
|
responses = append(responses, ToUserResponse(user))
|
|
}
|
|
return responses
|
|
}
|