1247 lines
40 KiB
Go
1247 lines
40 KiB
Go
package handlers
|
|
|
|
import (
|
|
"Yimaru-Backend/internal/domain"
|
|
"Yimaru-Backend/internal/services/authentication"
|
|
"Yimaru-Backend/internal/web_server/response"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// ResendOtp godoc
|
|
// @Summary Resend OTP
|
|
// @Description Resend OTP if the previous one is expired
|
|
// @Tags otp
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param resendOtp body domain.ResendOtpReq true "Resend OTP"
|
|
// @Success 200 {object} response.APIResponse
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Router /api/v1/{tenant_slug}/otp/resend [post]
|
|
func (h *Handler) ResendOtp(c *fiber.Ctx) error {
|
|
var req domain.ResendOtpReq
|
|
if err := c.BodyParser(&req); err != nil {
|
|
h.mongoLoggerSvc.Info("Failed to parse ResendOtp request",
|
|
zap.Int("status_code", fiber.StatusBadRequest),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Failed to resend OTP",
|
|
Error: "Invalid request body: " + err.Error(),
|
|
})
|
|
}
|
|
|
|
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
|
var errMsg string
|
|
for field, msg := range valErrs {
|
|
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
|
}
|
|
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Failed to resend OTP",
|
|
Error: errMsg,
|
|
})
|
|
}
|
|
|
|
user, err := h.userSvc.GetUserByUserName(c.Context(), req.UserName)
|
|
if err != nil {
|
|
h.mongoLoggerSvc.Info("Failed to get user by user name",
|
|
zap.String("user_name", req.UserName),
|
|
zap.Int("status_code", fiber.StatusBadRequest),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Failed to resend OTP",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
medium, err := getMedium(user.Email, user.PhoneNumber)
|
|
if err != nil {
|
|
h.mongoLoggerSvc.Info("Failed to determine OTP medium",
|
|
zap.String("email", user.Email),
|
|
zap.String("phone_number", user.PhoneNumber),
|
|
zap.Int("status_code", fiber.StatusBadRequest),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Failed to resend OTP",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
sentTo := user.Email
|
|
if medium == domain.OtpMediumSms {
|
|
sentTo = user.PhoneNumber
|
|
}
|
|
|
|
if err := h.userSvc.ResendOtp(
|
|
c.Context(),
|
|
req.UserName,
|
|
); err != nil {
|
|
|
|
h.mongoLoggerSvc.Error("Failed to resend OTP",
|
|
zap.String("sent_to", sentTo),
|
|
zap.String("medium", string(medium)),
|
|
zap.Int("status_code", fiber.StatusBadRequest),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Failed to resend OTP",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
Message: "OTP resent successfully",
|
|
Data: nil,
|
|
})
|
|
}
|
|
|
|
// CheckUserNameUnique godoc
|
|
// @Summary Check if user_name is unique
|
|
// @Description Returns whether the specified user_name is available (unique)
|
|
// @Tags user
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param user_name path string true "User Name"
|
|
// @Success 200 {object} domain.Response
|
|
// @Failure 400 {object} domain.ErrorResponse
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/user/{user_name}/is-unique [get]
|
|
func (h *Handler) CheckUserNameUnique(c *fiber.Ctx) error {
|
|
userName := c.Params("user_name")
|
|
if userName == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid user name",
|
|
Error: "user_name path parameter cannot be empty",
|
|
})
|
|
}
|
|
|
|
isUnique, err := h.userSvc.IsUserNameUnique(c.Context(), userName)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Failed to check user name uniqueness",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
Message: "User name uniqueness checked successfully",
|
|
Data: map[string]bool{
|
|
"is_unique": isUnique,
|
|
},
|
|
})
|
|
}
|
|
|
|
// CheckUserPending godoc
|
|
// @Summary Check if user status is pending
|
|
// @Description Returns whether the specified user has a status of "pending"
|
|
// @Tags user
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param user_name path string true "User Name"
|
|
// @Success 200 {object} domain.Response
|
|
// @Failure 400 {object} domain.ErrorResponse
|
|
// @Failure 404 {object} domain.ErrorResponse
|
|
// @Failure 500 {object} domain.ErrorResponse
|
|
// @Router /api/v1/{tenant_slug}/user/{user_name}/is-pending [get]
|
|
func (h *Handler) CheckUserPending(c *fiber.Ctx) error {
|
|
userName := c.Params("user_name")
|
|
if userName == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Invalid user name",
|
|
Error: "User name cannot be empty",
|
|
})
|
|
}
|
|
|
|
isPending, err := h.userSvc.IsUserPending(c.Context(), userName)
|
|
if err != nil {
|
|
if errors.Is(err, authentication.ErrUserNotFound) {
|
|
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
|
Message: "User not found",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
|
Message: "Failed to check user status",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
Message: "User status fetched successfully",
|
|
Data: map[string]bool{
|
|
"is_pending": isPending,
|
|
},
|
|
})
|
|
}
|
|
|
|
// VerifyOtp godoc
|
|
// @Summary Verify OTP
|
|
// @Description Verify OTP for registration or other actions
|
|
// @Tags user
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param verifyOtp body domain.VerifyOtpReq true "Verify OTP"
|
|
// @Success 200 {object} response.APIResponse
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Router /api/v1/user/verify-otp [post]
|
|
func (h *Handler) VerifyOtp(c *fiber.Ctx) error {
|
|
var req domain.VerifyOtpReq
|
|
if err := c.BodyParser(&req); err != nil {
|
|
h.mongoLoggerSvc.Info("Failed to parse VerifyOtp request",
|
|
zap.Int("status_code", fiber.StatusBadRequest),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Failed to verify OTP",
|
|
Error: "Invalid request body: " + err.Error(),
|
|
})
|
|
}
|
|
|
|
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
|
var errMsg string
|
|
for field, msg := range valErrs {
|
|
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
|
}
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Failed to verify OTP",
|
|
Error: errMsg,
|
|
})
|
|
}
|
|
|
|
// Call service to verify OTP
|
|
err := h.userSvc.VerifyOtp(c.Context(), req.UserName, req.Otp)
|
|
if err != nil {
|
|
var errMsg string
|
|
switch {
|
|
case errors.Is(err, domain.ErrOtpAlreadyUsed):
|
|
errMsg = "OTP already used"
|
|
case errors.Is(err, domain.ErrOtpExpired):
|
|
errMsg = "OTP expired"
|
|
case errors.Is(err, domain.ErrInvalidOtp):
|
|
errMsg = "Invalid OTP"
|
|
case errors.Is(err, domain.ErrOtpNotFound):
|
|
errMsg = "OTP not found"
|
|
default:
|
|
h.mongoLoggerSvc.Error("Failed to verify OTP",
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
errMsg = "Failed to verify OTP: " + err.Error()
|
|
}
|
|
statusCode := fiber.StatusBadRequest
|
|
if errMsg == "Failed to verify OTP: "+err.Error() {
|
|
statusCode = fiber.StatusInternalServerError
|
|
}
|
|
return c.Status(statusCode).JSON(domain.ErrorResponse{
|
|
Message: "Failed to verify OTP",
|
|
Error: errMsg,
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
Message: "OTP verified successfully",
|
|
Data: nil,
|
|
})
|
|
}
|
|
|
|
type GetTenantSlugByToken struct {
|
|
Slug string `json:"slug"`
|
|
}
|
|
|
|
// GetTenantSlugByToken godoc
|
|
// @Summary Check if phone number or email exist
|
|
// @Description Check if phone number or email exist
|
|
// @Tags user
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param checkPhoneEmailExist body CheckPhoneEmailExistReq true "Check phone number or email exist"
|
|
// @Success 200 {object} CheckPhoneEmailExistRes
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Router /api/v1/tenant [get]
|
|
func (h *Handler) GetTenantSlugByToken(c *fiber.Ctx) error {
|
|
userID, ok := c.Locals("user_id").(int64)
|
|
if !ok || userID == 0 {
|
|
h.mongoLoggerSvc.Error("Invalid user ID in context",
|
|
zap.Int64("userID", userID),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Invalid user identification")
|
|
}
|
|
|
|
_, err := h.userSvc.GetUserByID(c.Context(), userID)
|
|
if err != nil {
|
|
h.mongoLoggerSvc.Error("Failed to get user profile",
|
|
zap.Int64("userID", userID),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user profile:"+err.Error())
|
|
}
|
|
|
|
// if !user.OrganizationID.Valid {
|
|
// if user.Role == domain.RoleSuperAdmin {
|
|
// return fiber.NewError(fiber.StatusBadRequest, "Role Super-Admin Doesn't have a company-id")
|
|
// }
|
|
// h.mongoLoggerSvc.Error("Unknown Error: User doesn't have a company-id",
|
|
// zap.Int64("userID", userID),
|
|
// zap.Int("status_code", fiber.StatusInternalServerError),
|
|
// zap.Error(err),
|
|
// zap.Time("timestamp", time.Now()),
|
|
// )
|
|
// return fiber.NewError(fiber.StatusInternalServerError, "Unknown Error: User doesn't have a company-id")
|
|
// }
|
|
// company, err := h.companySvc.GetCompanyByID(c.Context(), user.CompanyID.Value)
|
|
|
|
// if err != nil {
|
|
// h.mongoLoggerSvc.Error("Failed to get company by id",
|
|
// zap.Int64("company", user.CompanyID.Value),
|
|
// zap.Int("status_code", fiber.StatusInternalServerError),
|
|
// zap.Error(err),
|
|
// zap.Time("timestamp", time.Now()),
|
|
// )
|
|
// return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve company:"+err.Error())
|
|
// }
|
|
|
|
res := GetTenantSlugByToken{
|
|
Slug: strconv.FormatInt(userID, 10),
|
|
}
|
|
|
|
return response.WriteJSON(c, fiber.StatusOK, "Tenant Slug retrieved successfully", res, nil)
|
|
}
|
|
|
|
type CheckPhoneEmailExistReq struct {
|
|
Email string `json:"email" example:"john.doe@example.com"`
|
|
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
|
}
|
|
type CheckPhoneEmailExistRes struct {
|
|
EmailExist bool `json:"email_exist"`
|
|
PhoneNumberExist bool `json:"phone_number_exist"`
|
|
}
|
|
|
|
// CheckPhoneEmailExist godoc
|
|
// @Summary Check if phone number or email exist
|
|
// @Description Check if phone number or email exist
|
|
// @Tags user
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param checkPhoneEmailExist body CheckPhoneEmailExistReq true "Check phone number or email exist"
|
|
// @Success 200 {object} CheckPhoneEmailExistRes
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Router /api/v1/{tenant_slug}/user/checkPhoneEmailExist [post]
|
|
func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error {
|
|
// companyID := c.Locals("company_id").(domain.ValidInt64)
|
|
// if !companyID.Valid {
|
|
// h.BadRequestLogger().Error("invalid company id")
|
|
// return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
|
|
// }
|
|
var req CheckPhoneEmailExistReq
|
|
if err := c.BodyParser(&req); err != nil {
|
|
h.mongoLoggerSvc.Info("Failed to parse CheckPhoneEmailExist request",
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
|
|
}
|
|
|
|
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
|
var errMsg string
|
|
for field, msg := range valErrs {
|
|
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
|
}
|
|
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
|
}
|
|
|
|
emailExist, phoneExist, err := h.userSvc.CheckPhoneEmailExist(c.Context(), req.PhoneNumber, req.Email)
|
|
if err != nil {
|
|
h.mongoLoggerSvc.Error("Failed to check phone/email existence",
|
|
zap.Any("request", req),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to check phone/email existence:"+err.Error())
|
|
}
|
|
|
|
res := CheckPhoneEmailExistRes{
|
|
EmailExist: emailExist,
|
|
PhoneNumberExist: phoneExist,
|
|
}
|
|
return response.WriteJSON(c, fiber.StatusOK, "Check successful", res, nil)
|
|
}
|
|
|
|
type RegisterCodeReq struct {
|
|
Email string `json:"email" example:"john.doe@example.com"`
|
|
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
|
}
|
|
|
|
// SendRegisterCode godoc
|
|
// @Summary Send register code
|
|
// @Description Send register code
|
|
// @Tags user
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param registerCode body RegisterCodeReq true "Send register code"
|
|
// @Success 200 {object} response.APIResponse
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Router /api/v1/{tenant_slug}/user/sendRegisterCode [post]
|
|
func (h *Handler) SendRegisterCode(c *fiber.Ctx) error {
|
|
// companyID := c.Locals("company_id").(domain.ValidInt64)
|
|
// if !companyID.Valid {
|
|
// h.BadRequestLogger().Error("invalid company id")
|
|
// return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
|
|
// }
|
|
var req RegisterCodeReq
|
|
if err := c.BodyParser(&req); err != nil {
|
|
h.mongoLoggerSvc.Info("Failed to parse SendRegisterCode request",
|
|
zap.Int("status_code", fiber.StatusBadRequest),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
|
|
}
|
|
|
|
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
|
var errMsg string
|
|
for field, msg := range valErrs {
|
|
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
|
}
|
|
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
|
}
|
|
|
|
var sentTo string
|
|
var medium domain.OtpMedium
|
|
if req.Email != "" {
|
|
sentTo = req.Email
|
|
medium = domain.OtpMediumEmail
|
|
} else if req.PhoneNumber != "" {
|
|
sentTo = req.PhoneNumber
|
|
medium = domain.OtpMediumSms
|
|
} else {
|
|
return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided")
|
|
}
|
|
|
|
if err := h.userSvc.SendRegisterCode(c.Context(), medium, sentTo, domain.AfroMessage); err != nil {
|
|
h.mongoLoggerSvc.Error("Failed to send register code",
|
|
zap.String("Medium", string(medium)),
|
|
zap.String("Send To", string(sentTo)),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send register code:"+err.Error())
|
|
}
|
|
|
|
return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil)
|
|
}
|
|
|
|
// RegisterUser godoc
|
|
// @Summary Register user
|
|
// @Description Register user
|
|
// @Tags user
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param registerUser body domain.RegisterUserReq true "Register user"
|
|
// @Success 200 {object} response.APIResponse
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Router /api/v1/{tenant_slug}/user/register [post]
|
|
func (h *Handler) RegisterUser(c *fiber.Ctx) error {
|
|
var req domain.RegisterUserReq
|
|
if err := c.BodyParser(&req); err != nil {
|
|
h.mongoLoggerSvc.Info("Failed to parse RegisterUser request",
|
|
zap.Int("status_code", fiber.StatusBadRequest),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Failed to register user",
|
|
Error: "Invalid request body: " + err.Error(),
|
|
})
|
|
}
|
|
|
|
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
|
var errMsg string
|
|
for field, msg := range valErrs {
|
|
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
|
}
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Failed to register user",
|
|
Error: errMsg,
|
|
})
|
|
}
|
|
|
|
user := domain.RegisterUserReq{
|
|
FirstName: req.FirstName,
|
|
LastName: req.LastName,
|
|
UserName: req.UserName,
|
|
Email: req.Email,
|
|
PhoneNumber: req.PhoneNumber,
|
|
Password: req.Password,
|
|
OtpMedium: domain.OtpMediumEmail,
|
|
Role: string(domain.RoleStudent),
|
|
Age: req.Age,
|
|
EducationLevel: req.EducationLevel,
|
|
Country: req.Country,
|
|
Region: req.Region,
|
|
PreferredLanguage: req.PreferredLanguage,
|
|
}
|
|
|
|
medium, err := getMedium(req.Email, req.PhoneNumber)
|
|
if err != nil {
|
|
h.mongoLoggerSvc.Info("Failed to get medium",
|
|
zap.String("email", req.Email),
|
|
zap.String("phone number", req.PhoneNumber),
|
|
zap.Int("status_code", fiber.StatusBadRequest),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Failed to register user",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
user.OtpMedium = medium
|
|
|
|
_, err = h.userSvc.RegisterUser(c.Context(), user)
|
|
if err != nil {
|
|
h.mongoLoggerSvc.Error("Failed to register user",
|
|
zap.String("email", req.Email),
|
|
zap.String("phone number", req.PhoneNumber),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
|
|
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
|
Message: "Failed to register user",
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
|
Message: "Registration successful",
|
|
Data: nil,
|
|
})
|
|
}
|
|
|
|
type ResetCodeReq struct {
|
|
Email string `json:"email" example:"john.doe@example.com"`
|
|
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
|
|
// Provider domain.OtpProvider `json:"provider" validate:"required" example:"twilio"`
|
|
}
|
|
|
|
// SendResetCode godoc
|
|
// @Summary Send reset code
|
|
// @Description Send reset code
|
|
// @Tags user
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param resetCode body ResetCodeReq true "Send reset code"
|
|
// @Success 200 {object} response.APIResponse
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Router /api/v1/user/sendResetCode [post]
|
|
func (h *Handler) SendResetCode(c *fiber.Ctx) error {
|
|
var req ResetCodeReq
|
|
if err := c.BodyParser(&req); err != nil {
|
|
h.mongoLoggerSvc.Info("Failed to parse SendResetCode request",
|
|
zap.Int("status_code", fiber.StatusBadRequest),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
|
|
}
|
|
|
|
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
|
var errMsg string
|
|
for field, msg := range valErrs {
|
|
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
|
}
|
|
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
|
}
|
|
|
|
var sentTo string
|
|
var medium domain.OtpMedium
|
|
if req.Email != "" {
|
|
sentTo = req.Email
|
|
medium = domain.OtpMediumEmail
|
|
} else if req.PhoneNumber != "" {
|
|
sentTo = req.PhoneNumber
|
|
medium = domain.OtpMediumSms
|
|
} else {
|
|
h.mongoLoggerSvc.Info("Email or PhoneNumber must be provided",
|
|
zap.String("Email", req.Email),
|
|
zap.String("Phone Number", req.PhoneNumber),
|
|
zap.Int("status_code", fiber.StatusBadRequest),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided")
|
|
}
|
|
|
|
if err := h.userSvc.SendResetCode(c.Context(), medium, sentTo, domain.AfroMessage); err != nil {
|
|
h.mongoLoggerSvc.Error("Failed to send reset code",
|
|
zap.String("medium", string(medium)),
|
|
zap.String("sentTo", string(sentTo)),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send reset code:"+err.Error())
|
|
}
|
|
|
|
return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil)
|
|
}
|
|
|
|
// SendTenantResetCode godoc
|
|
// @Summary Send reset code
|
|
// @Description Send reset code
|
|
// @Tags user
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param resetCode body ResetCodeReq true "Send reset code"
|
|
// @Success 200 {object} response.APIResponse
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Router /api/v1/{tenant_slug}/user/sendResetCode [post]
|
|
func (h *Handler) SendTenantResetCode(c *fiber.Ctx) error {
|
|
// companyID := c.Locals("company_id").(domain.ValidInt64)
|
|
// if !companyID.Valid {
|
|
// h.BadRequestLogger().Error("invalid company id")
|
|
// return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
|
|
// }
|
|
var req ResetCodeReq
|
|
if err := c.BodyParser(&req); err != nil {
|
|
h.mongoLoggerSvc.Info("Failed to parse SendResetCode request",
|
|
zap.Int("status_code", fiber.StatusBadRequest),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
|
|
}
|
|
|
|
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
|
var errMsg string
|
|
for field, msg := range valErrs {
|
|
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
|
}
|
|
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
|
}
|
|
|
|
var sentTo string
|
|
var medium domain.OtpMedium
|
|
if req.Email != "" {
|
|
sentTo = req.Email
|
|
medium = domain.OtpMediumEmail
|
|
} else if req.PhoneNumber != "" {
|
|
sentTo = req.PhoneNumber
|
|
medium = domain.OtpMediumSms
|
|
} else {
|
|
h.mongoLoggerSvc.Info("Email or PhoneNumber must be provided",
|
|
zap.String("Email", req.Email),
|
|
zap.String("Phone Number", req.PhoneNumber),
|
|
zap.Int("status_code", fiber.StatusBadRequest),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided")
|
|
}
|
|
|
|
if err := h.userSvc.SendResetCode(c.Context(), medium, sentTo, domain.AfroMessage); err != nil {
|
|
h.mongoLoggerSvc.Error("Failed to send reset code",
|
|
zap.String("medium", string(medium)),
|
|
zap.String("sentTo", string(sentTo)),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to send reset code:"+err.Error())
|
|
}
|
|
|
|
return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil)
|
|
}
|
|
|
|
type ResetPasswordReq struct {
|
|
UserName string `json:"user_name" validate:"required" example:"johndoe"`
|
|
Password string `json:"password" validate:"required,min=8" example:"newpassword123"`
|
|
Otp string `json:"otp" validate:"required" example:"123456"`
|
|
}
|
|
|
|
// ResetPassword godoc
|
|
// @Summary Reset password
|
|
// @Description Reset password
|
|
// @Tags user
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param resetPassword body ResetPasswordReq true "Reset password"
|
|
// @Success 200 {object} response.APIResponse
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Router /api/v1/user/resetPassword [post]
|
|
func (h *Handler) ResetPassword(c *fiber.Ctx) error {
|
|
|
|
var req ResetPasswordReq
|
|
if err := c.BodyParser(&req); err != nil {
|
|
h.mongoLoggerSvc.Info("Failed to parse ResetPassword request",
|
|
zap.Int("status_code", fiber.StatusBadRequest),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
|
|
}
|
|
|
|
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
|
var errMsg string
|
|
for field, msg := range valErrs {
|
|
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
|
}
|
|
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
|
}
|
|
|
|
// user, err := h.userSvc.GetUserByUserName(c.Context(), req.UserName)
|
|
// if err != nil {
|
|
// h.mongoLoggerSvc.Info("Failed to get user by user name",
|
|
// zap.String("user_name", req.UserName),
|
|
// zap.Int("status_code", fiber.StatusBadRequest),
|
|
// zap.Error(err),
|
|
// zap.Time("timestamp", time.Now()),
|
|
// )
|
|
// }
|
|
|
|
// medium, err := getMedium(user.Email, user.PhoneNumber)
|
|
// if err != nil {
|
|
// h.mongoLoggerSvc.Info("Failed to determine medium for ResetPassword",
|
|
// zap.String("Email", user.Email),
|
|
// zap.String("Phone Number", user.PhoneNumber),
|
|
// zap.Int("status_code", fiber.StatusBadRequest),
|
|
// zap.Error(err),
|
|
// zap.Time("timestamp", time.Now()),
|
|
// )
|
|
// return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
|
// }
|
|
|
|
resetReq := domain.ResetPasswordReq{
|
|
UserName: req.UserName,
|
|
Password: req.Password,
|
|
OtpCode: req.Otp,
|
|
}
|
|
|
|
if err := h.userSvc.ResetPassword(c.Context(), resetReq); err != nil {
|
|
h.mongoLoggerSvc.Error("Failed to reset password",
|
|
zap.Any("userID", resetReq),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to reset password:"+err.Error())
|
|
}
|
|
|
|
return response.WriteJSON(c, fiber.StatusOK, "Password reset successful", nil, nil)
|
|
}
|
|
|
|
// ResetTenantPassword godoc
|
|
// @Summary Reset tenant password
|
|
// @Description Reset tenant password
|
|
// @Tags user
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param resetPassword body ResetPasswordReq true "Reset password"
|
|
// @Success 200 {object} response.APIResponse
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Router /api/v1/{tenant_slug}/user/resetPassword [post]
|
|
func (h *Handler) ResetTenantPassword(c *fiber.Ctx) error {
|
|
// companyID := c.Locals("company_id").(domain.ValidInt64)
|
|
// if !companyID.Valid {
|
|
// h.BadRequestLogger().Error("invalid company id")
|
|
// return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
|
|
// }
|
|
|
|
var req ResetPasswordReq
|
|
if err := c.BodyParser(&req); err != nil {
|
|
h.mongoLoggerSvc.Info("Failed to parse ResetPassword request",
|
|
zap.Int("status_code", fiber.StatusBadRequest),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body:"+err.Error())
|
|
}
|
|
|
|
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
|
var errMsg string
|
|
for field, msg := range valErrs {
|
|
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
|
}
|
|
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
|
}
|
|
|
|
// medium, err := getMedium(req.Email, req.PhoneNumber)
|
|
// if err != nil {
|
|
// h.mongoLoggerSvc.Info("Failed to determine medium for ResetPassword",
|
|
// zap.String("Email", req.Email),
|
|
// zap.String("Phone Number", req.PhoneNumber),
|
|
// zap.Int("status_code", fiber.StatusBadRequest),
|
|
// zap.Error(err),
|
|
// zap.Time("timestamp", time.Now()),
|
|
// )
|
|
// return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
|
// }
|
|
|
|
resetReq := domain.ResetPasswordReq{
|
|
UserName: req.UserName,
|
|
Password: req.Password,
|
|
OtpCode: req.Otp,
|
|
}
|
|
|
|
if err := h.userSvc.ResetPassword(c.Context(), resetReq); err != nil {
|
|
h.mongoLoggerSvc.Error("Failed to reset password",
|
|
zap.Any("userID", resetReq),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to reset password:"+err.Error())
|
|
}
|
|
|
|
return response.WriteJSON(c, fiber.StatusOK, "Password reset successful", nil, nil)
|
|
}
|
|
|
|
// CustomerProfile godoc
|
|
// @Summary Get user profile
|
|
// @Description Get user profile
|
|
// @Tags user
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Success 200 {object} domain.UserProfileResponse
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Security Bearer
|
|
// @Router /api/v1/{tenant_slug}/user/user-profile [get]
|
|
func (h *Handler) GetUserProfile(c *fiber.Ctx) error {
|
|
|
|
userID, ok := c.Locals("user_id").(int64)
|
|
if !ok || userID == 0 {
|
|
h.mongoLoggerSvc.Error("Invalid user ID in context",
|
|
zap.Int64("userID", userID),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Invalid user identification")
|
|
}
|
|
|
|
user, err := h.userSvc.GetUserByID(c.Context(), userID)
|
|
if err != nil {
|
|
h.mongoLoggerSvc.Error("Failed to get user profile",
|
|
zap.Int64("userID", userID),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user profile:"+err.Error())
|
|
}
|
|
|
|
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
|
|
if err != nil {
|
|
if err != authentication.ErrRefreshTokenNotFound {
|
|
h.mongoLoggerSvc.Error("Failed to get user last login",
|
|
zap.Int64("userID", userID),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login:"+err.Error())
|
|
}
|
|
|
|
lastLogin = &user.CreatedAt
|
|
}
|
|
|
|
res := domain.UserProfileResponse{
|
|
ID: user.ID,
|
|
FirstName: user.FirstName,
|
|
LastName: user.LastName,
|
|
UserName: user.UserName,
|
|
Email: user.Email,
|
|
PhoneNumber: user.PhoneNumber,
|
|
Role: user.Role,
|
|
Age: user.Age,
|
|
EducationLevel: user.EducationLevel,
|
|
Country: user.Country,
|
|
Region: user.Region,
|
|
EmailVerified: user.EmailVerified,
|
|
PhoneVerified: user.PhoneVerified,
|
|
Status: user.Status,
|
|
LastLogin: lastLogin,
|
|
ProfileCompleted: user.ProfileCompleted,
|
|
ProfilePictureURL: user.ProfilePictureURL,
|
|
PreferredLanguage: user.PreferredLanguage,
|
|
CreatedAt: user.CreatedAt,
|
|
UpdatedAt: user.UpdatedAt,
|
|
}
|
|
return response.WriteJSON(c, fiber.StatusOK, "User profile retrieved successfully", res, nil)
|
|
}
|
|
|
|
type AdminProfileRes struct {
|
|
ID int64 `json:"id"`
|
|
FirstName string `json:"first_name"`
|
|
LastName string `json:"last_name"`
|
|
Email string `json:"email"`
|
|
PhoneNumber string `json:"phone_number"`
|
|
Role domain.Role `json:"role"`
|
|
EmailVerified bool `json:"email_verified"`
|
|
PhoneVerified bool `json:"phone_verified"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
LastLogin time.Time `json:"last_login"`
|
|
SuspendedAt time.Time `json:"suspended_at"`
|
|
Suspended bool `json:"suspended"`
|
|
}
|
|
|
|
// AdminProfile godoc
|
|
// @Summary Get user profile
|
|
// @Description Get user profile
|
|
// @Tags user
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Success 200 {object} AdminProfileRes
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Security Bearer
|
|
// @Router /api/v1/{tenant_slug}/user/admin-profile [get]
|
|
func (h *Handler) AdminProfile(c *fiber.Ctx) error {
|
|
|
|
userID, ok := c.Locals("user_id").(int64)
|
|
if !ok || userID == 0 {
|
|
h.mongoLoggerSvc.Error("Invalid user ID in context",
|
|
zap.Int64("userID", userID),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Invalid user identification")
|
|
}
|
|
|
|
user, err := h.userSvc.GetUserByID(c.Context(), userID)
|
|
if err != nil {
|
|
h.mongoLoggerSvc.Error("Failed to get user profile",
|
|
zap.Int64("userID", userID),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user profile:"+err.Error())
|
|
}
|
|
|
|
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
|
|
if err != nil {
|
|
if err != authentication.ErrRefreshTokenNotFound {
|
|
h.mongoLoggerSvc.Error("Failed to get user last login",
|
|
zap.Int64("userID", userID),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login:"+err.Error())
|
|
}
|
|
|
|
lastLogin = &user.CreatedAt
|
|
}
|
|
res := domain.UserProfileResponse{
|
|
ID: user.ID,
|
|
FirstName: user.FirstName,
|
|
LastName: user.LastName,
|
|
UserName: user.UserName,
|
|
Email: user.Email,
|
|
PhoneNumber: user.PhoneNumber,
|
|
Role: user.Role,
|
|
Age: user.Age,
|
|
EducationLevel: user.EducationLevel,
|
|
Country: user.Country,
|
|
Region: user.Region,
|
|
EmailVerified: user.EmailVerified,
|
|
PhoneVerified: user.PhoneVerified,
|
|
Status: user.Status,
|
|
LastLogin: lastLogin,
|
|
ProfileCompleted: user.ProfileCompleted,
|
|
ProfilePictureURL: user.ProfilePictureURL,
|
|
PreferredLanguage: user.PreferredLanguage,
|
|
CreatedAt: user.CreatedAt,
|
|
UpdatedAt: user.UpdatedAt,
|
|
}
|
|
|
|
return response.WriteJSON(c, fiber.StatusOK, "User profile retrieved successfully", res, nil)
|
|
}
|
|
|
|
// Helper function (unchanged)
|
|
func getMedium(email, phoneNumber string) (domain.OtpMedium, error) {
|
|
if email != "" {
|
|
return domain.OtpMediumEmail, nil
|
|
}
|
|
if phoneNumber != "" {
|
|
return domain.OtpMediumSms, nil
|
|
}
|
|
return "", errors.New("both email and phone number are empty")
|
|
}
|
|
|
|
type SearchUserByNameOrPhoneReq struct {
|
|
SearchString string `json:"query"`
|
|
Role *domain.Role `json:"role,omitempty"`
|
|
}
|
|
|
|
// SearchUserByNameOrPhone godoc
|
|
// @Summary Search for user using name or phone
|
|
// @Description Search for user using name or phone
|
|
// @Tags user
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param searchUserByNameOrPhone body SearchUserByNameOrPhoneReq true "Search for using his name or phone"
|
|
// @Success 200 {object} domain.UserProfileResponse
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Router /api/v1/user/search [post]
|
|
func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error {
|
|
var req SearchUserByNameOrPhoneReq
|
|
if err := c.BodyParser(&req); err != nil {
|
|
h.mongoLoggerSvc.Error("SearchUserByNameOrPhone failed - invalid request body",
|
|
zap.Any("request", req),
|
|
zap.Int("status_code", fiber.StatusBadRequest),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body: "+err.Error())
|
|
}
|
|
|
|
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
|
var errMsg string
|
|
for field, msg := range valErrs {
|
|
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
|
}
|
|
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
|
}
|
|
|
|
// Optional role filter
|
|
var rolePtr *int64
|
|
if req.Role != nil && *req.Role != "" {
|
|
roleStr := string(*req.Role)
|
|
roleVal, err := strconv.ParseInt(roleStr, 10, 64)
|
|
if err != nil {
|
|
h.mongoLoggerSvc.Info("Invalid role value",
|
|
zap.String("role", roleStr),
|
|
zap.Int("status_code", fiber.StatusBadRequest),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusBadRequest, "invalid role value")
|
|
}
|
|
rolePtr = &roleVal
|
|
}
|
|
|
|
users, err := h.userSvc.SearchUserByNameOrPhone(c.Context(), req.SearchString, rolePtr)
|
|
if err != nil {
|
|
h.mongoLoggerSvc.Error("SearchUserByNameOrPhone - failed to fetch users",
|
|
zap.Any("request", req),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get users: "+err.Error())
|
|
}
|
|
|
|
res := make([]domain.UserProfileResponse, 0, len(users))
|
|
for _, user := range users {
|
|
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
|
|
if err != nil && err != authentication.ErrRefreshTokenNotFound {
|
|
h.mongoLoggerSvc.Error("Failed to get user last login",
|
|
zap.Int64("user_id", user.ID),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login: "+err.Error())
|
|
}
|
|
if err == authentication.ErrRefreshTokenNotFound {
|
|
lastLogin = &user.CreatedAt
|
|
}
|
|
|
|
// var orgID *int64
|
|
// if user.OrganizationID.Valid {
|
|
// orgID = &user.OrganizationID.Value
|
|
// }
|
|
|
|
res = append(res, domain.UserProfileResponse{
|
|
ID: user.ID,
|
|
FirstName: user.FirstName,
|
|
LastName: user.LastName,
|
|
UserName: user.UserName,
|
|
Email: user.Email,
|
|
PhoneNumber: user.PhoneNumber,
|
|
Role: user.Role,
|
|
Age: user.Age,
|
|
EducationLevel: user.EducationLevel,
|
|
Country: user.Country,
|
|
Region: user.Region,
|
|
EmailVerified: user.EmailVerified,
|
|
PhoneVerified: user.PhoneVerified,
|
|
Status: user.Status,
|
|
LastLogin: lastLogin,
|
|
ProfileCompleted: user.ProfileCompleted,
|
|
ProfilePictureURL: user.ProfilePictureURL,
|
|
PreferredLanguage: user.PreferredLanguage,
|
|
CreatedAt: user.CreatedAt,
|
|
UpdatedAt: user.UpdatedAt,
|
|
})
|
|
}
|
|
|
|
return response.WriteJSON(c, fiber.StatusOK, "Search successful", res, nil)
|
|
}
|
|
|
|
// GetUserByID godoc
|
|
// @Summary Get user by id
|
|
// @Description Get a single user by id
|
|
// @Tags user
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path int true "User ID"
|
|
// @Success 200 {object} domain.UserProfileResponse
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 401 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Router /api/v1/user/single/{id} [get]
|
|
func (h *Handler) GetUserByID(c *fiber.Ctx) error {
|
|
userIDstr := c.Params("id")
|
|
userID, err := strconv.ParseInt(userIDstr, 10, 64)
|
|
if err != nil {
|
|
h.mongoLoggerSvc.Info("failed to parse user id",
|
|
zap.String("userID", userIDstr),
|
|
zap.Int("status_code", fiber.StatusBadRequest),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusBadRequest, "invalid user id")
|
|
}
|
|
|
|
user, err := h.userSvc.GetUserByID(c.Context(), userID)
|
|
if err != nil {
|
|
h.mongoLoggerSvc.Error("Get User By ID failed",
|
|
zap.Int64("userID", userID),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get user: "+err.Error())
|
|
}
|
|
|
|
lastLogin, err := h.authSvc.GetLastLogin(c.Context(), user.ID)
|
|
if err != nil && err != authentication.ErrRefreshTokenNotFound {
|
|
h.mongoLoggerSvc.Error("Failed to get user last login",
|
|
zap.Int64("userID", user.ID),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user last login: "+err.Error())
|
|
}
|
|
if err == authentication.ErrRefreshTokenNotFound {
|
|
lastLogin = &user.CreatedAt
|
|
}
|
|
|
|
// var orgID *int64
|
|
// if user.OrganizationID.Valid {
|
|
// orgID = &user.OrganizationID.Value
|
|
// }
|
|
|
|
res := domain.UserProfileResponse{
|
|
ID: user.ID,
|
|
FirstName: user.FirstName,
|
|
LastName: user.LastName,
|
|
UserName: user.UserName,
|
|
Email: user.Email,
|
|
PhoneNumber: user.PhoneNumber,
|
|
Role: user.Role,
|
|
Age: user.Age,
|
|
EducationLevel: user.EducationLevel,
|
|
Country: user.Country,
|
|
Region: user.Region,
|
|
EmailVerified: user.EmailVerified,
|
|
PhoneVerified: user.PhoneVerified,
|
|
Status: user.Status,
|
|
LastLogin: lastLogin,
|
|
ProfileCompleted: user.ProfileCompleted,
|
|
ProfilePictureURL: user.ProfilePictureURL,
|
|
PreferredLanguage: user.PreferredLanguage,
|
|
CreatedAt: user.CreatedAt,
|
|
UpdatedAt: user.UpdatedAt,
|
|
}
|
|
|
|
return response.WriteJSON(c, fiber.StatusOK, "User retrieved successfully", res, nil)
|
|
}
|
|
|
|
// DeleteUser godoc
|
|
// @Summary Delete user by ID
|
|
// @Description Delete a user by their ID
|
|
// @Tags user
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path int true "User ID"
|
|
// @Success 200 {object} response.APIResponse
|
|
// @Failure 400 {object} response.APIResponse
|
|
// @Failure 500 {object} response.APIResponse
|
|
// @Router /api/v1/user/delete/{id} [delete]
|
|
func (h *Handler) DeleteUser(c *fiber.Ctx) error {
|
|
userIDstr := c.Params("id")
|
|
userID, err := strconv.ParseInt(userIDstr, 10, 64)
|
|
if err != nil {
|
|
h.mongoLoggerSvc.Info("Failed to parse user id",
|
|
zap.String("userID", userIDstr),
|
|
zap.Int("status_code", fiber.StatusBadRequest),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid user ID")
|
|
}
|
|
|
|
err = h.userSvc.DeleteUser(c.Context(), userID)
|
|
if err != nil {
|
|
h.mongoLoggerSvc.Error("Failed to delete user",
|
|
zap.Int64("userID", userID),
|
|
zap.Int("status_code", fiber.StatusInternalServerError),
|
|
zap.Error(err),
|
|
zap.Time("timestamp", time.Now()),
|
|
)
|
|
return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete user:"+err.Error())
|
|
}
|
|
|
|
return response.WriteJSON(c, fiber.StatusOK, "User deleted successfully", nil, nil)
|
|
}
|
|
|
|
type UpdateUserSuspendReq struct {
|
|
UserID int64 `json:"user_id" validate:"required" example:"123"`
|
|
Suspended bool `json:"suspended" example:"true"`
|
|
}
|
|
type UpdateUserSuspendRes struct {
|
|
UserID int64 `json:"user_id"`
|
|
Suspended bool `json:"suspended"`
|
|
}
|