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
This commit was merged in pull request #210.
This commit is contained in:
Arjun Patel
2026-05-18 12:44:31 -07:00
committed by GitHub
parent a564ea819b
commit d262f734f0
61 changed files with 1682 additions and 531 deletions
+10 -4
View File
@@ -16,11 +16,11 @@ type Reader interface {
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
// 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.
// 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)
@@ -34,8 +34,8 @@ type readerImpl struct {
repo repository
}
// newReader returns the concrete reader. Used by NewService to embed without
// going through the Reader interface (which would hide pool/repo).
// 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,
@@ -69,6 +69,12 @@ func (r *readerImpl) IsMember(ctx context.Context, networkID, humanId string) (b
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)
}