377 lines
12 KiB
Go
377 lines
12 KiB
Go
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/services/wallet"
|
|
"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, walletSvc *wallet.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
|
|
newUser, err := 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)
|
|
}
|
|
logger.Error("RegisterUser failed", "error", err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
"error": "Internal server error",
|
|
})
|
|
}
|
|
|
|
// TODO: Integrate company when we move to multi-vendor system
|
|
_, err = walletSvc.CreateCustomerWallet(c.Context(), newUser.ID, 0)
|
|
|
|
if err != nil {
|
|
logger.Error("CreateCustomerWallet failed", "error", err)
|
|
return response.WriteJSON(c, fiber.StatusInternalServerError, "Failed to create customer wallet for user", err, nil)
|
|
}
|
|
|
|
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")
|
|
}
|