func (r *fakeRouter) CreateRoute(route *router.Route) error { r.mtx.Lock() defer r.mtx.Unlock() route.ID = route.Type + "/" + postgres.FormatUUID(random.UUID()) now := time.Now() route.CreatedAt = now route.UpdatedAt = now r.routes[route.ID] = route return nil }
func streamJobs(ctx context.Context, req *http.Request, w http.ResponseWriter, app *ct.App, repo *JobRepo) (err error) { var lastID int64 if req.Header.Get("Last-Event-Id") != "" { lastID, err = strconv.ParseInt(req.Header.Get("Last-Event-Id"), 10, 64) if err != nil { return ct.ValidationError{Field: "Last-Event-Id", Message: "is invalid"} } } var count int if req.FormValue("count") != "" { count, err = strconv.Atoi(req.FormValue("count")) if err != nil { return ct.ValidationError{Field: "count", Message: "is invalid"} } } ch := make(chan *ct.JobEvent) l, _ := ctxhelper.LoggerFromContext(ctx) s := sse.NewStream(w, ch, l) s.Serve() connected := make(chan struct{}) done := make(chan struct{}) listenEvent := func(ev pq.ListenerEventType, listenErr error) { switch ev { case pq.ListenerEventConnected: close(connected) case pq.ListenerEventDisconnected: if done != nil { close(done) done = nil } case pq.ListenerEventConnectionAttemptFailed: err = listenErr if done != nil { close(done) done = nil } } } listener := pq.NewListener(repo.db.DSN(), 10*time.Second, time.Minute, listenEvent) defer listener.Close() listener.Listen("job_events:" + postgres.FormatUUID(app.ID)) var currID int64 if lastID > 0 || count > 0 { events, err := repo.listEvents(app.ID, lastID, count) if err != nil { return err } // events are in ID DESC order, so iterate in reverse for i := len(events) - 1; i >= 0; i-- { e := events[i] ch <- e currID = e.ID } } select { case <-done: return case <-connected: } for { select { case <-s.Done: return case <-done: return case n := <-listener.Notify: id, err := strconv.ParseInt(n.Extra, 10, 64) if err != nil { return err } if id <= currID { continue } e, err := repo.getEvent(id) if err != nil { return err } ch <- e } } }