filter ss events by account

This commit is contained in:
2026-01-20 21:11:02 -07:00
parent 1130366bf5
commit 4f2ae26d58
3 changed files with 42 additions and 20 deletions
+1 -1
View File
@@ -61,7 +61,7 @@ func (r *sseRouter) serveEvents(c *gin.Context) (response.Response, error) {
hdr.Set("Cache-Control", "no-cache") hdr.Set("Cache-Control", "no-cache")
w.Flush() w.Flush()
err := r.sse.Listen(ctx, func(ctx context.Context, e *sse.Event) error { err := r.sse.Listen(ctx, acctID, func(ctx context.Context, e *sse.Event) error {
log.Debugf("sending event of type %s", e.Type) log.Debugf("sending event of type %s", e.Type)
e.Write(w) e.Write(w)
+8 -1
View File
@@ -2,6 +2,7 @@ package middleware
import ( import (
"context" "context"
"fmt"
"path" "path"
"ruben/inventory2/internal/logging" "ruben/inventory2/internal/logging"
"ruben/inventory2/internal/server/sse" "ruben/inventory2/internal/server/sse"
@@ -40,6 +41,7 @@ func (p *UpdateNotificationPublisher) Group(pathPattern string) *UpdateNotificat
return &p2 return &p2
} }
// Publish must be applied AFTER a middleware puts in the Identity into the gin.Context.
func (p *UpdateNotificationPublisher) Publish(pathPattern string) gin.HandlerFunc { func (p *UpdateNotificationPublisher) Publish(pathPattern string) gin.HandlerFunc {
trimBasePathSegs := getPathSegments(p.trimBasePath) trimBasePathSegs := getPathSegments(p.trimBasePath)
trimmedBasePathPattern := path.Join(p.basePathPattern, pathPattern) trimmedBasePathPattern := path.Join(p.basePathPattern, pathPattern)
@@ -85,12 +87,17 @@ func (p *UpdateNotificationPublisher) Publish(pathPattern string) gin.HandlerFun
parentEvent = e parentEvent = e
} }
acctID := GetIdentity(c).Account.AccountID
for _, e := range events { for _, e := range events {
go func() { go func() {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute) ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel() defer cancel()
if err := p.sse.Send(ctx, e, nil); err != nil { if err := p.sse.Send(ctx, sse.Event{
AccountID: acctID,
Type: e,
Data: []byte(fmt.Sprintf(`{"eventType": %q}`, e)),
}); err != nil {
p.log.Errorf("failed to send sse event to listener: %v", err) p.log.Errorf("failed to send sse event to listener: %v", err)
} }
}() }()
+33 -18
View File
@@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"context" "context"
"fmt" "fmt"
"maps"
"net/http" "net/http"
"sync" "sync"
) )
@@ -11,18 +12,18 @@ import (
type ( type (
Queue struct { Queue struct {
in chan Event in chan Event
out map[int]chan Event out map[int64]map[int]chan Event
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
lock sync.Mutex lock sync.Mutex
prevID int
} }
Event struct { Event struct {
Type string AccountID int64
Data []byte Type string
Data []byte
} }
) )
@@ -30,7 +31,7 @@ func NewQueue() *Queue {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
return &Queue{ return &Queue{
in: make(chan Event), in: make(chan Event),
out: make(map[int]chan Event), out: make(map[int64]map[int]chan Event),
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
@@ -46,9 +47,13 @@ func (q *Queue) Start(ctx context.Context) error {
// we're done piping events // we're done piping events
return nil return nil
case e := <-q.in: case e := <-q.in:
// share event will all subscribers if e.AccountID == 0 {
continue
}
// share event with listeners on the account
q.lock.Lock() q.lock.Lock()
for _, out := range q.out { for _, out := range q.out[e.AccountID] {
out <- e out <- e
} }
q.lock.Unlock() q.lock.Unlock()
@@ -56,21 +61,34 @@ func (q *Queue) Start(ctx context.Context) error {
} }
} }
func (q *Queue) Listen(ctx context.Context, fn func(context.Context, *Event) error) error { func (q *Queue) Listen(ctx context.Context, acctID int64, fn func(context.Context, *Event) error) error {
// create new out pipe and append it to the queue // create new out pipe and append it to the queue
out := make(chan Event, 1) out := make(chan Event, 1)
q.lock.Lock() q.lock.Lock()
q.prevID += 1 outs := q.out[acctID]
id := q.prevID if outs == nil {
q.out[id] = out outs = make(map[int]chan Event)
q.out[acctID] = outs
}
var maxID int
for n := range maps.Keys(outs) {
maxID = max(maxID, n)
}
id := maxID + 1
outs[id] = out
q.lock.Unlock() q.lock.Unlock()
// delete the pipe when done listening // delete the pipe when done listening
defer func() { defer func() {
q.lock.Lock() q.lock.Lock()
delete(q.out, id) delete(q.out[acctID], id)
if len(q.out[acctID]) == 0 {
delete(q.out, acctID)
}
q.lock.Unlock() q.lock.Unlock()
}() }()
@@ -91,7 +109,7 @@ func (q *Queue) Listen(ctx context.Context, fn func(context.Context, *Event) err
} }
} }
func (q *Queue) Send(ctx context.Context, eventType string, data []byte) error { func (q *Queue) Send(ctx context.Context, e Event) error {
select { select {
case <-q.ctx.Done(): case <-q.ctx.Done():
// the queue has closed // the queue has closed
@@ -100,10 +118,7 @@ func (q *Queue) Send(ctx context.Context, eventType string, data []byte) error {
// sender ran out of time // sender ran out of time
return fmt.Errorf("provided context canceled: %w", ctx.Err()) return fmt.Errorf("provided context canceled: %w", ctx.Err())
// push the event onto the queue // push the event onto the queue
case q.in <- Event{ case q.in <- e:
Type: eventType,
Data: data,
}:
} }
return nil return nil