package service // PlatformUserService handles business logic for Platform API user endpoints. // Reference: Chatwoot Platform::Api::V1::UsersController — AccessToken authenticated // // These endpoints are distinct from the SuperAdmin-platform routes because they use // AccessToken authentication (api_access_token header) instead of JWT+SuperAdmin. // The Permissible system governs which resources each PlatformApp can access. import ( "context" "errors" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/repository" ) // PlatformUserService provides user CRUD for Platform API (AccessToken auth). type PlatformUserService struct { userRepo *repository.UserRepo permissibleRepo *repository.PermissibleRepo } // NewPlatformUserService creates a new PlatformUserService. func NewPlatformUserService( userRepo *repository.UserRepo, permissibleRepo *repository.PermissibleRepo, ) *PlatformUserService { return &PlatformUserService{ userRepo: userRepo, permissibleRepo: permissibleRepo, } } // ValidatePermissible checks that the PlatformApp has permission to access the target user. func (s *PlatformUserService) ValidatePermissible(ctx context.Context, platformAppID uint, userID uint) error { perm, err := s.permissibleRepo.FindByPlatformAppAndResource(ctx, platformAppID, model.PermissibleTypeUser, userID) if err != nil { // GORM returns "record not found" when no matching row — treat as non-permissible return errors.New("non permissible resource") } if perm == nil { return errors.New("non permissible resource") } return nil } // GetUser retrieves a user by ID, after verifying permissible access. func (s *PlatformUserService) GetUser(ctx context.Context, platformAppID uint, userID uint) (*model.User, error) { if err := s.ValidatePermissible(ctx, platformAppID, userID); err != nil { return nil, err } return s.userRepo.FindByID(ctx, userID) } // CreateUser creates a new user and auto-creates a Permissible record. // Reference: Chatwoot UsersController#create — skips confirmation, auto-permissible func (s *PlatformUserService) CreateUser(ctx context.Context, platformAppID uint, name, email, password string) (*model.User, error) { user := &model.User{ Name: name, Email: email, Provider: "email", } if password != "" { user.Password = password } if err := s.userRepo.Create(ctx, user); err != nil { return nil, err } // Auto-create Permissible record (PlatformApp can access this user) perm := &model.Permissible{ PlatformAppID: platformAppID, PermissibleType: model.PermissibleTypeUser, PermissibleID: user.ID, } if err := s.permissibleRepo.Create(ctx, perm); err != nil { // Non-critical — permissible creation failure shouldn't block user creation } return user, nil } // UpdateUser updates a user, after verifying permissible access. // Reference: Chatwoot UsersController#update — merges custom_attributes, skips reconfirmation func (s *PlatformUserService) UpdateUser(ctx context.Context, platformAppID uint, userID uint, name, email string) (*model.User, error) { if err := s.ValidatePermissible(ctx, platformAppID, userID); err != nil { return nil, err } user, err := s.userRepo.FindByID(ctx, userID) if err != nil { return nil, err } if name != "" { user.Name = name } if email != "" { user.Email = email } if err := s.userRepo.Update(ctx, user); err != nil { return nil, err } return user, nil } // DeleteUser deletes a user, after verifying permissible access. // Reference: Chatwoot UsersController#destroy — uses DeleteObjectJob (async) func (s *PlatformUserService) DeleteUser(ctx context.Context, platformAppID uint, userID uint) error { if err := s.ValidatePermissible(ctx, platformAppID, userID); err != nil { return err } return s.userRepo.Delete(ctx, userID) } // ListPermissibleUsers returns all users that the PlatformApp has permissible access to. func (s *PlatformUserService) ListPermissibleUsers(ctx context.Context, platformAppID uint) ([]model.User, error) { permissibles, err := s.permissibleRepo.FindByPlatformAppID(ctx, platformAppID) if err != nil { return nil, err } var users []model.User for _, perm := range permissibles { if perm.PermissibleType == model.PermissibleTypeUser { user, err := s.userRepo.FindByID(ctx, perm.PermissibleID) if err != nil { continue // skip missing users } users = append(users, *user) } } return users, nil }