wg-portal/internal/app/api/v1/backend/user_service.go
2025-01-08 22:41:20 +01:00

122 lines
3.2 KiB
Go

package backend
import (
"context"
"errors"
"fmt"
"math"
"sync"
"github.com/h44z/wg-portal/internal/app/users"
"github.com/h44z/wg-portal/internal/config"
"github.com/h44z/wg-portal/internal/domain"
)
type UserDatabaseRepo interface {
GetUser(ctx context.Context, id domain.UserIdentifier) (*domain.User, error)
GetAllUsers(ctx context.Context) ([]domain.User, error)
FindUsers(ctx context.Context, search string) ([]domain.User, error)
SaveUser(ctx context.Context, id domain.UserIdentifier, updateFunc func(u *domain.User) (*domain.User, error)) error
DeleteUser(ctx context.Context, id domain.UserIdentifier) error
}
type PeerDatabaseRepo interface {
GetUserPeers(ctx context.Context, id domain.UserIdentifier) ([]domain.Peer, error)
}
type UserService struct {
cfg *config.Config
users UserDatabaseRepo
peers PeerDatabaseRepo
}
func NewUserService(cfg *config.Config, users UserDatabaseRepo, peers PeerDatabaseRepo) *UserService {
return &UserService{
cfg: cfg,
users: users,
peers: peers,
}
}
func (s UserService) GetUsers(ctx context.Context) ([]domain.User, error) {
if err := domain.ValidateAdminAccessRights(ctx); err != nil {
return nil, err
}
users, err := s.users.GetAllUsers(ctx)
if err != nil {
return nil, fmt.Errorf("unable to load users: %w", err)
}
ch := make(chan *domain.User)
wg := sync.WaitGroup{}
workers := int(math.Min(float64(len(users)), 10))
wg.Add(workers)
for i := 0; i < workers; i++ {
go func() {
defer wg.Done()
for user := range ch {
peers, _ := s.peers.GetUserPeers(ctx, user.Identifier) // ignore error, list will be empty in error case
user.LinkedPeerCount = len(peers)
}
}()
}
for i := range users {
ch <- &users[i]
}
close(ch)
wg.Wait()
return users, nil
}
func (s UserService) GetUserById(ctx context.Context, id domain.UserIdentifier) (*domain.User, error) {
if err := domain.ValidateUserAccessRights(ctx, id); err != nil {
return nil, errors.Join(err, domain.ErrNoPermission)
}
user, err := s.users.GetUser(ctx, id)
if err != nil {
return nil, fmt.Errorf("unable to load user %s: %w", id, err)
}
peers, _ := s.peers.GetUserPeers(ctx, user.Identifier) // ignore error, list will be empty in error case
user.LinkedPeerCount = len(peers)
return user, nil
}
func (s UserService) CreateUser(ctx context.Context, user *domain.User) (*domain.User, error) {
if err := domain.ValidateAdminAccessRights(ctx); err != nil {
return nil, err
}
existingUser, err := s.users.GetUser(ctx, user.Identifier)
if err != nil && !errors.Is(err, domain.ErrNotFound) {
return nil, fmt.Errorf("unable to load existing user %s: %w", user.Identifier, err)
}
if existingUser != nil {
return nil, errors.Join(fmt.Errorf("user %s already exists", user.Identifier), domain.ErrDuplicateEntry)
}
if err := users.ValidateCreation(ctx, user); err != nil {
return nil, errors.Join(fmt.Errorf("creation not allowed: %w", err), domain.ErrInvalidData)
}
err = user.HashPassword()
if err != nil {
return nil, err
}
err = s.users.SaveUser(ctx, user.Identifier, func(u *domain.User) (*domain.User, error) {
user.CopyCalculatedAttributes(u)
return user, nil
})
if err != nil {
return nil, fmt.Errorf("creation failure: %w", err)
}
return user, nil
}