package middleware import ( "context" "fmt" "path" "ruben/inventory2/internal/logging" "ruben/inventory2/internal/server/sse" "strings" "time" "github.com/gin-gonic/gin" ) type UpdateNotificationPublisher struct { log *logging.Logger sse *sse.Queue trimBasePath string basePathPattern string } func NewUpdateNotificationPublisher( log *logging.Logger, sse *sse.Queue, ) *UpdateNotificationPublisher { return &UpdateNotificationPublisher{ log: log, sse: sse, } } func (p *UpdateNotificationPublisher) Trim(pathPattern string) *UpdateNotificationPublisher { p2 := *p p2.trimBasePath = path.Join(p2.trimBasePath, pathPattern) return &p2 } func (p *UpdateNotificationPublisher) Group(pathPattern string) *UpdateNotificationPublisher { p2 := *p p2.basePathPattern = path.Join(p2.basePathPattern, pathPattern) 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 { trimBasePathSegs := getPathSegments(p.trimBasePath) trimmedBasePathPattern := path.Join(p.basePathPattern, pathPattern) trimmedBasePathPatternSegs := getPathSegments(trimmedBasePathPattern) fullBasePathPatternSegs := append(trimBasePathSegs, trimmedBasePathPatternSegs...) return func(c *gin.Context) { reqPathSegs := getPathSegments(c.Request.URL.Path) c.Next() if len(c.Errors) > 0 { return } if len(reqPathSegs) < len(fullBasePathPatternSegs) { p.log.Errorf("req path is less that the full path: %v, %v", c.Request.URL.Path, path.Join(p.trimBasePath, p.basePathPattern, pathPattern)) return } // if the path is a subpath, then emit events along the subpath. for i, s := range trimmedBasePathPatternSegs { if isWildcard := s[0] == ':'; isWildcard { continue } rs := reqPathSegs[i] if isSubpath := s != rs; isSubpath { continue } return } topEventParts := reqPathSegs[len(trimBasePathSegs):len(fullBasePathPatternSegs)] topEvent := strings.Join(topEventParts, "_") events := make([]string, len(reqPathSegs)-len(fullBasePathPatternSegs)+1) events[0] = topEvent parentEvent := topEvent for i, s := range reqPathSegs[len(fullBasePathPatternSegs):] { e := parentEvent + "_" + s events[i+1] = e parentEvent = e } acctID := GetIdentity(c).Account.AccountID for _, e := range events { go func() { ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() 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) } }() } } } func getPathSegments(p string) []string { p = path.Clean(p) if p == "" || p == "." || p == "/" { return nil } return strings.Split(strings.Trim(p, "/"), "/") }