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" ) // CheckProfileCompleted godoc // @Summary Check if user profile is completed // @Description Returns whether the specified user's profile is completed // @Tags user // @Accept json // @Produce json // @Param user_id path int true "User ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 404 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/user/{user_id}/is-profile-completed [get] func (h *Handler) CheckProfileCompleted(c *fiber.Ctx) error { userIDParam := c.Params("user_id") if userIDParam == "" { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid user id", Error: "User id cannot be empty", }) } userID, err := strconv.ParseInt(userIDParam, 10, 64) if err != nil || userID <= 0 { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid user id", Error: "User id must be a valid positive integer", }) } isCompleted, err := h.userSvc.IsProfileCompleted(c.Context(), userID) 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 profile completion status", Error: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "Profile completion status fetched successfully", Data: map[string]bool{ "is_profile_completed": isCompleted, }, }) } // UpdateUser godoc // @Summary Update user profile // @Description Updates user profile information (partial updates supported) // @Tags user // @Accept json // @Produce json // @Param user_id path int true "User ID" // @Param body body domain.UpdateUserReq true "Update user payload" // @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 [put] func (h *Handler) UpdateUser(c *fiber.Ctx) error { // Extract user ID from context userIDStr, ok := c.Locals("user_id").(string) if !ok { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid user context", Error: "User ID not found in request context", }) } userID, err := strconv.ParseInt(userIDStr, 10, 64) if err != nil || userID <= 0 { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid user ID", Error: "User ID must be a positive integer", }) } // Parse request body var req domain.UpdateUserReq if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: err.Error(), }) } // Enforce user identity req.UserID = userID // Optional: lightweight validation (example) // if req.Status.IsSet() { // if !domain.(req.Status.Value) { // return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ // Message: "Invalid status value", // Error: "Unsupported user status", // }) // } // } // Call service if err := h.userSvc.UpdateUser(c.Context(), req); 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 update user", Error: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "User updated successfully", }) } // UpdateUserKnowledgeLevel godoc // @Summary Update user's knowledge level // @Description Updates the knowledge level of the specified user after initial assessment // @Tags user // @Accept json // @Produce json // @Param user_id path int true "User ID" // @Param knowledge_level body domain.UpdateKnowledgeLevelReq true "Knowledge level" // @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/knowledge-level [put] func (h *Handler) UpdateUserKnowledgeLevel(c *fiber.Ctx) error { userIDStr := c.Locals("user_id").(string) userID, err := strconv.ParseInt(userIDStr, 10, 64) if err != nil || userID <= 0 { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid user ID", Error: "User ID must be a positive integer", }) } var req domain.UpdateKnowledgeLevelReq if err := c.BodyParser(&req); err != nil || req.KnowledgeLevel == "" { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid request body", Error: "Knowledge level is required", }) } err = h.userSvc.UpdateUserKnowledgeLevel(c.Context(), userID, req.KnowledgeLevel) 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 update user knowledge level", Error: err.Error(), }) } return c.Status(fiber.StatusOK).JSON(domain.Response{ Message: "User knowledge level updated successfully", }) } // 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.GetUserByEmailPhone(c.Context(), req.Email, req.PhoneNumber) if err != nil { h.mongoLoggerSvc.Info("Failed to get user by user name", 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 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.Email, req.PhoneNumber, ); 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", Success: true, StatusCode: fiber.StatusOK, 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, }, }) } // GetAllUsers godoc // @Summary Get all users // @Description Get users with optional filters // @Tags user // @Accept json // @Produce json // @Param role query string false "Role filter" // @Param query query string false "Search query" // @Param page query int false "Page number" // @Param page_size query int false "Page size" // @Param created_before query string false "Created before (RFC3339)" // @Param created_after query string false "Created after (RFC3339)" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/{tenant_slug}/users [get] func (h *Handler) GetAllUsers(c *fiber.Ctx) error { searchQuery := c.Query("query") searchString := domain.ValidString{ Value: searchQuery, Valid: searchQuery != "", } createdBeforeQuery := c.Query("created_before") var createdBefore domain.ValidTime if createdBeforeQuery != "" { parsed, err := time.Parse(time.RFC3339, createdBeforeQuery) if err != nil { h.logger.Info("invalid created_before format", "error", err) return fiber.NewError(fiber.StatusBadRequest, "Invalid created_before format") } createdBefore = domain.ValidTime{Value: parsed, Valid: true} } createdAfterQuery := c.Query("created_after") var createdAfter domain.ValidTime if createdAfterQuery != "" { parsed, err := time.Parse(time.RFC3339, createdAfterQuery) if err != nil { h.logger.Info("invalid created_after format", "error", err) return fiber.NewError(fiber.StatusBadRequest, "Invalid created_after format") } createdAfter = domain.ValidTime{Value: parsed, Valid: true} } filter := domain.UserFilter{ Role: c.Query("role"), Page: domain.ValidInt{ Value: c.QueryInt("page", 1) - 1, Valid: true, }, PageSize: domain.ValidInt{ Value: c.QueryInt("page_size", 10), Valid: true, }, Query: searchString, CreatedBefore: createdBefore, CreatedAfter: createdAfter, } if valErrs, ok := h.validator.Validate(c, filter); !ok { var errMsg string for f, msg := range valErrs { errMsg += fmt.Sprintf("%s: %s; ", f, msg) } h.mongoLoggerSvc.Info("invalid filter values in GetAllUsers request", zap.Int("status_code", fiber.StatusBadRequest), zap.Any("validation_errors", valErrs), zap.Time("timestamp", time.Now())) return fiber.NewError(fiber.StatusBadRequest, errMsg) } users, total, err := h.userSvc.GetAllUsers(c.Context(), filter) if err != nil { h.mongoLoggerSvc.Error("failed to get users", zap.Int("status_code", fiber.StatusInternalServerError), zap.Any("filter", filter), zap.Error(err), zap.Time("timestamp", time.Now())) return fiber.NewError(fiber.StatusInternalServerError, "Failed to get users: "+err.Error()) } // Map to profile response to avoid leaking sensitive fields result := make([]domain.UserProfileResponse, len(users)) for i, u := range users { result[i] = domain.UserProfileResponse{ ID: u.ID, FirstName: u.FirstName, LastName: u.LastName, UserName: u.UserName, Email: u.Email, PhoneNumber: u.PhoneNumber, Role: u.Role, Age: u.Age, EducationLevel: u.EducationLevel, Country: u.Country, Region: u.Region, NickName: u.NickName, Occupation: u.Occupation, LearningGoal: u.LearningGoal, LanguageGoal: u.LanguageGoal, LanguageChallange: u.LanguageChallange, FavoutiteTopic: u.FavoutiteTopic, EmailVerified: u.EmailVerified, PhoneVerified: u.PhoneVerified, LastLogin: u.LastLogin, ProfileCompleted: u.ProfileCompleted, ProfilePictureURL: u.ProfilePictureURL, PreferredLanguage: u.PreferredLanguage, CreatedAt: u.CreatedAt, UpdatedAt: u.UpdatedAt, } } return response.WriteJSON(c, fiber.StatusOK, "Users fetched successfully", map[string]interface{}{"users": result, "total": total}, nil) } // 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.Email, req.PhoneNumber, 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, // NickName: req.NickName, // Occupation: req.Occupation, // LearningGoal: req.LearningGoal, // LanguageGoal: req.LanguageGoal, // LanguageChallange: req.LanguageChallange, // FavoutiteTopic: req.FavoutiteTopic, } 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, }) } func MapRegisterReqToUser(req domain.RegisterUserReq) domain.User { return domain.User{ FirstName: req.FirstName, LastName: req.LastName, UserName: req.UserName, Email: req.Email, PhoneNumber: req.PhoneNumber, Password: []byte(req.Password), // or hashed password Role: domain.Role(req.Role), // Age: req.Age, // EducationLevel: req.EducationLevel, // Country: req.Country, // Region: req.Region, // PreferredLanguage: req.PreferredLanguage, // NickName: req.NickName, // Occupation: req.Occupation, // LearningGoal: req.LearningGoal, // LanguageGoal: req.LanguageGoal, // LanguageChallange: req.LanguageChallange, // FavoutiteTopic: req.FavoutiteTopic, } } 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"` }