package network import ( "context" "errors" "fmt" "github.com/flowy-live/llink/internal/utils" "github.com/jackc/pgx/v5/pgxpool" ) type Reader interface { // GetByID returns ErrNotFound if network doesn't exist. GetByID(ctx context.Context, id string) (*Network, error) // ListForHuman returns ErrInvalidHumanId if humanId is empty. ListForHuman(ctx context.Context, humanId string) ([]*Network, error) // IsMember returns ErrInvalidHumanId if humanId is empty. IsMember(ctx context.Context, networkID, humanId string) (bool, error) // ListAll returns all networks with their members ListAll(ctx context.Context) ([]*Network, error) // ListAllMemberships returns humanId -> networkIds for every human with at // least one membership. Humans with zero memberships are absent from the map. // Used by the membership reconciler to diff the Firestore mirror. ListAllMemberships(ctx context.Context) (map[string][]string, error) CountSeats(ctx context.Context, networkID string) (int, error) // ListInvitationsForEmail returns ErrInvalidEmail if normalization fails. ListInvitationsForEmail(ctx context.Context, email string) ([]*Invitation, error) ListInvitationsForNetwork(ctx context.Context, networkID string) ([]*Invitation, error) } type readerImpl struct { pool *pgxpool.Pool repo repository } // newReader returns the concrete reader. Used by NewService to embed without // going through the Reader interface (which would hide pool/repo). func newReader(pool *pgxpool.Pool) *readerImpl { return &readerImpl{ pool: pool, repo: newRepository(pool), } } func NewReader(pool *pgxpool.Pool) Reader { return newReader(pool) } func (r *readerImpl) GetByID(ctx context.Context, id string) (*Network, error) { n, err := r.repo.getByID(ctx, id) if errors.Is(err, errNotFound) { return nil, ErrNotFound } return n, err } func (r *readerImpl) ListForHuman(ctx context.Context, humanId string) ([]*Network, error) { if humanId == "" { return nil, ErrInvalidHumanId } return r.repo.getNetworksForHuman(ctx, humanId) } func (r *readerImpl) IsMember(ctx context.Context, networkID, humanId string) (bool, error) { if humanId == "" { return false, ErrInvalidHumanId } return r.repo.isMember(ctx, networkID, humanId) } func (r *readerImpl) ListAll(ctx context.Context) ([]*Network, error) { return r.repo.listAll(ctx) } func (r *readerImpl) ListAllMemberships(ctx context.Context) (map[string][]string, error) { return r.repo.listAllMemberships(ctx) } func (r *readerImpl) CountSeats(ctx context.Context, networkID string) (int, error) { return r.repo.countSeats(ctx, r.pool, networkID) } func (r *readerImpl) ListInvitationsForEmail(ctx context.Context, email string) ([]*Invitation, error) { normalized, err := utils.NormalizeEmail(email) if err != nil { return nil, fmt.Errorf("%w: %w", ErrInvalidEmail, err) } return r.repo.getInvitationsByEmail(ctx, normalized) } func (r *readerImpl) ListInvitationsForNetwork(ctx context.Context, networkID string) ([]*Invitation, error) { return r.repo.getInvitationsByNetwork(ctx, networkID) }