package handlers import ( "errors" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/gofiber/fiber/v2" ) // 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 /user/checkPhoneEmailExist [post] func (h *Handler) CheckPhoneEmailExist(c *fiber.Ctx) error { type CheckPhoneEmailExistReq struct { Email string `json:"email" validate:"email" example:"john.doe@example.com"` PhoneNumber string `json:"phone_number" validate:"required" example:"1234567890"` } type CheckPhoneEmailExistRes struct { EmailExist bool `json:"email_exist"` PhoneNumberExist bool `json:"phone_number_exist"` } var req CheckPhoneEmailExistReq if err := c.BodyParser(&req); err != nil { h.logger.Error("Failed to parse CheckPhoneEmailExist request", "error", err) return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } if valErrs, ok := h.validator.Validate(c, req); !ok { return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } emailExist, phoneExist, err := h.userSvc.CheckPhoneEmailExist(c.Context(), req.PhoneNumber, req.Email) if err != nil { h.logger.Error("Failed to check phone/email existence", "error", err) return fiber.NewError(fiber.StatusInternalServerError, "Failed to check phone/email existence") } res := CheckPhoneEmailExistRes{ EmailExist: emailExist, PhoneNumberExist: phoneExist, } return response.WriteJSON(c, fiber.StatusOK, "Check successful", res, nil) } // 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 /user/sendRegisterCode [post] func (h *Handler) SendRegisterCode(c *fiber.Ctx) error { type RegisterCodeReq struct { Email string `json:"email" validate:"email" example:"john.doe@example.com"` PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"` } var req RegisterCodeReq if err := c.BodyParser(&req); err != nil { h.logger.Error("Failed to parse SendRegisterCode request", "error", err) return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } if valErrs, ok := h.validator.Validate(c, req); !ok { return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } 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); err != nil { h.logger.Error("Failed to send register code", "error", err) return fiber.NewError(fiber.StatusInternalServerError, "Failed to send register code") } 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 RegisterUserReq true "Register user" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /user/register [post] func (h *Handler) RegisterUser(c *fiber.Ctx) error { type RegisterUserReq struct { FirstName string `json:"first_name" example:"John"` LastName string `json:"last_name" example:"Doe"` Email string `json:"email" example:"john.doe@example.com"` PhoneNumber string `json:"phone_number" example:"1234567890"` Password string `json:"password" example:"password123"` Otp string `json:"otp" example:"123456"` ReferalCode string `json:"referal_code" example:"ABC123"` } var req RegisterUserReq if err := c.BodyParser(&req); err != nil { h.logger.Error("Failed to parse RegisterUser request", "error", err) return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } if valErrs, ok := h.validator.Validate(c, req); !ok { return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } user := domain.RegisterUserReq{ FirstName: req.FirstName, LastName: req.LastName, Email: req.Email, PhoneNumber: req.PhoneNumber, Password: req.Password, Otp: req.Otp, ReferralCode: req.ReferalCode, OtpMedium: domain.OtpMediumEmail, } medium, err := getMedium(req.Email, req.PhoneNumber) if err != nil { h.logger.Error("RegisterUser failed", "error", err) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": err.Error(), }) } user.OtpMedium = medium newUser, err := h.userSvc.RegisterUser(c.Context(), user) if err != nil { if errors.Is(err, domain.ErrOtpAlreadyUsed) { return response.WriteJSON(c, fiber.StatusBadRequest, "Otp already used", nil, nil) } if errors.Is(err, domain.ErrOtpExpired) { return response.WriteJSON(c, fiber.StatusBadRequest, "Otp expired", nil, nil) } if errors.Is(err, domain.ErrInvalidOtp) { return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid otp", nil, nil) } if errors.Is(err, domain.ErrOtpNotFound) { return response.WriteJSON(c, fiber.StatusBadRequest, "User already exist", nil, nil) } h.logger.Error("RegisterUser failed", "error", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": "Internal server error", }) } _, err = h.walletSvc.CreateWallet(c.Context(), domain.CreateWallet{ UserID: newUser.ID, IsWithdraw: true, IsBettable: true, }) if err != nil { h.logger.Error("Failed to create wallet for user", "userID", newUser.ID, "error", err) return fiber.NewError(fiber.StatusInternalServerError, "Failed to create user wallet") } if req.ReferalCode != "" { err = h.referralSvc.ProcessReferral(c.Context(), req.PhoneNumber, req.ReferalCode) if err != nil { h.logger.Warn("Failed to process referral during registration", "phone", req.PhoneNumber, "code", req.ReferalCode, "error", err) } } return response.WriteJSON(c, fiber.StatusOK, "Registration successful", nil, nil) } // 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 /user/sendResetCode [post] func (h *Handler) SendResetCode(c *fiber.Ctx) error { type ResetCodeReq struct { Email string `json:"email" validate:"email" example:"john.doe@example.com"` PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"` } var req ResetCodeReq if err := c.BodyParser(&req); err != nil { h.logger.Error("Failed to parse SendResetCode request", "error", err) return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } if valErrs, ok := h.validator.Validate(c, req); !ok { return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } 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.SendResetCode(c.Context(), medium, sentTo); err != nil { h.logger.Error("Failed to send reset code", "error", err) return fiber.NewError(fiber.StatusInternalServerError, "Failed to send reset code") } return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil) } // 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 /user/resetPassword [post] func (h *Handler) ResetPassword(c *fiber.Ctx) error { type ResetPasswordReq struct { Email string `json:"email" validate:"email" example:"john.doe@example.com"` PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"` Password string `json:"password" validate:"required,min=8" example:"newpassword123"` Otp string `json:"otp" validate:"required" example:"123456"` } var req ResetPasswordReq if err := c.BodyParser(&req); err != nil { h.logger.Error("Failed to parse ResetPassword request", "error", err) return fiber.NewError(fiber.StatusBadRequest, "Invalid request body") } if valErrs, ok := h.validator.Validate(c, req); !ok { return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) } medium, err := getMedium(req.Email, req.PhoneNumber) if err != nil { h.logger.Error("Failed to determine medium for ResetPassword", "error", err) return fiber.NewError(fiber.StatusBadRequest, err.Error()) } resetReq := domain.ResetPasswordReq{ Email: req.Email, PhoneNumber: req.PhoneNumber, Password: req.Password, Otp: req.Otp, OtpMedium: medium, } if err := h.userSvc.ResetPassword(c.Context(), resetReq); err != nil { h.logger.Error("Failed to reset password", "error", err) return fiber.NewError(fiber.StatusInternalServerError, "Failed to reset password") } return response.WriteJSON(c, fiber.StatusOK, "Password reset successful", nil, nil) } // UserProfile godoc // @Summary Get user profile // @Description Get user profile // @Tags user // @Accept json // @Produce json // @Success 200 {object} UserProfileRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Security Bearer // @Router /user/profile [get] func (h *Handler) UserProfile(c *fiber.Ctx) error { type UserProfileRes 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"` SuspendedAt time.Time `json:"suspended_at"` Suspended bool `json:"suspended"` } userID, ok := c.Locals("user_id").(int64) if !ok || userID == 0 { h.logger.Error("Invalid user ID in context") return fiber.NewError(fiber.StatusUnauthorized, "Invalid user identification") } user, err := h.userSvc.GetUserByID(c.Context(), userID) if err != nil { h.logger.Error("Failed to get user profile", "userID", userID, "error", err) return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve user profile") } res := UserProfileRes{ ID: user.ID, FirstName: user.FirstName, LastName: user.LastName, Email: user.Email, PhoneNumber: user.PhoneNumber, Role: user.Role, EmailVerified: user.EmailVerified, PhoneVerified: user.PhoneVerified, CreatedAt: user.CreatedAt, UpdatedAt: user.UpdatedAt, SuspendedAt: user.SuspendedAt, Suspended: user.Suspended, } 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") }