refactor: agentic comment cleanup

This commit is contained in:
talksik
2026-05-18 10:52:37 -07:00
parent 6f476fe773
commit 4421b8e832
45 changed files with 269 additions and 545 deletions
+10 -12
View File
@@ -20,7 +20,7 @@ const (
ExpoErrorDeviceNotRegistered = "DeviceNotRegistered"
)
// Message is one push to one device. Sound defaults to "default" when empty.
// Sound defaults to "default" when empty (set in Send).
type Message struct {
To string `json:"to"`
Title string `json:"title,omitempty"`
@@ -29,9 +29,8 @@ type Message struct {
Sound string `json:"sound,omitempty"`
}
// Ticket is the synchronous response Expo returns per message. Status is
// either "ok" or "error". On error, Details["error"] carries the code (e.g.
// "DeviceNotRegistered", "MessageTooBig", "InvalidCredentials").
// Status is "ok" or "error". On error, Details["error"] carries the code
// (e.g. "DeviceNotRegistered", "MessageTooBig", "InvalidCredentials").
type Ticket struct {
Status string `json:"status"`
ID string `json:"id,omitempty"`
@@ -39,8 +38,8 @@ type Ticket struct {
Details map[string]any `json:"details,omitempty"`
}
// ExpoClient is a minimal HTTP client for the Expo Push API. It does NOT poll
// receipts and does NOT retry.
// ExpoClient does NOT poll receipts and does NOT retry — fire-and-forget,
// with DeviceNotRegistered handled out-of-band by the notifier.
type ExpoClient struct {
http *http.Client
accessToken string
@@ -54,14 +53,13 @@ func NewExpoClient(accessToken string) *ExpoClient {
}
type expoSendResponse struct {
Data []Ticket `json:"data"`
Errors []map[string]any `json:"errors,omitempty"`
Data []Ticket `json:"data"`
Errors []map[string]any `json:"errors,omitempty"`
}
// Send delivers messages in batches of up to expoMaxBatchSize. Returned tickets
// preserve the input order across batches: tickets[i] corresponds to msgs[i].
// A request-level error (network, 5xx, malformed body) aborts the remaining
// batches and is returned to the caller along with whatever tickets succeeded.
// Send batches msgs (cap expoMaxBatchSize) and preserves input order:
// tickets[i] corresponds to msgs[i]. A request-level failure aborts the
// remaining batches; tickets already collected are returned with the error.
func (c *ExpoClient) Send(ctx context.Context, msgs []Message) ([]Ticket, error) {
if len(msgs) == 0 {
return nil, nil
+15 -19
View File
@@ -11,14 +11,12 @@ import (
"google.golang.org/grpc"
)
// PusherClient is the subset of the pusher gRPC service the notifier needs.
// Defined here (rather than depending on the generated client interface) so
// tests can supply a fake without standing up a gRPC server.
// PusherClient is a narrow subset of the pusher gRPC service so tests can
// supply a fake without standing up a real server.
type PusherClient interface {
IsOnline(ctx context.Context, in *pbpusher.IsOnlineRequest, opts ...grpc.CallOption) (*pbpusher.IsOnlineResponse, error)
}
// NotifyInput is everything the notifier needs to know about a single newly-created particle.
type NotifyInput struct {
NetworkID string
SenderHumanID string
@@ -39,12 +37,11 @@ type NotifyInput struct {
Body string
}
// Notifier orchestrates the per-particle fanout:
// 1. Resolve recipients (stream visibility ∩ network members, minus sender).
// 2. Filter out anyone with an active WebSocket connection.
// 3. Look up each remaining human's push tokens.
// 4. POST a single batched request to Expo.
// 5. Delete any token Expo reports as DeviceNotRegistered.
// Notifier fans out one particle to Expo:
// 1. Resolve recipients (visibility ∩ network members, minus sender).
// 2. Drop anyone currently connected via WebSocket.
// 3. Send a batched Expo request for the remainder's tokens.
// 4. Prune tokens Expo reports as DeviceNotRegistered.
type Notifier struct {
networkR network.Reader
tokens Service
@@ -127,9 +124,8 @@ func (n *Notifier) queryOnline(ctx context.Context, humanIDs []string) (map[stri
return resp.Online, nil
}
// cleanupDeadTokens DELETEs any token Expo reports as DeviceNotRegistered.
// This is the one feedback signal we honor — other ticket errors (e.g.
// MessageTooBig, RateLimit) are logged but never retried.
// DeviceNotRegistered is the one feedback signal we honor; other ticket
// errors (MessageTooBig, RateLimit, …) are logged and dropped.
func (n *Notifier) cleanupDeadTokens(ctx context.Context, msgs []Message, tickets []Ticket) {
for i, t := range tickets {
if i >= len(msgs) {
@@ -160,12 +156,12 @@ func buildMessages(tokens []*PushToken, in NotifyInput) []Message {
}
data := map[string]any{
"kind": "particle_created",
"network_id": in.NetworkID,
"stream_id": in.StreamID,
"particle_id": in.ParticleID,
"sender_human_id": in.SenderHumanID,
"particle_kind": in.ParticleKind,
"kind": "particle_created",
"network_id": in.NetworkID,
"stream_id": in.StreamID,
"particle_id": in.ParticleID,
"sender_human_id": in.SenderHumanID,
"particle_kind": in.ParticleKind,
}
msgs := make([]Message, 0, len(tokens))
+8 -12
View File
@@ -6,22 +6,18 @@ import (
"github.com/jackc/pgx/v5/pgxpool"
)
// Service is the full surface for per-device push token storage. HTTP handlers
// use Register/Unregister; the worker's notifier uses ListForHumans and
// DeleteByToken. Both consumers share the same underlying repository.
// Service stores per-device Expo push tokens and exposes the operations
// needed by both the HTTP handlers and the worker-side notifier.
type Service interface {
// Register upserts a token for the given human. Returns ErrInvalidToken /
// ErrInvalidPlatform on bad input.
// Register returns ErrInvalidToken / ErrInvalidPlatform on bad input.
Register(ctx context.Context, humanID string, in RegisterInput) error
// Unregister removes a token, scoped to the calling human so a user can't
// delete another user's token. Returns ErrNotFound if the token doesn't
// belong to humanID (or doesn't exist).
// Unregister is scoped to humanID so a user can't delete another user's
// token. Returns ErrNotFound if the token isn't owned by humanID.
Unregister(ctx context.Context, humanID, token string) error
// ListForHumans returns every push token belonging to any of the given
// human IDs. Returns an empty slice when nothing matches.
// ListForHumans returns an empty slice when nothing matches.
ListForHumans(ctx context.Context, humanIDs []string) ([]*PushToken, error)
// DeleteByToken removes a token regardless of owning human. Used by the
// notifier to clean up after Expo returns DeviceNotRegistered.
// DeleteByToken removes a token regardless of owner — used to prune after
// Expo reports DeviceNotRegistered.
DeleteByToken(ctx context.Context, token string) error
}
+6 -6
View File
@@ -21,12 +21,12 @@ func (p Platform) Valid() bool {
}
type PushToken struct {
Token string
HumanID string
Platform Platform
AppVersion string
CreatedAt time.Time
LastSeenAt time.Time
Token string
HumanID string
Platform Platform
AppVersion string
CreatedAt time.Time
LastSeenAt time.Time
}
var (