sse support

This commit is contained in:
2026-01-20 20:48:19 -07:00
parent 88eb917381
commit 1130366bf5
15 changed files with 886 additions and 29 deletions
+8 -9
View File
@@ -23,22 +23,21 @@ func Routes(
r *gin.RouterGroup,
logger *logging.Logger,
accts *accounts.Store,
authMiddleware *middleware.Auth,
pub *middleware.UpdateNotificationPublisher,
) {
as := &accountSubrouter{
log: logger,
accts: accts,
}
withAuth := func(fn response.HandlerFunc) gin.HandlerFunc {
return response.Handler(authMiddleware.AuthenticateAndAddIdentity(fn))
}
syncGroups := r.Group("/:acctID/inventory/sync-groups")
syncGroups.POST("", response.Handler(as.saveNewSyncGroup))
r.POST("/:acctID/inventory/sync-groups/draft/listings", withAuth(as.createSyncGroupListingDraft))
r.PUT("/:acctID/inventory/sync-groups/draft/listings/:orderIndex/shop", withAuth(as.setShopInSyncGroupListingDraft))
r.PUT("/:acctID/inventory/sync-groups/draft/listings/:orderIndex/listing", withAuth(as.setListingInSyncGroupListingDraft))
r.DELETE("/:acctID/inventory/sync-groups/draft/listings/:orderIndex", withAuth(as.deleteSyncGroupListingDraft))
r.POST("/:acctID/inventory/sync-groups", withAuth(as.saveNewSyncGroup))
draftListings := syncGroups.Group("/draft/listings", pub.Publish("/:acctID/inventory/sync-groups/draft/listings"))
draftListings.POST("", response.Handler(as.createSyncGroupListingDraft))
draftListings.PUT("/:orderIndex/shop", response.Handler(as.setShopInSyncGroupListingDraft))
draftListings.PUT("/:orderIndex/listing", response.Handler(as.setListingInSyncGroupListingDraft))
draftListings.DELETE("/:orderIndex", response.Handler(as.deleteSyncGroupListingDraft))
}
// POST /api/accounts
+97
View File
@@ -0,0 +1,97 @@
package sse
import (
"context"
"ruben/inventory2/internal/logging"
"ruben/inventory2/internal/server/middleware"
"ruben/inventory2/internal/server/response"
"ruben/inventory2/internal/server/sse"
"sync"
"github.com/gin-gonic/gin"
)
type (
sseRouter struct {
log *logging.Logger
sse *sse.Queue
users map[string]context.CancelFunc
lock sync.Mutex
}
)
func Routes(
r gin.IRouter,
logger *logging.Logger,
sq *sse.Queue,
auth *middleware.Auth,
) {
s := &sseRouter{
log: logger,
sse: sq,
users: make(map[string]context.CancelFunc),
}
r.GET("/", auth.AuthenticateAndAddIdentityGin(), response.Handler(s.serveEvents))
}
func (r *sseRouter) serveEvents(c *gin.Context) (response.Response, error) {
acct := middleware.GetIdentity(c).Account
acctID := acct.AccountID
userID := acct.UserID
email := acct.Email
log := r.log.WithGroup("serveEvents").With(
"accountID", acctID,
"userID", userID,
"email", email,
)
log.Info("user connected to sse queue")
ctx := r.closeExistingConnectionsForUserAndStoreCancelFuncForUser(c, userID)
w := c.Writer
hdr := w.Header()
hdr.Set("Access-Control-Allow-Origin", "*")
hdr.Set("Access-Control-Expose-Headers", "Content-Type")
hdr.Set("Content-Type", "text/event-stream")
hdr.Set("Connection", "keep-alive")
hdr.Set("Cache-Control", "no-cache")
w.Flush()
err := r.sse.Listen(ctx, func(ctx context.Context, e *sse.Event) error {
log.Debugf("sending event of type %s", e.Type)
e.Write(w)
return nil
})
if err != nil {
log.Errorf("no longer connected to sse queue due to error: %v", err)
return nil, err
}
// send a 'close' message so the front end doesn't try to reconnect
(&sse.Event{Type: "close"}).Write(w)
log.Info("user disconnecting sse queue")
return response.Status(200), nil
}
func (r *sseRouter) closeExistingConnectionsForUserAndStoreCancelFuncForUser(ctx context.Context, userID string) context.Context {
r.lock.Lock()
defer r.lock.Unlock()
// close existing connection
if closeConn, ok := r.users[userID]; ok {
closeConn()
delete(r.users, userID)
}
// store reference to cancel func
ctx, r.users[userID] = context.WithCancel(ctx)
return ctx
}