From 0490de4bdcb69a1de47e6d0af3dbc7b19f5a4828 Mon Sep 17 00:00:00 2001 From: Jordan Olshevski Date: Thu, 14 Dec 2023 23:42:58 -0600 Subject: [PATCH] Try to poll for webhook changes --- keycloak/keycloak.go | 20 ++++++++++++++++++++ main.go | 6 +++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/keycloak/keycloak.go b/keycloak/keycloak.go index dfc3abb..06bd2b6 100644 --- a/keycloak/keycloak.go +++ b/keycloak/keycloak.go @@ -113,6 +113,26 @@ func (k *Keycloak) GetUser(ctx context.Context, userID string) (*User, error) { return k.buildUser(ctx, kcuser) } +// GetUserAtETag returns the user object at the given etag, or a possibly different version on timeout. +// +// This is useful when avoiding backtracking for cases in which a change has been written to Stripe but +// the corresponding webhook may not have been handled. +func (k *Keycloak) GetUserAtETag(ctx context.Context, userID string, etag int64) (user *User, err error) { + for i := 0; i < 15; i++ { + user, err = k.GetUser(ctx, userID) + if err != nil { + return nil, err + } + if etag == 0 || user.StripeETag >= etag { + return user, nil + } + + time.Sleep(time.Millisecond * 100) // backoff + jitter would be nice here + } + log.Printf("timeout while waiting for Stripe webhook") + return user, nil // timeout +} + func (k *Keycloak) GetUserByEmail(ctx context.Context, email string) (*User, error) { token, err := k.GetToken(ctx) if err != nil { diff --git a/main.go b/main.go index 44f428b..728fbb5 100644 --- a/main.go +++ b/main.go @@ -137,14 +137,14 @@ func newRegistrationFormHandler(kc *keycloak.Keycloak) http.HandlerFunc { func newProfileViewHandler(kc *keycloak.Keycloak, pc *stripeutil.PriceCache) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - user, err := kc.GetUser(r.Context(), getUserID(r)) + etagString := r.URL.Query().Get("i") + etag, _ := strconv.ParseInt(etagString, 10, 0) + user, err := kc.GetUserAtETag(r.Context(), getUserID(r), etag) if err != nil { renderSystemError(w, "error while fetching user: %s", err) return } - etagString := r.URL.Query().Get("i") - etag, _ := strconv.ParseInt(etagString, 10, 0) viewData := map[string]any{ "page": "profile", "user": user,