package handlers import ( "errors" "fmt" "strconv" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/gofiber/fiber/v2" "go.uber.org/zap" ) 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, companyID) 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, companyID); 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) } 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"` } // 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 /api/v1/{tenant_slug}/user/register [post] func (h *Handler) RegisterUser(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 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 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 := 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, CompanyID: companyID, Role: string(domain.RoleCustomer), } 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 fiber.NewError(fiber.StatusBadRequest, err.Error()) } user.OtpMedium = medium newUser, err := h.userSvc.RegisterUser(c.Context(), user) if err != nil { if errors.Is(err, domain.ErrOtpAlreadyUsed) { return fiber.NewError(fiber.StatusBadRequest, "Otp already used") } if errors.Is(err, domain.ErrOtpExpired) { return fiber.NewError(fiber.StatusBadRequest, "Otp expired") } if errors.Is(err, domain.ErrInvalidOtp) { return fiber.NewError(fiber.StatusBadRequest, "Invalid otp") } if errors.Is(err, domain.ErrOtpNotFound) { return fiber.NewError(fiber.StatusBadRequest, "User already exist") } 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 fiber.NewError(fiber.StatusInternalServerError, "failed to register user:"+err.Error()) } newWallet, err := h.walletSvc.CreateCustomerWallet(c.Context(), newUser.ID) if err != nil { h.mongoLoggerSvc.Error("Failed to create wallet for user", zap.Int64("userID", newUser.ID), zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, "Failed to create user wallet:"+err.Error()) } if req.ReferalCode != "" { err = h.referralSvc.ProcessReferral(c.Context(), req.PhoneNumber, req.ReferalCode, companyID.Value) if err != nil { h.mongoLoggerSvc.Error("Failed to process referral during registration", zap.String("phone", req.PhoneNumber), zap.String("code", req.ReferalCode), zap.Error(err), zap.Time("timestamp", time.Now()), ) } } // TODO: Remove later _, err = h.walletSvc.AddToWallet( c.Context(), newWallet.RegularID, domain.ToCurrency(10000.0), domain.ValidInt64{}, domain.TRANSFER_DIRECT, domain.PaymentDetails{}, "Added 10000.0 to wallet only as test for deployment") if err != nil { h.mongoLoggerSvc.Error("Failed to update wallet for user", zap.Int64("userID", newUser.ID), zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, "Failed to update user wallet:"+err.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" 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/{tenant_slug}/user/sendResetCode [post] func (h *Handler) SendResetCode(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, companyID); 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 { Email string `json:"email,omitempty" validate:"required_without=PhoneNumber,omitempty,email" example:"john.doe@example.com"` PhoneNumber string `json:"phone_number,omitempty" validate:"required_without=Email,omitempty" example:"1234567890"` 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/{tenant_slug}/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) } 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{ 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.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) } 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"` LastLogin time.Time `json:"last_login"` SuspendedAt time.Time `json:"suspended_at"` Suspended bool `json:"suspended"` ReferralCode string `json:"referral_code"` } type CustomerProfileRes 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"` ReferralCode string `json:"referral_code"` } // CustomerProfile godoc // @Summary Get user profile // @Description Get user profile // @Tags user // @Accept json // @Produce json // @Success 200 {object} CustomerProfileRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Security Bearer // @Router /api/v1/{tenant_slug}/user/customer-profile [get] func (h *Handler) CustomerProfile(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 := CustomerProfileRes{ 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, LastLogin: *lastLogin, } 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 := 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, LastLogin: *lastLogin, } 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} UserProfileRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/{tenant_slug}/user/search [post] func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error { // TODO: Add filtering by role based on which user is calling this var req SearchUserByNameOrPhoneReq if err := c.BodyParser(&req); err != nil { h.mongoLoggerSvc.Error("Failed to Search UserBy Name Or Phone failed", 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()) } valErrs, ok := h.validator.Validate(c, req) if !ok { var errMsg string for field, msg := range valErrs { errMsg += fmt.Sprintf("%s: %s; ", field, msg) } return fiber.NewError(fiber.StatusBadRequest, errMsg) } companyID := c.Locals("company_id").(domain.ValidInt64) users, err := h.userSvc.SearchUserByNameOrPhone(c.Context(), req.SearchString, req.Role, companyID) if err != nil { h.mongoLoggerSvc.Error("Failed to get user by name or phone", zap.Any("request", req), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "failed to get users"+err.Error()) } var res []UserProfileRes = make([]UserProfileRes, 0, len(users)) for _, user := range users { 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.Any("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()) } lastLogin = &user.CreatedAt } res = append(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, LastLogin: *lastLogin, }) } 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} UserProfileRes // @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 { // branchId := int64(12) //c.Locals("branch_id").(int64) // filter := user.Filter{ // Role: string(domain.RoleUser), // BranchId: user.ValidBranchId{ // Value: branchId, // Valid: true, // }, // Page: c.QueryInt("page", 1), // PageSize: c.QueryInt("page_size", 10), // } // valErrs, ok := validator.Validate(c, filter) // if !ok { // return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil) // } 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 cashiers:"+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", 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()) } lastLogin = &user.CreatedAt } 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, LastLogin: *lastLogin, } 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"` } // UpdateUserSuspend godoc // @Summary Suspend or unsuspend a user // @Description Suspend or unsuspend a user // @Tags user // @Accept json // @Produce json // @Param updateUserSuspend body UpdateUserSuspendReq true "Suspend or unsuspend a user" // @Success 200 {object} UpdateUserSuspendRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/user/suspend [post] func (h *Handler) UpdateUserSuspend(c *fiber.Ctx) error { var req UpdateUserSuspendReq if err := c.BodyParser(&req); err != nil { h.mongoLoggerSvc.Info("Failed to parse UpdateUserSuspend 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) } err := h.userSvc.UpdateUserSuspend(c.Context(), req.UserID, req.Suspended) if err != nil { h.mongoLoggerSvc.Error("Failed to update user suspend status", zap.Any("UpdateUserSuspendReq", req), zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, "Failed to update user suspend status:"+err.Error()) } res := UpdateUserSuspendRes{ UserID: req.UserID, Suspended: req.Suspended, } return response.WriteJSON(c, fiber.StatusOK, "User suspend status updated successfully", res, nil) } // GetBetByUserID godoc // @Summary Gets user bets // @Description Gets user bets // @Tags user // @Accept json // @Produce json // @Success 200 {array} domain.BetRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/{tenant_slug}/user/bets [get] func (h *Handler) GetBetByUserID(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") } bets, err := h.betSvc.GetBetByUserID(c.Context(), userID) if err != nil { h.mongoLoggerSvc.Error("Failed to get bets", 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 bets:"+err.Error()) } res := make([]domain.BetRes, len(bets)) for i, bet := range bets { res[i] = domain.ConvertBet(bet) } return response.WriteJSON(c, fiber.StatusOK, "User bets retrieved successfully", res, nil) }