package user import ( "strconv" "tm/pkg/authorization" "tm/pkg/logger" "tm/pkg/response" "github.com/asaskevich/govalidator" "github.com/google/uuid" "github.com/labstack/echo/v4" ) // Handler handles HTTP requests for user operations type Handler struct { service Service logger logger.Logger validator ValidationService authService authorization.AuthorizationService } // NewUserHandler creates a new user handler func NewUserHandler(service Service, logger logger.Logger, validator ValidationService, authService authorization.AuthorizationService) *Handler { return &Handler{ service: service, logger: logger, validator: validator, authService: authService, } } // RegisterRoutes registers user routes func (h *Handler) RegisterRoutes(e *echo.Echo) { v1 := e.Group("/api/v1") userGroup := v1.Group("/users") { // Public routes userGroup.POST("/login", h.Login) userGroup.POST("/refresh-token", h.RefreshToken) userGroup.POST("/reset-password", h.ResetPassword) // Protected routes (require authentication) authGroup := userGroup.Group("") // authGroup.Use(h.AuthMiddleware()) { authGroup.GET("/profile", h.GetProfile) authGroup.PUT("/profile", h.UpdateProfile) authGroup.PUT("/change-password", h.ChangePassword) authGroup.POST("/logout", h.Logout) // Admin routes adminGroup := authGroup.Group("/admin") // adminGroup.Use(h.AdminMiddleware()) { adminGroup.POST("", h.CreateUser) adminGroup.GET("", h.ListUsers) adminGroup.GET("/:id", h.GetUserByID) adminGroup.PUT("/:id", h.UpdateUser) adminGroup.DELETE("/:id", h.DeleteUser) adminGroup.PUT("/:id/status", h.UpdateUserStatus) adminGroup.PUT("/:id/role", h.UpdateUserRole) adminGroup.GET("/company/:company_id", h.GetUsersByCompanyID) adminGroup.GET("/role/:role", h.GetUsersByRole) } } } } // Login handles user login // @Summary Login user // @Description Authenticate user with email/username and password // @Tags users // @Accept json // @Produce json // @Param login body LoginForm true "Login credentials" // @Success 200 {object} response.APIResponse{data=AuthResponse} // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /users/login [post] func (h *Handler) Login(c echo.Context) error { form, err := response.Parse[LoginForm](c) if err != nil { return response.BadRequest(c, "Invalid request format", "") } // Call service authResponse, err := h.service.Login(c.Request().Context(), form) if err != nil { return response.Unauthorized(c, err.Error()) } return response.Success(c, authResponse, "Login successful") } // RefreshToken handles token refresh // @Summary Refresh access token // @Description Refresh access token using refresh token // @Tags users // @Accept json // @Produce json // @Param refresh body RefreshTokenForm true "Refresh token" // @Success 200 {object} response.APIResponse{data=AuthResponse} // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /users/refresh-token [post] func (h *Handler) RefreshToken(c echo.Context) error { form, err := response.Parse[RefreshTokenForm](c) if err != nil { return response.BadRequest(c, "Invalid request format", "") } authResponse, err := h.service.RefreshToken(c.Request().Context(), form.RefreshToken) if err != nil { return response.Unauthorized(c, err.Error()) } return response.Success(c, authResponse, "Token refreshed successfully") } // ResetPassword handles password reset request // @Summary Reset password // @Description Send password reset email to user // @Tags users // @Accept json // @Produce json // @Param reset body ResetPasswordForm true "Password reset request" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /users/reset-password [post] func (h *Handler) ResetPassword(c echo.Context) error { form, err := response.Parse[ResetPasswordForm](c) if err != nil { return response.BadRequest(c, "Invalid request format", "") } err = h.service.ResetPassword(c.Request().Context(), form) if err != nil { return response.InternalServerError(c, "Failed to process password reset") } return response.Success(c, map[string]interface{}{ "message": "Password reset email sent successfully", }, "Password reset initiated") } // GetProfile gets current user profile // @Summary Get user profile // @Description Get current user profile information // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} response.APIResponse{data=UserResponse} // @Failure 401 {object} response.APIResponse // @Failure 404 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /users/profile [get] func (h *Handler) GetProfile(c echo.Context) error { // Extract user ID from JWT token context userID, err := GetUserIDFromContext(c) if err != nil { return response.Unauthorized(c, "User not authenticated") } user, err := h.service.GetProfile(c.Request().Context(), userID) if err != nil { return response.NotFound(c, "User not found") } return response.Success(c, user.ToResponse(), "Profile retrieved successfully") } // UpdateProfile updates current user profile // @Summary Update user profile // @Description Update current user profile information // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param profile body UpdateUserForm true "Profile update data" // @Success 200 {object} response.APIResponse{data=UserResponse} // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /users/profile [put] func (h *Handler) UpdateProfile(c echo.Context) error { // TODO: Extract user ID from JWT token userID := uuid.New() // Placeholder - should be extracted from JWT form, err := response.Parse[UpdateUserForm](c) if err != nil { return response.BadRequest(c, "Invalid request format", "") } user, err := h.service.UpdateUser(c.Request().Context(), userID, form, &userID) if err != nil { return response.BadRequest(c, err.Error(), "") } return response.Success(c, user.ToResponse(), "Profile updated successfully") } // ChangePassword changes current user password // @Summary Change password // @Description Change current user password // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param password body ChangePasswordForm true "Password change data" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /users/change-password [put] func (h *Handler) ChangePassword(c echo.Context) error { // TODO: Extract user ID from JWT token userID := uuid.New() // Placeholder - should be extracted from JWT form, err := response.Parse[ChangePasswordForm](c) if err != nil { return response.BadRequest(c, "Invalid request format", "") } err = h.service.ChangePassword(c.Request().Context(), userID, form) if err != nil { return response.BadRequest(c, err.Error(), "") } return response.Success(c, map[string]interface{}{ "message": "Password changed successfully", }, "Password changed successfully") } // Logout handles user logout // @Summary Logout user // @Description Logout current user and invalidate tokens // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /users/logout [post] func (h *Handler) Logout(c echo.Context) error { // Extract user ID and access token from context userID, err := GetUserIDFromContext(c) if err != nil { return response.Unauthorized(c, "User not authenticated") } accessToken, err := GetAccessTokenFromContext(c) if err != nil { return response.Unauthorized(c, "Access token not found") } err = h.service.Logout(c.Request().Context(), userID, accessToken) if err != nil { return response.InternalServerError(c, "Failed to logout") } return response.Success(c, map[string]interface{}{ "message": "Logged out successfully", }, "Logged out successfully") } // CreateUser creates a new user (admin only) // @Summary Create user // @Description Create a new user (admin only) // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param user body CreateUserForm true "User creation data" // @Success 201 {object} response.APIResponse{data=UserResponse} // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 403 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /users/admin [post] func (h *Handler) CreateUser(c echo.Context) error { // Get current user ID from JWT token currentUserID, err := GetUserIDFromContext(c) if err != nil { return response.Unauthorized(c, "User not authenticated") } var form CreateUserForm if err := c.Bind(&form); err != nil { return response.BadRequest(c, "Invalid request format", "") } // Validate form if _, err := govalidator.ValidateStruct(form); err != nil { return response.ValidationError(c, err.Error(), "") } // Call service user, err := h.service.CreateUser(c.Request().Context(), &form, ¤tUserID) if err != nil { return response.BadRequest(c, err.Error(), "") } return response.Created(c, user.ToResponse(), "User created successfully") } // ListUsers lists users with search and filters (admin only) // @Summary List users // @Description List users with search and filters (admin only) // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param search query string false "Search term" // @Param status query string false "User status filter" // @Param role query string false "User role filter" // @Param company_id query string false "Company ID filter" // @Param limit query int false "Limit results" // @Param offset query int false "Offset results" // @Param sort_by query string false "Sort field" // @Param sort_order query string false "Sort order" // @Success 200 {object} response.APIResponse{data=UserListResponse} // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 403 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /users/admin [get] func (h *Handler) ListUsers(c echo.Context) error { var form ListUsersForm if err := c.Bind(&form); err != nil { return response.BadRequest(c, "Invalid request format", "") } // Validate form if _, err := govalidator.ValidateStruct(form); err != nil { return response.ValidationError(c, err.Error(), "") } // Call service users, err := h.service.ListUsers(c.Request().Context(), &form) if err != nil { return response.InternalServerError(c, "Failed to list users") } return response.Success(c, users, "Users retrieved successfully") } // GetUserByID gets a user by ID (admin only) // @Summary Get user by ID // @Description Get user details by ID (admin only) // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param id path string true "User ID" // @Success 200 {object} response.APIResponse{data=UserResponse} // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 403 {object} response.APIResponse // @Failure 404 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /users/admin/{id} [get] func (h *Handler) GetUserByID(c echo.Context) error { idStr := c.Param("id") userID, err := uuid.Parse(idStr) if err != nil { return response.BadRequest(c, "Invalid user ID", "") } user, err := h.service.GetUserByID(c.Request().Context(), userID) if err != nil { return response.NotFound(c, "User not found") } return response.Success(c, user.ToResponse(), "User retrieved successfully") } // UpdateUser updates a user (admin only) // @Summary Update user // @Description Update user information (admin only) // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param id path string true "User ID" // @Param user body UpdateUserForm true "User update data" // @Success 200 {object} response.APIResponse{data=UserResponse} // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 403 {object} response.APIResponse // @Failure 404 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /users/admin/{id} [put] func (h *Handler) UpdateUser(c echo.Context) error { // TODO: Get current user ID from JWT token currentUserID := uuid.New() // Placeholder idStr := c.Param("id") userID, err := uuid.Parse(idStr) if err != nil { return response.BadRequest(c, "Invalid user ID", "") } var form UpdateUserForm if err := c.Bind(&form); err != nil { return response.BadRequest(c, "Invalid request format", "") } // Validate form if _, err := govalidator.ValidateStruct(form); err != nil { return response.ValidationError(c, err.Error(), "") } // Call service user, err := h.service.UpdateUser(c.Request().Context(), userID, &form, ¤tUserID) if err != nil { return response.BadRequest(c, err.Error(), "") } return response.Success(c, user.ToResponse(), "User updated successfully") } // DeleteUser deletes a user (admin only) // @Summary Delete user // @Description Delete a user (admin only) // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param id path string true "User ID" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 403 {object} response.APIResponse // @Failure 404 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /users/admin/{id} [delete] func (h *Handler) DeleteUser(c echo.Context) error { // TODO: Get current user ID from JWT token currentUserID := uuid.New() // Placeholder idStr := c.Param("id") userID, err := uuid.Parse(idStr) if err != nil { return response.BadRequest(c, "Invalid user ID", "") } err = h.service.DeleteUser(c.Request().Context(), userID, ¤tUserID) if err != nil { return response.BadRequest(c, err.Error(), "") } return response.Success(c, map[string]interface{}{ "message": "User deleted successfully", }, "User deleted successfully") } // UpdateUserStatus updates user status (admin only) // @Summary Update user status // @Description Update user account status (admin only) // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param id path string true "User ID" // @Param status body UpdateUserStatusForm true "Status update data" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 403 {object} response.APIResponse // @Failure 404 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /users/admin/{id}/status [put] func (h *Handler) UpdateUserStatus(c echo.Context) error { // TODO: Get current user ID from JWT token currentUserID := uuid.New() // Placeholder idStr := c.Param("id") userID, err := uuid.Parse(idStr) if err != nil { return response.BadRequest(c, "Invalid user ID", "") } var form UpdateUserStatusForm if err := c.Bind(&form); err != nil { return response.BadRequest(c, "Invalid request format", "") } // Validate form if _, err := govalidator.ValidateStruct(form); err != nil { return response.ValidationError(c, err.Error(), "") } // Call service err = h.service.UpdateUserStatus(c.Request().Context(), userID, &form, ¤tUserID) if err != nil { return response.BadRequest(c, err.Error(), "") } return response.Success(c, map[string]interface{}{ "message": "User status updated successfully", }, "User status updated successfully") } // UpdateUserRole updates user role (admin only) // @Summary Update user role // @Description Update user role (admin only) // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param id path string true "User ID" // @Param role body UpdateUserRoleForm true "Role update data" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 403 {object} response.APIResponse // @Failure 404 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /users/admin/{id}/role [put] func (h *Handler) UpdateUserRole(c echo.Context) error { // TODO: Get current user ID from JWT token currentUserID := uuid.New() // Placeholder idStr := c.Param("id") userID, err := uuid.Parse(idStr) if err != nil { return response.BadRequest(c, "Invalid user ID", "") } var form UpdateUserRoleForm if err := c.Bind(&form); err != nil { return response.BadRequest(c, "Invalid request format", "") } // Validate form if _, err := govalidator.ValidateStruct(form); err != nil { return response.ValidationError(c, err.Error(), "") } // Call service err = h.service.UpdateUserRole(c.Request().Context(), userID, &form, ¤tUserID) if err != nil { return response.BadRequest(c, err.Error(), "") } return response.Success(c, map[string]interface{}{ "message": "User role updated successfully", }, "User role updated successfully") } // GetUsersByCompanyID gets users by company ID (admin only) // @Summary Get users by company ID // @Description Get users belonging to a specific company (admin only) // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param company_id path string true "Company ID" // @Param limit query int false "Limit results" // @Param offset query int false "Offset results" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 403 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /users/admin/company/{company_id} [get] func (h *Handler) GetUsersByCompanyID(c echo.Context) error { companyIDStr := c.Param("company_id") companyID, err := uuid.Parse(companyIDStr) if err != nil { return response.BadRequest(c, "Invalid company ID", "") } // Get query parameters limitStr := c.QueryParam("limit") if limitStr == "" { limitStr = "20" } offsetStr := c.QueryParam("offset") if offsetStr == "" { offsetStr = "0" } limit, err := strconv.Atoi(limitStr) if err != nil { return response.BadRequest(c, "Invalid limit parameter", "") } offset, err := strconv.Atoi(offsetStr) if err != nil { return response.BadRequest(c, "Invalid offset parameter", "") } // Call service users, total, err := h.service.GetUsersByCompanyID(c.Request().Context(), companyID, limit, offset) if err != nil { return response.InternalServerError(c, "Failed to get users by company") } // Convert to responses var userResponses []*UserResponse for _, user := range users { userResponses = append(userResponses, user.ToResponse()) } return response.SuccessWithMeta(c, map[string]interface{}{ "users": userResponses, }, &response.Meta{ Total: int(total), Limit: limit, Offset: offset, }, "Users retrieved successfully") } // GetUsersByRole gets users by role (admin only) // @Summary Get users by role // @Description Get users with a specific role (admin only) // @Tags users // @Accept json // @Produce json // @Security BearerAuth // @Param role path string true "User role" // @Param limit query int false "Limit results" // @Param offset query int false "Offset results" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 401 {object} response.APIResponse // @Failure 403 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /users/admin/role/{role} [get] func (h *Handler) GetUsersByRole(c echo.Context) error { roleStr := c.Param("role") role := UserRole(roleStr) // Validate role if !h.validator.IsValidRole(role) { return response.BadRequest(c, "Invalid role", "") } // Get query parameters limitStr := c.QueryParam("limit") if limitStr == "" { limitStr = "20" } offsetStr := c.QueryParam("offset") if offsetStr == "" { offsetStr = "0" } limit, err := strconv.Atoi(limitStr) if err != nil { return response.BadRequest(c, "Invalid limit parameter", "") } offset, err := strconv.Atoi(offsetStr) if err != nil { return response.BadRequest(c, "Invalid offset parameter", "") } // Call service users, err := h.service.GetUsersByRole(c.Request().Context(), role, limit, offset) if err != nil { return response.InternalServerError(c, "Failed to get users by role") } // Convert to responses var userResponses []*UserResponse for _, user := range users { userResponses = append(userResponses, user.ToResponse()) } return response.SuccessWithMeta(c, map[string]interface{}{ "users": userResponses, "role": role, }, &response.Meta{ Limit: limit, Offset: offset, }, "Users retrieved successfully") } // AuthMiddleware and AdminMiddleware are now implemented in middleware.go