Files
Arjun Patel d262f734f0 Mobile notifications for iOS (#210)
* mobile: wire notification registration and listener

* implement backend components for push notifications

* refactor: agentic comment cleanup

* docs: use proper module name for particle processor

* set required env variables for push notifications

* bump version

* fix: always upsert push token on mobile start

* Revert "fix: always upsert push token on mobile start"

This reverts commit 90ff18a788.

* send push notifications regardless of online status
2026-05-18 12:44:31 -07:00

101 lines
3.3 KiB
Go

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)
// ListMembers returns an empty slice if the network doesn't exist.
ListMembers(ctx context.Context, networkID string) ([]string, error)
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.
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 exposes the concrete type so the service can embed it without
// hiding pool/repo behind the Reader interface.
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) ListMembers(ctx context.Context, networkID string) ([]string, error) {
// Admin is guaranteed to be in network_members: Create() calls AddMembers
// for the admin, and RemoveMemberFromNetwork rejects admin removal.
return r.repo.getMemberHumanIds(ctx, networkID)
}
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)
}