* refactor: update api and client to reference humanIds * fix: prevent deletion of network member This may cause various side effects if there is data in other services which reference this member
167 lines
4.9 KiB
Go
167 lines
4.9 KiB
Go
package network
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/flowy-live/llink/internal/utils"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
var ErrNotFound = errors.New("network not found")
|
|
var ErrInvalidName = errors.New("name cannot be empty")
|
|
var ErrCapacityExceeded = errors.New("active stream capacity exceeded")
|
|
|
|
type Service interface {
|
|
// Create creates a network and adds adminHumanId as the first member. Returns ErrInvalidName if name is empty.
|
|
Create(ctx context.Context, name, adminHumanId string) (*Network, error)
|
|
// GetByID returns ErrNotFound if network doesn't exist.
|
|
GetByID(ctx context.Context, id string) (*Network, error)
|
|
// SetName returns ErrNotFound or ErrInvalidName.
|
|
SetName(ctx context.Context, id, name string) error
|
|
AddMembers(ctx context.Context, networkID string, humanIds []string) error
|
|
RemoveMember(ctx context.Context, networkID, humanId string) error
|
|
ListForHuman(ctx context.Context, humanId string) ([]*Network, error)
|
|
IsMember(ctx context.Context, networkID, humanId string) (bool, error)
|
|
|
|
// Invitations (email-based, for users who haven't registered yet)
|
|
InviteByEmail(ctx context.Context, networkID string, emails []string) error
|
|
ListInvitationsForEmail(ctx context.Context, email string) ([]*Invitation, error)
|
|
ListInvitationsForNetwork(ctx context.Context, networkID string) ([]*Invitation, error)
|
|
AcceptInvitation(ctx context.Context, networkID, email, humanId string) error
|
|
RevokeInvitation(ctx context.Context, networkID, email string) error
|
|
}
|
|
|
|
type serviceImpl struct {
|
|
repo repository
|
|
}
|
|
|
|
func NewService(pool *pgxpool.Pool) Service {
|
|
return &serviceImpl{repo: newRepository(pool)}
|
|
}
|
|
|
|
func (s *serviceImpl) Create(ctx context.Context, name, adminHumanId string) (*Network, error) {
|
|
name = strings.TrimSpace(name)
|
|
if name == "" {
|
|
return nil, ErrInvalidName
|
|
}
|
|
|
|
network, err := s.repo.create(ctx, name, adminHumanId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = s.AddMembers(ctx, network.ID, []string{adminHumanId})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return network, nil
|
|
}
|
|
|
|
func (s *serviceImpl) GetByID(ctx context.Context, id string) (*Network, error) {
|
|
n, err := s.repo.getByID(ctx, id)
|
|
if errors.Is(err, errNotFound) {
|
|
return nil, ErrNotFound
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
func (s *serviceImpl) SetName(ctx context.Context, id, name string) error {
|
|
name = strings.TrimSpace(name)
|
|
if name == "" {
|
|
return ErrInvalidName
|
|
}
|
|
|
|
err := s.repo.updateName(ctx, id, name)
|
|
if errors.Is(err, errNotFound) {
|
|
return ErrNotFound
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (s *serviceImpl) AddMembers(ctx context.Context, networkID string, humanIds []string) error {
|
|
for _, humanId := range humanIds {
|
|
if humanId == "" {
|
|
return fmt.Errorf("invalid humanId")
|
|
}
|
|
if err := s.repo.addMember(ctx, networkID, humanId); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *serviceImpl) RemoveMember(ctx context.Context, networkID, humanId string) error {
|
|
if humanId == "" {
|
|
return fmt.Errorf("invalid humanId")
|
|
}
|
|
return s.repo.removeMember(ctx, networkID, humanId)
|
|
}
|
|
|
|
func (s *serviceImpl) ListForHuman(ctx context.Context, humanId string) ([]*Network, error) {
|
|
if humanId == "" {
|
|
return nil, fmt.Errorf("invalid humanId")
|
|
}
|
|
return s.repo.getNetworksForHuman(ctx, humanId)
|
|
}
|
|
|
|
func (s *serviceImpl) IsMember(ctx context.Context, networkID, humanId string) (bool, error) {
|
|
if humanId == "" {
|
|
return false, fmt.Errorf("invalid humanId")
|
|
}
|
|
return s.repo.isMember(ctx, networkID, humanId)
|
|
}
|
|
|
|
// Invitation methods
|
|
|
|
func (s *serviceImpl) InviteByEmail(ctx context.Context, networkID string, emails []string) error {
|
|
for _, email := range emails {
|
|
normalized, err := utils.NormalizeEmail(email)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid email %q: %w", email, err)
|
|
}
|
|
if err := s.repo.createInvitation(ctx, networkID, normalized); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *serviceImpl) ListInvitationsForEmail(ctx context.Context, email string) ([]*Invitation, error) {
|
|
normalized, err := utils.NormalizeEmail(email)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid email: %w", err)
|
|
}
|
|
return s.repo.getInvitationsByEmail(ctx, normalized)
|
|
}
|
|
|
|
func (s *serviceImpl) ListInvitationsForNetwork(ctx context.Context, networkID string) ([]*Invitation, error) {
|
|
return s.repo.getInvitationsByNetwork(ctx, networkID)
|
|
}
|
|
|
|
func (s *serviceImpl) AcceptInvitation(ctx context.Context, networkID, email, humanId string) error {
|
|
normalized, err := utils.NormalizeEmail(email)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid email: %w", err)
|
|
}
|
|
if humanId == "" {
|
|
return fmt.Errorf("invalid humanId")
|
|
}
|
|
|
|
if err := s.repo.deleteInvitation(ctx, networkID, normalized); err != nil {
|
|
return err
|
|
}
|
|
return s.repo.addMember(ctx, networkID, humanId)
|
|
}
|
|
|
|
func (s *serviceImpl) RevokeInvitation(ctx context.Context, networkID, email string) error {
|
|
normalized, err := utils.NormalizeEmail(email)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid email: %w", err)
|
|
}
|
|
return s.repo.deleteInvitation(ctx, networkID, normalized)
|
|
}
|