package handlers import ( "errors" "log/slog" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/user" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator" "github.com/gofiber/fiber/v2" ) 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 /user/checkPhoneEmailExist [post] func CheckPhoneEmailExist(logger *slog.Logger, userSvc *user.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { var req CheckPhoneEmailExistReq if err := c.BodyParser(&req); err != nil { logger.Error("CheckPhoneEmailExist failed", "error", err) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Invalid request", }) } valErrs, ok := validator.Validate(c, req) if !ok { response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return nil } emailExist, phoneExist, err := userSvc.CheckPhoneEmailExist(c.Context(), req.PhoneNumber, req.Email) if err != nil { logger.Error("CheckPhoneEmailExist failed", "error", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": "Internal server error", }) } res := CheckPhoneEmailExistRes{ EmailExist: emailExist, PhoneNumberExist: phoneExist, } return response.WriteJSON(c, fiber.StatusOK, "Check Success", 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 /user/sendRegisterCode [post] func SendRegisterCode(logger *slog.Logger, userSvc *user.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { var req RegisterCodeReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Invalid request", }) } valErrs, ok := validator.Validate(c, req) if !ok { response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return nil } var sentTo string var medium domain.OtpMedium if req.Email != "" { sentTo = req.Email medium = domain.OtpMediumEmail } if req.PhoneNumber != "" { sentTo = req.PhoneNumber medium = domain.OtpMediumSms } if err := userSvc.SendRegisterCode(c.Context(), medium, sentTo); err != nil { logger.Error("SendRegisterCode failed", "error", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": "Internal server error", }) } return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil) } } 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"` //Role string Otp string `json:"otp" example:"123456"` ReferalCode string `json:"referal_code" example:"ABC123"` // } // 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 RegisterUser(logger *slog.Logger, userSvc *user.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { var req RegisterUserReq if err := c.BodyParser(&req); err != nil { logger.Error("RegisterUser failed", "error", err) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Invalid request", }) } valErrs, ok := validator.Validate(c, req) if !ok { response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return nil } user := domain.RegisterUserReq{ FirstName: req.FirstName, LastName: req.LastName, Email: req.Email, PhoneNumber: req.PhoneNumber, Password: req.Password, Otp: req.Otp, ReferalCode: req.ReferalCode, OtpMedium: domain.OtpMediumEmail, } medium, err := getMedium(req.Email, req.PhoneNumber) if err != nil { logger.Error("RegisterUser failed", "error", err) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": err.Error(), }) } user.OtpMedium = medium if _, err := userSvc.RegisterUser(c.Context(), user); 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) } logger.Error("RegisterUser failed", "error", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": "Internal server error", }) } return response.WriteJSON(c, fiber.StatusOK, "Registration successful", nil, nil) } } type ResetCodeReq struct { Email string `json:"email" example:"john.doe@example.com"` PhoneNumber string `json:"phone_number" example:"1234567890"` } // 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 SendResetCode(logger *slog.Logger, userSvc *user.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { var req ResetCodeReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Invalid request", }) } valErrs, ok := validator.Validate(c, req) if !ok { response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return nil } var sentTo string var medium domain.OtpMedium if req.Email != "" { sentTo = req.Email medium = domain.OtpMediumEmail } if req.PhoneNumber != "" { sentTo = req.PhoneNumber medium = domain.OtpMediumSms } if err := userSvc.SendResetCode(c.Context(), medium, sentTo); err != nil { logger.Error("SendResetCode failed", "error", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": "Internal server error", }) } return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil) } } type ResetPasswordReq struct { Email string PhoneNumber string Password string Otp string } // 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 ResetPassword(logger *slog.Logger, userSvc *user.Service, validator *customvalidator.CustomValidator) fiber.Handler { return func(c *fiber.Ctx) error { var req ResetPasswordReq if err := c.BodyParser(&req); err != nil { logger.Error("ResetPassword failed", "error", err) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": "Invalid request", }) } valErrs, ok := validator.Validate(c, req) if !ok { response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) return nil } user := domain.ResetPasswordReq{ Email: req.Email, PhoneNumber: req.PhoneNumber, Password: req.Password, Otp: req.Otp, } medium, err := getMedium(req.Email, req.PhoneNumber) if err != nil { logger.Error("ResetPassword failed", "error", err) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "error": err.Error(), }) } user.OtpMedium = medium if err := userSvc.ResetPassword(c.Context(), user); err != nil { logger.Error("ResetPassword failed", "error", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": "Internal server error", }) } return response.WriteJSON(c, fiber.StatusOK, "Password reset successful", nil, nil) } } 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"` } // 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 UserProfile(logger *slog.Logger, userSvc *user.Service) fiber.Handler { return func(c *fiber.Ctx) error { userId := c.Locals("user_id").(int64) user, err := userSvc.GetUserByID(c.Context(), userId) if err != nil { logger.Error("GetUserProfile failed", "error", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": "Internal server error", }) } 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) } } 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") }