Files
gochat/internal/service/platform_user_service.go
T
2026-06-04 15:44:48 +08:00

136 lines
4.4 KiB
Go

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
}