ee830f8c1b
- Introduced a new search functionality for companies, allowing advanced filtering capabilities including tags, business criteria, and location. - Updated the API documentation to reflect changes in the search endpoint, enhancing clarity for API consumers. - Refactored existing company-related API endpoints for consistency, including renaming and restructuring routes. - Enhanced response structures to return company entities directly, simplifying the response handling in API endpoints. - Removed unused query parameters and handlers, streamlining the company management functionality.
453 lines
18 KiB
Go
453 lines
18 KiB
Go
package user
|
|
|
|
import (
|
|
"tm/pkg/authorization"
|
|
"tm/pkg/logger"
|
|
"tm/pkg/response"
|
|
|
|
"github.com/asaskevich/govalidator"
|
|
"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,
|
|
}
|
|
}
|
|
|
|
// Login handles user login
|
|
// @Summary Authenticate user login
|
|
// @Description Authenticate user with username/email and password to obtain access and refresh tokens for web panel administration. This endpoint validates credentials and returns JWT tokens for subsequent API calls.
|
|
// @Tags Admin-Authorization
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param login body LoginForm true "User login credentials including username/email and password"
|
|
// @Success 200 {object} response.APIResponse{data=AuthResponse} "Login successful with access and refresh tokens"
|
|
// @Failure 400 {object} response.APIResponse "Bad request - Invalid request format or missing fields"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized - Invalid credentials or account locked"
|
|
// @Failure 422 {object} response.APIResponse "Validation error - Invalid input data format"
|
|
// @Failure 429 {object} response.APIResponse "Too many requests - Rate limit exceeded"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /admin/v1/profile/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 Generate a new access token using a valid refresh token. This endpoint allows clients to obtain fresh access tokens without re-authentication, maintaining session continuity.
|
|
// @Tags Admin-Authorization
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param refresh body RefreshTokenForm true "Refresh token for generating new access token"
|
|
// @Success 200 {object} response.APIResponse{data=AuthResponse} "Token refreshed successfully with new access token"
|
|
// @Failure 400 {object} response.APIResponse "Bad request - Invalid request format"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized - Invalid or expired refresh token"
|
|
// @Failure 422 {object} response.APIResponse "Validation error - Invalid token format"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /admin/v1/profile/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 Initiate password reset process
|
|
// @Description Send password reset email to user with reset token. This endpoint validates the email address and sends a secure reset link to the user's registered email address.
|
|
// @Tags Admin-Authorization
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param reset body ResetPasswordForm true "Email address for password reset request"
|
|
// @Success 200 {object} response.APIResponse "Password reset email sent successfully"
|
|
// @Failure 400 {object} response.APIResponse "Bad request - Invalid request format or email address"
|
|
// @Failure 404 {object} response.APIResponse "Not found - Email address not registered"
|
|
// @Failure 422 {object} response.APIResponse "Validation error - Invalid email format"
|
|
// @Failure 429 {object} response.APIResponse "Too many requests - Rate limit for reset emails exceeded"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error - Failed to send email"
|
|
// @Router /admin/v1/profile/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 authenticated user profile
|
|
// @Description Retrieve complete profile information for the currently authenticated user including personal details, role information, permissions, and account status. This endpoint requires valid authentication token.
|
|
// @Tags Admin-Authorization
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Success 200 {object} response.APIResponse{data=UserResponse} "Profile retrieved successfully with user details"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized - Invalid or expired authentication token"
|
|
// @Failure 404 {object} response.APIResponse "Not found - User profile not found"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /admin/v1/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.GetUserByID(c.Request().Context(), userID)
|
|
if err != nil {
|
|
return response.NotFound(c, "User not found")
|
|
}
|
|
|
|
return response.Success(c, user, "Profile retrieved successfully")
|
|
}
|
|
|
|
// UpdateProfile updates current user profile
|
|
// @Summary Update authenticated user profile
|
|
// @Description Update profile information for the currently authenticated user including personal details, contact information, and preferences. Only the authenticated user can update their own profile.
|
|
// @Tags Admin-Authorization
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param profile body UpdateUserForm true "Profile update data including name, email, phone, and other personal information"
|
|
// @Success 200 {object} response.APIResponse{data=UserResponse} "Profile updated successfully with updated user details"
|
|
// @Failure 400 {object} response.APIResponse "Bad request - Invalid request format or missing required fields"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized - Invalid or expired authentication token"
|
|
// @Failure 409 {object} response.APIResponse "Conflict - Email address already exists for another user"
|
|
// @Failure 422 {object} response.APIResponse "Validation error - Invalid input data format"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /admin/v1/profile [put]
|
|
func (h *Handler) UpdateProfile(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")
|
|
}
|
|
|
|
form, err := response.Parse[UpdateUserForm](c)
|
|
if err != nil {
|
|
return response.BadRequest(c, "Invalid request format", "")
|
|
}
|
|
|
|
user, err := h.service.Update(c.Request().Context(), userID, form)
|
|
if err != nil {
|
|
return response.BadRequest(c, err.Error(), "")
|
|
}
|
|
|
|
return response.Success(c, user, "Profile updated successfully")
|
|
}
|
|
|
|
// ChangePassword changes current user password
|
|
// @Summary Change user password
|
|
// @Description Change password for the currently authenticated user. Requires current password verification and enforces password policy. This operation invalidates all existing sessions and requires re-authentication.
|
|
// @Tags Admin-Authorization
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param password body ChangePasswordForm true "Password change data including current password and new password"
|
|
// @Success 200 {object} response.APIResponse "Password changed successfully - user must re-authenticate"
|
|
// @Failure 400 {object} response.APIResponse "Bad request - Invalid request format or password policy violation"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized - Invalid authentication token or incorrect current password"
|
|
// @Failure 422 {object} response.APIResponse "Validation error - Invalid password format or policy requirements not met"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /admin/v1/profile/change-password [put]
|
|
func (h *Handler) ChangePassword(c echo.Context) error {
|
|
userID, err := GetUserIDFromContext(c)
|
|
if err != nil {
|
|
return response.Unauthorized(c, "User not authenticated")
|
|
}
|
|
|
|
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 authenticated user
|
|
// @Description Logout the currently authenticated user by invalidating their access and refresh tokens. This endpoint ensures secure session termination and prevents further use of the user's tokens.
|
|
// @Tags Admin-Authorization
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Success 200 {object} response.APIResponse "Logout successful - all tokens invalidated"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized - Invalid or expired authentication token"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error - Failed to invalidate tokens"
|
|
// @Router /admin/v1/profile/logout [delete]
|
|
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 new user account
|
|
// @Description Create a new user account with specified role and permissions. This endpoint is restricted to administrators only and allows creation of various user types including admins, managers, and operators with appropriate access levels.
|
|
// @Tags Admin-Users
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param user body CreateUserForm true "User creation data including username, email, password, role, and profile information"
|
|
// @Success 201 {object} response.APIResponse{data=UserResponse} "User created successfully with assigned role and permissions"
|
|
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data or missing required fields"
|
|
// @Failure 401 {object} response.APIResponse "Unauthorized - Invalid or expired authentication token"
|
|
// @Failure 403 {object} response.APIResponse "Forbidden - Insufficient privileges to create users"
|
|
// @Failure 409 {object} response.APIResponse "Conflict - Username or email already exists"
|
|
// @Failure 422 {object} response.APIResponse "Validation error - Invalid data format or password policy violation"
|
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
|
// @Router /admin/v1/users [post]
|
|
func (h *Handler) CreateUser(c echo.Context) error {
|
|
form, err := response.Parse[CreateUserForm](c)
|
|
if 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.Register(c.Request().Context(), form)
|
|
if err != nil {
|
|
return response.BadRequest(c, err.Error(), "")
|
|
}
|
|
|
|
return response.Created(c, user, "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 Admin-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 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 /admin/v1/users [get]
|
|
func (h *Handler) ListUsers(c echo.Context) error {
|
|
form, err := response.Parse[SearchUsersForm](c)
|
|
if 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.Search(c.Request().Context(), form, response.NewPagination(c))
|
|
if err != nil {
|
|
return response.InternalServerError(c, "Failed to list users")
|
|
}
|
|
|
|
return response.SuccessWithMeta(c, users, users.Meta, "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 Admin-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 /admin/v1/users/{id} [get]
|
|
func (h *Handler) GetUserByID(c echo.Context) error {
|
|
idStr := c.Param("id")
|
|
|
|
user, err := h.service.GetUserByID(c.Request().Context(), idStr)
|
|
if err != nil {
|
|
return response.NotFound(c, "User not found")
|
|
}
|
|
|
|
return response.Success(c, user, "User retrieved successfully")
|
|
}
|
|
|
|
// UpdateUser updates a user (admin only)
|
|
// @Summary Update user
|
|
// @Description Update user information (admin only)
|
|
// @Tags Admin-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 /admin/v1/users/{id} [put]
|
|
func (h *Handler) UpdateUser(c echo.Context) error {
|
|
idStr := c.Param("id")
|
|
|
|
form, err := response.Parse[UpdateUserForm](c)
|
|
if 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.Update(c.Request().Context(), idStr, form)
|
|
if err != nil {
|
|
return response.BadRequest(c, err.Error(), "")
|
|
}
|
|
|
|
return response.Success(c, user, "User updated successfully")
|
|
}
|
|
|
|
// DeleteUser deletes a user (admin only)
|
|
// @Summary Delete user
|
|
// @Description Delete a user (admin only)
|
|
// @Tags Admin-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 /admin/v1/users/{id} [delete]
|
|
func (h *Handler) DeleteUser(c echo.Context) error {
|
|
id := c.Param("id")
|
|
|
|
err := h.service.Delete(c.Request().Context(), id)
|
|
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 Admin-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 /admin/v1/users/{id}/status [put]
|
|
func (h *Handler) UpdateUserStatus(c echo.Context) error {
|
|
idStr := c.Param("id")
|
|
|
|
form, err := response.Parse[UpdateUserStatusForm](c)
|
|
if 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.UpdateStatus(c.Request().Context(), idStr, form)
|
|
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")
|
|
}
|