diff --git a/internal/customer/repository.go b/internal/customer/repository.go index afea8d7..1dd3dd8 100644 --- a/internal/customer/repository.go +++ b/internal/customer/repository.go @@ -178,7 +178,16 @@ func (r *customerRepository) GetByUsername(ctx context.Context, username string) // GetByIDs retrieves customers by IDs func (r *customerRepository) GetByIDs(ctx context.Context, ids []string) ([]Customer, error) { - customers, err := r.ormRepo.FindAll(ctx, bson.M{"_id": bson.M{"$in": ids}}, orm.NewPaginationBuilder().Limit(len(ids)).Build()) + objectIDs := make([]bson.ObjectID, 0) + for _, id := range ids { + objectID, err := bson.ObjectIDFromHex(id) + if err != nil { + return nil, err + } + objectIDs = append(objectIDs, objectID) + } + + customers, err := r.ormRepo.FindAll(ctx, bson.M{"_id": bson.M{"$in": objectIDs}}, orm.NewPaginationBuilder().Limit(len(ids)).Build()) if err != nil { if errors.Is(err, orm.ErrDocumentNotFound) { return nil, errors.New("customers not found") @@ -211,7 +220,16 @@ func (r *customerRepository) GetByRole(ctx context.Context, role CustomerRole) ( // GetByCompanies retrieves customers by companies func (r *customerRepository) GetByCompanies(ctx context.Context, companies []string) ([]Customer, error) { - customers, err := r.ormRepo.FindAll(ctx, bson.M{"companies": bson.M{"$in": companies}}, orm.NewPaginationBuilder().Build()) + companyIDs := make([]bson.ObjectID, 0) + for _, company := range companies { + id, err := bson.ObjectIDFromHex(company) + if err != nil { + return nil, err + } + companyIDs = append(companyIDs, id) + } + + customers, err := r.ormRepo.FindAll(ctx, bson.M{"companies": bson.M{"$in": companyIDs}}, orm.NewPaginationBuilder().Build()) if err != nil { return nil, err } diff --git a/internal/customer/service.go b/internal/customer/service.go index 78b53d9..7c68b7a 100644 --- a/internal/customer/service.go +++ b/internal/customer/service.go @@ -152,7 +152,7 @@ func (s *customerService) Register(ctx context.Context, form *CreateCustomerForm "company_ids": Companies, }) - s.notification.SendEmail(ctx, notification.Model{ + go s.notification.SendEmail(context.Background(), notification.Model{ Recipient: customer.Email, Title: "Welcome to Opplens", Message: fmt.Sprintf("Welcome to Opplens\nUsername: %s\nPassword: %s", strings.ToLower(customer.Username), form.Password), @@ -322,6 +322,7 @@ func (s *customerService) Update(ctx context.Context, id string, form *UpdateCus return nil, errors.New("invalid username format") } + changeUsername := false // Check if username already exists (if changing username) if form.Username != "" && strings.ToLower(form.Username) != customer.Username { existingCustomer, _ := s.repository.GetByUsername(ctx, strings.ToLower(form.Username)) @@ -329,6 +330,7 @@ func (s *customerService) Update(ctx context.Context, id string, form *UpdateCus return nil, errors.New("customer with this username already exists") } customer.Username = strings.ToLower(form.Username) + changeUsername = true } // Handle company assignments @@ -366,6 +368,16 @@ func (s *customerService) Update(ctx context.Context, id string, form *UpdateCus return nil, errors.New("failed to update customer") } + if changeUsername { + go s.notification.SendEmail(context.Background(), notification.Model{ + Recipient: customer.Email, + Title: "Username Updated", + Message: fmt.Sprintf("Your username has been updated to %s", customer.Username), + Type: "info", + Priority: "important", + UserID: customer.ID.Hex(), + }) + } s.logger.Info("Customer updated successfully", map[string]interface{}{ "customer_id": customer.ID, "email": customer.Email, @@ -428,7 +440,7 @@ func (s *customerService) UpdateStatus(ctx context.Context, id string, form *Upd "status": form.Status, }) - s.notification.SendEmail(ctx, notification.Model{ + go s.notification.SendEmail(context.Background(), notification.Model{ Recipient: customer.Email, Title: "Account Status Updated", Message: fmt.Sprintf("Your account has been %s", strings.ToUpper(form.Status)), @@ -779,18 +791,37 @@ func (s *customerService) GetProfileWithCompanies(ctx context.Context, customerI // Logout handles customer logout func (s *customerService) Logout(ctx context.Context, customerID string, accessToken string) error { s.logger.Info("Customer logout", map[string]interface{}{ - "customer_id": customerID, - "access_token": accessToken[:10] + "...", // Log only first 10 chars for security + "customer_id": customerID, }) + // get customer by id + customer, err := s.repository.GetByID(ctx, customerID) + if err != nil { + s.logger.Error("Failed to get customer for logout", map[string]interface{}{ + "error": err.Error(), + "customer_id": customerID, + }) + } + // Invalidate the access token - err := s.authService.InvalidateToken(accessToken) + err = s.authService.InvalidateToken(accessToken) if err != nil { s.logger.Error("Failed to invalidate access token", map[string]interface{}{ "error": err.Error(), "customer_id": customerID, }) // Don't return error here as the customer should still be logged out + return errors.New("failed to invalidate access token") + } + + // delete device token from customer + customer.DeviceToken = make([]string, 0) + err = s.repository.Update(ctx, customer) + if err != nil { + s.logger.Error("Failed to update customer device token", map[string]interface{}{ + "error": err.Error(), + "customer_id": customerID, + }) } s.logger.Info("Customer logout successful", map[string]interface{}{ @@ -869,7 +900,7 @@ func (s *customerService) RequestResetPassword(ctx context.Context, form *Reques } // Send OTP via email - s.notification.SendEmail(ctx, notification.Model{ + go s.notification.SendEmail(context.Background(), notification.Model{ Recipient: form.Email, Title: "Password Reset", Message: fmt.Sprintf("Your password reset code is: %s", otpCode), @@ -1064,7 +1095,7 @@ func (s *customerService) ResetPassword(ctx context.Context, form *ResetPassword "customer_id": customer.ID, }) - s.notification.SendEmail(ctx, notification.Model{ + go s.notification.SendEmail(context.Background(), notification.Model{ Recipient: tokenData.Email, Title: "Password Reset Success", Message: "Your password has been reset successfully", diff --git a/internal/notification/handler.go b/internal/notification/handler.go index ba173a2..ede2ad4 100644 --- a/internal/notification/handler.go +++ b/internal/notification/handler.go @@ -1,6 +1,8 @@ package notification import ( + "context" + "github.com/asaskevich/govalidator" "github.com/labstack/echo/v4" @@ -48,7 +50,7 @@ func (h *NotificationHandler) Send(c echo.Context) error { } // Send notification - h.service.Send(c.Request().Context(), &req) + go h.service.Send(context.Background(), &req) return response.Created(c, nil, "Notification sent successfully") } diff --git a/internal/notification/service.go b/internal/notification/service.go index 9cad95f..b45f8c1 100644 --- a/internal/notification/service.go +++ b/internal/notification/service.go @@ -397,6 +397,10 @@ func (s *notificationService) getCustomers(ctx context.Context, values []string, recipients := make([]notificationRecipient, 0) if target == string(TargetAudienceSpecificRole) { + if len(values) == 0 { + return nil, errors.New("no role provided") + } + customers, err := s.customerService.GetCustomersByRole(ctx, values[0]) if err != nil { return nil, err diff --git a/internal/user/repository.go b/internal/user/repository.go index 0aa960e..88417db 100644 --- a/internal/user/repository.go +++ b/internal/user/repository.go @@ -218,7 +218,16 @@ func (r *userRepository) Delete(ctx context.Context, id string) error { // GetByIDs retrieves users by IDs func (r *userRepository) GetByIDs(ctx context.Context, userIDs []string) ([]User, error) { - users, err := r.ormRepo.FindAll(ctx, bson.M{"_id": bson.M{"$in": userIDs}}, orm.NewPaginationBuilder().Limit(len(userIDs)).Build()) + objectIDs := make([]bson.ObjectID, 0) + for _, id := range userIDs { + objectID, err := bson.ObjectIDFromHex(id) + if err != nil { + return nil, err + } + objectIDs = append(objectIDs, objectID) + } + + users, err := r.ormRepo.FindAll(ctx, bson.M{"_id": bson.M{"$in": objectIDs}}, orm.NewPaginationBuilder().Limit(len(userIDs)).Build()) if err != nil { return nil, err } diff --git a/internal/user/service.go b/internal/user/service.go index d1b6a82..20d6e20 100644 --- a/internal/user/service.go +++ b/internal/user/service.go @@ -118,7 +118,7 @@ func (s *userService) Register(ctx context.Context, form *CreateUserForm) (*User "role": user.Role, }) - s.notification.SendEmail(ctx, notification.Model{ + go s.notification.SendEmail(context.Background(), notification.Model{ Recipient: form.Email, Title: "Welcome to Opplens", Message: fmt.Sprintf("Welcome to Opplens\nUsername: %s\nPassword: %s", strings.ToLower(form.Username), form.Password), @@ -229,7 +229,7 @@ func (s *userService) UpdateStatus(ctx context.Context, id string, form *UpdateU "status": form.Status, }) - s.notification.SendEmail(ctx, notification.Model{ + go s.notification.SendEmail(context.Background(), notification.Model{ Recipient: user.Email, Title: "Account Status Updated", Message: fmt.Sprintf("Your account has been %s", strings.ToUpper(form.Status)), @@ -495,7 +495,7 @@ func (s *userService) ChangePassword(ctx context.Context, userID string, form *C "user_id": userID, }) - s.notification.SendEmail(ctx, notification.Model{ + go s.notification.SendEmail(context.Background(), notification.Model{ Recipient: user.Email, Title: "Password Reset Success", Message: "Your password has been reset successfully", @@ -632,14 +632,33 @@ func (s *userService) UpdateRole(ctx context.Context, id string, form *UpdateUse // Logout logs out a user and invalidates tokens func (s *userService) Logout(ctx context.Context, userID string, accessToken string) error { + // get user by id + user, err := s.repository.GetByID(ctx, userID) + if err != nil { + s.logger.Error("Failed to get user for logout", map[string]interface{}{ + "error": err.Error(), + "user_id": userID, + }) + } + // Invalidate the access token - err := s.authService.InvalidateToken(accessToken) + err = s.authService.InvalidateToken(accessToken) if err != nil { s.logger.Error("Failed to invalidate access token", map[string]interface{}{ "error": err.Error(), "user_id": userID, }) - // Don't return error here as the user should still be logged out + return errors.New("failed to invalidate access token") + } + + // delete device token from user + user.DeviceToken = make([]string, 0) + err = s.repository.Update(ctx, user) + if err != nil { + s.logger.Error("Failed to update user device token", map[string]interface{}{ + "error": err.Error(), + "user_id": userID, + }) } s.logger.Info("User logged out successfully", map[string]interface{}{