diff --git a/api/handlers/addresses.go b/api/handlers/addresses.go index 3c6454c..b62edc9 100644 --- a/api/handlers/addresses.go +++ b/api/handlers/addresses.go @@ -35,7 +35,7 @@ func NewAddressHandler(shippoAPIKey, bringAPIUID, bringAPIKey string) *AddressHa // Retrieve all saved addresses for a user func (h *AddressHandler) GetAddresses(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) userID := middleware.UserIDFromContext(r.Context()) @@ -48,7 +48,7 @@ func (h *AddressHandler) GetAddresses(w http.ResponseWriter, r *http.Request) { } func (h *AddressHandler) CreateAddress(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) userID := middleware.UserIDFromContext(r.Context()) @@ -82,7 +82,7 @@ func (h *AddressHandler) CreateAddress(w http.ResponseWriter, r *http.Request) { return } client := shippo.NewClient(h.shippoKey) - validated, err := client.ValidateAddress(shippo.Address{ + validated, err := client.ValidateAddress(r.Context(), shippo.Address{ Name: req.Name, Street1: req.Street, Street2: req.Street2, @@ -114,7 +114,7 @@ func (h *AddressHandler) CreateAddress(w http.ResponseWriter, r *http.Request) { return } client := bring.NewClient(h.bringAPIUID, h.bringAPIKey) - validated, err := client.ValidateAddress(bring.Address{ + validated, err := client.ValidateAddress(r.Context(), bring.Address{ Name: req.Name, Street1: req.Street, Street2: req.Street2, @@ -161,7 +161,7 @@ func (h *AddressHandler) CreateAddress(w http.ResponseWriter, r *http.Request) { // DeleteAddress deltes a saved address func (h *AddressHandler) DeleteAddress(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) userID := middleware.UserIDFromContext(r.Context()) idStr := chi.URLParam(r, "id") @@ -191,7 +191,7 @@ func (h *AddressHandler) DeleteAddress(w http.ResponseWriter, r *http.Request) { // Mark an address as the users default address func (h *AddressHandler) SetDefaultAddress(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) userID := middleware.UserIDFromContext(r.Context()) idStr := chi.URLParam(r, "id") diff --git a/api/handlers/auth.go b/api/handlers/auth.go index 84b9d16..47137c9 100644 --- a/api/handlers/auth.go +++ b/api/handlers/auth.go @@ -46,7 +46,7 @@ type TokenResponse struct { } func (h *AuthHandler) GetToken(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) var req TokenRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { @@ -127,7 +127,7 @@ type RegisterRequest struct { // RegisterWithSSHKey registers a new user with their SSH public key // No username required - following terminal.shop pattern func (h *AuthHandler) RegisterWithSSHKey(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) var req RegisterRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { @@ -180,7 +180,7 @@ func (h *AuthHandler) RegisterWithSSHKey(w http.ResponseWriter, r *http.Request) // GetUserBySSHKey retrieves a user by their SSH key fingerprint func (h *AuthHandler) GetUserBySSHKey(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) fingerprint := r.URL.Query().Get("fingerprint") if fingerprint == "" { diff --git a/api/handlers/cards.go b/api/handlers/cards.go index 5665af2..db18d84 100644 --- a/api/handlers/cards.go +++ b/api/handlers/cards.go @@ -66,7 +66,7 @@ func NewCardHandler(stripeSecretKey string, appURL string) *CardHandler { // GetCards returns all saved cards for the authenticated user. func (h *CardHandler) GetCards(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) userID := middleware.UserIDFromContext(r.Context()) var cards []models.Card @@ -81,7 +81,7 @@ func (h *CardHandler) GetCards(w http.ResponseWriter, r *http.Request) { // GetCard returns a single saved card by ID. func (h *CardHandler) GetCard(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) userID := middleware.UserIDFromContext(r.Context()) idStr := chi.URLParam(r, "id") @@ -103,7 +103,7 @@ func (h *CardHandler) GetCard(w http.ResponseWriter, r *http.Request) { } func (h *CardHandler) SetDefaultCard(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) userID := middleware.UserIDFromContext(r.Context()) idStr := chi.URLParam(r, "id") @@ -136,7 +136,7 @@ func (h *CardHandler) SetDefaultCard(w http.ResponseWriter, r *http.Request) { } func (h *CardHandler) CollectCard(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) userID := middleware.UserIDFromContext(r.Context()) var user models.User @@ -184,7 +184,7 @@ type SaveCardRequest struct { // fingerprint, attaches to the user's Stripe customer, and saves the // card metadata locally. No raw card data is stored at any point. func (h *CardHandler) SaveCard(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) userID := middleware.UserIDFromContext(r.Context()) var req SaveCardRequest @@ -280,7 +280,7 @@ func (h *CardHandler) SaveCard(w http.ResponseWriter, r *http.Request) { // DeleteCard removes a saved card. If the card is currently set on the user's // cart, the cart's card selection is cleared. func (h *CardHandler) DeleteCard(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) userID := middleware.UserIDFromContext(r.Context()) idStr := chi.URLParam(r, "id") @@ -345,7 +345,7 @@ func ensureStripeCustomer(gdb *gorm.DB, user *models.User) error { } user.StripeCustomerID = cust.ID if err := gdb.Save(user).Error; err != nil { - return fmt.Errorf("Failed to save stripe customer: %w", err) + return fmt.Errorf("failed to save stripe customer: %w", err) } return nil } diff --git a/api/handlers/cart.go b/api/handlers/cart.go index 2bf9f38..a433ffe 100644 --- a/api/handlers/cart.go +++ b/api/handlers/cart.go @@ -1,6 +1,7 @@ package handlers import ( + "context" "encoding/json" "fmt" "net/http" @@ -30,8 +31,8 @@ func NewCartHandler(stripeSecretKey string) *CartHandler { } // getOrCreateCart finds the users cart or creates one. -func getOrCreateCart(userID uint) (*models.Cart, error) { - db := database.GetDB() +func getOrCreateCart(ctx context.Context, userID uint) (*models.Cart, error) { + db := database.GetDB().WithContext(ctx) var cart models.Cart err := db.Where("user_id = ?", userID).First(&cart).Error if err != nil { @@ -43,8 +44,8 @@ func getOrCreateCart(userID uint) (*models.Cart, error) { return &cart, nil } -func cartResponse(cart *models.Cart) map[string]interface{} { - db := database.GetDB() +func cartResponse(ctx context.Context, cart *models.Cart) map[string]interface{} { + db := database.GetDB().WithContext(ctx) var items []models.CartItem db.Where("cart_id = ? AND quantity > 0", cart.ID).Preload("Coffee").Find(&items) @@ -76,14 +77,14 @@ func cartResponse(cart *models.Cart) map[string]interface{} { func (h *CartHandler) GetCart(w http.ResponseWriter, r *http.Request) { userID := middleware.UserIDFromContext(r.Context()) - cart, err := getOrCreateCart(userID) + cart, err := getOrCreateCart(r.Context(), userID) if err != nil { utils.RespondError(w, http.StatusInternalServerError, "CART_ERROR", "failed to get cart", nil) return } utils.RespondSuccess(w, http.StatusOK, map[string]interface{}{ - "cart": cartResponse(cart), + "cart": cartResponse(r.Context(), cart), }) } @@ -93,7 +94,7 @@ type SetItemRequest struct { } func (h *CartHandler) SetItem(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) userID := middleware.UserIDFromContext(r.Context()) var req SetItemRequest @@ -113,7 +114,7 @@ func (h *CartHandler) SetItem(w http.ResponseWriter, r *http.Request) { return } - cart, err := getOrCreateCart(userID) + cart, err := getOrCreateCart(r.Context(), userID) if err != nil { utils.RespondError(w, http.StatusInternalServerError, "CART_ERROR", "failed to get cart", nil) return @@ -146,7 +147,7 @@ func (h *CartHandler) SetItem(w http.ResponseWriter, r *http.Request) { } utils.RespondSuccess(w, http.StatusOK, map[string]interface{}{ - "cart": cartResponse(cart), + "cart": cartResponse(r.Context(), cart), }) } @@ -155,7 +156,7 @@ type SetAddressRequest struct { } func (h *CartHandler) SetAddress(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) userID := middleware.UserIDFromContext(r.Context()) var req SetAddressRequest @@ -170,7 +171,7 @@ func (h *CartHandler) SetAddress(w http.ResponseWriter, r *http.Request) { return } - cart, err := getOrCreateCart(userID) + cart, err := getOrCreateCart(r.Context(), userID) if err != nil { utils.RespondError(w, http.StatusInternalServerError, "CART_ERROR", "failed to get cart", nil) return @@ -191,7 +192,7 @@ type SetCardRequest struct { } func (h *CartHandler) SetCard(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) userID := middleware.UserIDFromContext(r.Context()) var req SetCardRequest @@ -206,7 +207,7 @@ func (h *CartHandler) SetCard(w http.ResponseWriter, r *http.Request) { return } - cart, err := getOrCreateCart(userID) + cart, err := getOrCreateCart(r.Context(), userID) if err != nil { utils.RespondError(w, http.StatusInternalServerError, "CART_ERROR", "failed to get cart", nil) return @@ -223,10 +224,10 @@ func (h *CartHandler) SetCard(w http.ResponseWriter, r *http.Request) { } func (h *CartHandler) ClearCart(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) userID := middleware.UserIDFromContext(r.Context()) - cart, err := getOrCreateCart(userID) + cart, err := getOrCreateCart(r.Context(), userID) if err != nil { utils.RespondError(w, http.StatusInternalServerError, "CART_ERROR", "failed to get cart", nil) return @@ -243,14 +244,14 @@ func (h *CartHandler) ClearCart(w http.ResponseWriter, r *http.Request) { } func (h *CartHandler) ConvertCart(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) userID := middleware.UserIDFromContext(r.Context()) ctx, span := otel.Tracer("api").Start(r.Context(), "cart.convert") defer span.End() r = r.WithContext(ctx) - cart, err := getOrCreateCart(userID) + cart, err := getOrCreateCart(r.Context(), userID) if err != nil { middleware.RecordCartConversion("cart_lookup_failed") utils.RespondError(w, http.StatusInternalServerError, "CART_ERROR", "failed to get cart", nil) diff --git a/api/handlers/orders.go b/api/handlers/orders.go index 718c861..21089f7 100644 --- a/api/handlers/orders.go +++ b/api/handlers/orders.go @@ -24,7 +24,7 @@ func NewOrderHandler(stripeSecretKey string) *OrderHandler { } func (h *OrderHandler) GetOrders(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) userID := middleware.UserIDFromContext(r.Context()) var orders []models.Order @@ -38,7 +38,7 @@ func (h *OrderHandler) GetOrders(w http.ResponseWriter, r *http.Request) { // RefundOrder issues a full refund for an order via Stripe and marks it as // refunded. Only orders in paid or shipped status can be refunded. func (h *OrderHandler) RefundOrder(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) userID := middleware.UserIDFromContext(r.Context()) idStr := chi.URLParam(r, "id") diff --git a/api/handlers/products.go b/api/handlers/products.go index 1a6d95e..13e642e 100644 --- a/api/handlers/products.go +++ b/api/handlers/products.go @@ -20,7 +20,7 @@ func NewProductHandler() *ProductHandler { // GetProducts returns all products from the database func (h *ProductHandler) GetProducts(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) var products []models.Coffee @@ -37,7 +37,7 @@ func (h *ProductHandler) GetProducts(w http.ResponseWriter, r *http.Request) { // GetProduct returns a single product by ID func (h *ProductHandler) GetProduct(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) // Get ID from URL parameter idStr := chi.URLParam(r, "id") diff --git a/api/handlers/view.go b/api/handlers/view.go index 9727379..12b6c9b 100644 --- a/api/handlers/view.go +++ b/api/handlers/view.go @@ -16,7 +16,7 @@ func NewViewHandler() *ViewHandler { } func (h *ViewHandler) GetViewInit(w http.ResponseWriter, r *http.Request) { - db := database.GetDB() + db := database.GetDB().WithContext(r.Context()) userID := middleware.UserIDFromContext(r.Context()) var user models.User diff --git a/api/handlers/webhook.go b/api/handlers/webhook.go index f7323a2..5de8a66 100644 --- a/api/handlers/webhook.go +++ b/api/handlers/webhook.go @@ -1,6 +1,7 @@ package handlers import ( + "context" "encoding/json" "fmt" "io" @@ -69,18 +70,18 @@ func (h *WebhookHandler) HandleStripe(w http.ResponseWriter, r *http.Request) { switch event.Type { case "checkout.session.completed": - h.handleCheckoutSessionCompleted(event) + h.handleCheckoutSessionCompleted(r.Context(), event) // payment_intent.succeeded fires when a charge completes asynchronously // (e.g. 3DS, bank redirects). For confirm=true intents this is normally // already handled by ConvertCart, but the webhook is the authoritative source. case "payment_intent.succeeded": - h.handlePaymentIntentSucceeded(event) + h.handlePaymentIntentSucceeded(r.Context(), event) // payment_intent.payment_failed fires when a payment fails after initial // confirmation (e.g. bank decline on a deferred 3DS flow). case "payment_intent.payment_failed": - h.handlePaymentIntentFailed(event) + h.handlePaymentIntentFailed(r.Context(), event) // Sync local card state when Stripe's side changes. case "payment_method.attached", @@ -94,7 +95,7 @@ func (h *WebhookHandler) HandleStripe(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } -func (h *WebhookHandler) handlePaymentIntentSucceeded(event stripe.Event) { +func (h *WebhookHandler) handlePaymentIntentSucceeded(ctx context.Context, event stripe.Event) { var pi stripe.PaymentIntent if err := json.Unmarshal(event.Data.Raw, &pi); err != nil { webhookLog().Error("failed to unmarshal payment_intent", "error", err, "event", "payment_intent.succeeded") @@ -107,7 +108,7 @@ func (h *WebhookHandler) handlePaymentIntentSucceeded(event stripe.Event) { return } - db := database.GetDB() + db := database.GetDB().WithContext(ctx) var order models.Order if err := db.Where("id = ?", orderIDStr).First(&order).Error; err != nil { webhookLog().Warn("order not found for payment intent", "order_id", orderIDStr, "pi", pi.ID) @@ -149,7 +150,7 @@ func (h *WebhookHandler) handlePaymentIntentSucceeded(event stripe.Event) { webhookLog().Info("order marked paid", "order_id", orderIDStr, "pi", pi.ID) } -func (h *WebhookHandler) handlePaymentIntentFailed(event stripe.Event) { +func (h *WebhookHandler) handlePaymentIntentFailed(ctx context.Context, event stripe.Event) { var pi stripe.PaymentIntent if err := json.Unmarshal(event.Data.Raw, &pi); err != nil { webhookLog().Error("failed to unmarshal payment_intent", "error", err, "event", "payment_intent.payment_failed") @@ -161,7 +162,7 @@ func (h *WebhookHandler) handlePaymentIntentFailed(event stripe.Event) { return } - db := database.GetDB() + db := database.GetDB().WithContext(ctx) if err := db.Model(&models.Order{}).Where("id = ? AND status = ?", orderIDStr, models.OrderStatusPending). Update("status", models.OrderStatusFailed).Error; err != nil { webhookLog().Error("failed to mark order failed", "order_id", orderIDStr, "error", err) @@ -174,7 +175,7 @@ func (h *WebhookHandler) handlePaymentIntentFailed(event stripe.Event) { webhookLog().Warn("payment failed", "order_id", orderIDStr, "pi", pi.ID, "reason", errMsg) } -func (h *WebhookHandler) handleCheckoutSessionCompleted(event stripe.Event) { +func (h *WebhookHandler) handleCheckoutSessionCompleted(ctx context.Context, event stripe.Event) { var sess stripe.CheckoutSession if err := json.Unmarshal(event.Data.Raw, &sess); err != nil { webhookLog().Error("failed to unmarshal checkout session", "error", err) @@ -209,7 +210,7 @@ func (h *WebhookHandler) handleCheckoutSessionCompleted(event stripe.Event) { return } - db := database.GetDB() + db := database.GetDB().WithContext(ctx) var user models.User if err := db.Where("stripe_customer_id = ?", sess.Customer.ID).First(&user).Error; err != nil { webhookLog().Warn("user not found for customer", "customer", sess.Customer.ID, "error", err) diff --git a/api/main.go b/api/main.go index 7d772d9..361fac8 100644 --- a/api/main.go +++ b/api/main.go @@ -17,6 +17,7 @@ import ( "terminalShop/pkg/config" "terminalShop/pkg/database" "terminalShop/pkg/observability" + "terminalShop/pkg/stripeclient" ) const version = "v0.1.0" @@ -61,6 +62,8 @@ func main() { defer shutdown(context.Background()) } + stripeclient.InitOTel() + // Init JWT manager (same secret + duration as SSH server) jwtManager := auth.NewJWTManager(cfg.JWTSecret, 30*time.Minute) diff --git a/go.mod b/go.mod index c3fcc61..6ea3dde 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/prometheus/client_golang v1.23.2 github.com/riandyrn/otelchi v0.12.2 github.com/stripe/stripe-go/v78 v78.12.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 go.opentelemetry.io/otel v1.43.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 go.opentelemetry.io/otel/sdk v1.43.0 @@ -26,10 +27,14 @@ require ( gorm.io/driver/postgres v1.6.0 gorm.io/driver/sqlite v1.6.0 gorm.io/gorm v1.31.1 + gorm.io/plugin/opentelemetry v0.1.16 rsc.io/qr v0.2.0 ) require ( + github.com/ClickHouse/ch-go v0.61.5 // indirect + github.com/ClickHouse/clickhouse-go/v2 v2.30.0 // indirect + github.com/andybalholm/brotli v1.1.1 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect @@ -56,17 +61,22 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-faster/city v1.0.1 // indirect + github.com/go-faster/errors v0.7.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.6.0 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -78,10 +88,15 @@ require ( github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.16.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/paulmach/orb v0.11.1 // indirect + github.com/pierrec/lz4/v4 v4.1.21 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.66.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/segmentio/asm v1.2.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/zeebo/xxh3 v1.0.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect @@ -98,4 +113,7 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/grpc v1.80.0 // indirect google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/clickhouse v0.7.0 // indirect + gorm.io/driver/mysql v1.5.7 // indirect ) diff --git a/go.sum b/go.sum index c037d52..2a61dc4 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,11 @@ +github.com/ClickHouse/ch-go v0.61.5 h1:zwR8QbYI0tsMiEcze/uIMK+Tz1D3XZXLdNrlaOpeEI4= +github.com/ClickHouse/ch-go v0.61.5/go.mod h1:s1LJW/F/LcFs5HJnuogFMta50kKDO0lf9zzfrbl0RQg= +github.com/ClickHouse/clickhouse-go/v2 v2.30.0 h1:AG4D/hW39qa58+JHQIFOSnxyL46H6h2lrmGGk17dhFo= +github.com/ClickHouse/clickhouse-go/v2 v2.30.0/go.mod h1:i9ZQAojcayW3RsdCb3YR+n+wC2h65eJsZCscZ1Z1wyo= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= +github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= @@ -81,6 +87,10 @@ github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE= github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/httprate v0.15.0 h1:j54xcWV9KGmPf/X4H32/aTH+wBlrvxL7P+SdnRqxh5g= github.com/go-chi/httprate v0.15.0/go.mod h1:rzGHhVrsBn3IMLYDOZQsSU4fJNWcjui4fWKJcCId1R4= +github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= +github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= +github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= +github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -88,16 +98,25 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -112,12 +131,18 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= @@ -134,6 +159,7 @@ github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -142,6 +168,13 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU= +github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= +github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= @@ -158,21 +191,38 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stripe/stripe-go/v78 v78.12.0 h1:YzKjO5Cx1dTfSkqBXzg6GFG7LnRHkZiU0+k0vSF5yt4= github.com/stripe/stripe-go/v78 v78.12.0/go.mod h1:GjncxVLUc1xoIOidFqVwq+y3pYiG7JLVWiVQxTsLrvQ= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= +go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= @@ -193,17 +243,36 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= @@ -211,10 +280,20 @@ golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= @@ -223,19 +302,29 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/clickhouse v0.7.0 h1:BCrqvgONayvZRgtuA6hdya+eAW5P2QVagV3OlEp1vtA= +gorm.io/driver/clickhouse v0.7.0/go.mod h1:TmNo0wcVTsD4BBObiRnCahUgHJHjBIwuRejHwYt3JRs= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= +gorm.io/plugin/opentelemetry v0.1.16 h1:Kypj2YYAliJqkIczDZDde6P6sFMhKSlG5IpngMFQGpc= +gorm.io/plugin/opentelemetry v0.1.16/go.mod h1:P3RmTeZXT+9n0F1ccUqR5uuTvEXDxF8k2UpO7mTIB2Y= rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs= diff --git a/pkg/bring/bring.go b/pkg/bring/bring.go index c41d431..34b15f9 100644 --- a/pkg/bring/bring.go +++ b/pkg/bring/bring.go @@ -1,6 +1,7 @@ package bring import ( + "context" "encoding/json" "fmt" "io" @@ -9,6 +10,8 @@ import ( "regexp" "strings" "time" + + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) const baseURL = "https://api.bring.com/address" @@ -27,6 +30,7 @@ func NewClient(apiUID, apiKey string) *Client { apiKey: apiKey, httpClient: &http.Client{ Timeout: 10 * time.Second, + Transport: otelhttp.NewTransport(http.DefaultTransport), }, } } @@ -79,7 +83,7 @@ func parseStreet(street string) (name string, number string) { return strings.TrimSpace(street), "" } -func (c *Client) ValidateAddress(addr Address) (*Address, error) { +func (c *Client) ValidateAddress(ctx context.Context, addr Address) (*Address, error) { streetName, streetNumber := parseStreet(addr.Street1) params := url.Values{} @@ -93,7 +97,7 @@ func (c *Client) ValidateAddress(addr Address) (*Address, error) { reqURL := fmt.Sprintf("%s/api/no/validation?%s", baseURL, params.Encode()) - req, err := http.NewRequest("GET", reqURL, nil) + req, err := http.NewRequestWithContext(ctx, "GET", reqURL, nil) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } diff --git a/pkg/database/db.go b/pkg/database/db.go index 84ee492..c134e59 100644 --- a/pkg/database/db.go +++ b/pkg/database/db.go @@ -11,6 +11,8 @@ import ( "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" + + "gorm.io/plugin/opentelemetry/tracing" ) var ( @@ -42,6 +44,14 @@ func Connect(dsn string) (*gorm.DB, error) { return } + if pluginErr := db.Use(tracing.NewPlugin( + tracing.WithoutMetrics(), + tracing.WithoutQueryVariables(), + )); pluginErr != nil { + err = fmt.Errorf("failed to register otel plugin: %w", pluginErr) + return + } + // Configure connection pool for PostgreSQL. if isPostgres { sqlDB, poolErr := db.DB() diff --git a/pkg/shippo/shippo.go b/pkg/shippo/shippo.go index d1d8819..3acccc6 100644 --- a/pkg/shippo/shippo.go +++ b/pkg/shippo/shippo.go @@ -2,11 +2,14 @@ package shippo import ( "bytes" + "context" "encoding/json" "fmt" "io" "net/http" "time" + + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) const baseURL = "https://api.goshippo.com" @@ -22,7 +25,8 @@ func NewClient(apiKey string) *Client { return &Client{ apiKey: apiKey, httpClient: &http.Client{ - Timeout: 10 * time.Second, + Timeout: 10 * time.Second, + Transport: otelhttp.NewTransport(http.DefaultTransport), }, } } @@ -75,7 +79,7 @@ type addressResponse struct { // ValidateAddress sends an address to Shippo for validation. // Returns the normalized/corrected address if valid, or an error if invalid. -func (c *Client) ValidateAddress(addr Address) (*Address, error) { +func (c *Client) ValidateAddress(ctx context.Context, addr Address) (*Address, error) { reqBody := addressRequest{ Name: addr.Name, Street1: addr.Street1, @@ -93,7 +97,7 @@ func (c *Client) ValidateAddress(addr Address) (*Address, error) { return nil, fmt.Errorf("failed to marshal address: %w", err) } - req, err := http.NewRequest("POST", baseURL+"/v1/addresses", bytes.NewReader(body)) + req, err := http.NewRequestWithContext(ctx, "POST", baseURL+"/v1/addresses", bytes.NewReader(body)) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } diff --git a/pkg/stripeclient/init.go b/pkg/stripeclient/init.go new file mode 100644 index 0000000..b25fb04 --- /dev/null +++ b/pkg/stripeclient/init.go @@ -0,0 +1,22 @@ +package stripeclient + +import ( + "net/http" + "time" + + "github.com/stripe/stripe-go/v78" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" +) + +// InitOTel wires the Stripe SDK to use an HTTP client whose transport emits +// OpenTelemetry client spans. Call once at startup, after tracing is initialized. +func InitOTel() { + httpClient := &http.Client{ + Timeout: 80 * time.Second, + Transport: otelhttp.NewTransport(http.DefaultTransport), + } + backends := stripe.NewBackends(httpClient) + stripe.SetBackend(stripe.APIBackend, backends.API) + stripe.SetBackend(stripe.UploadsBackend, backends.Uploads) + stripe.SetBackend(stripe.ConnectBackend, backends.Connect) +} diff --git a/pkg/tui/model.go b/pkg/tui/model.go index 42dc861..e01adfc 100644 --- a/pkg/tui/model.go +++ b/pkg/tui/model.go @@ -108,7 +108,7 @@ type Model struct { pendingWidth int // buffered width from latest resize pendingHeight int // buffered height from latest resize - Loading bool // true when fetching data from API + Loading bool // true when fetching data from API error *VisibleError APIClient *api.Client @@ -587,6 +587,38 @@ func (m Model) deleteCardCmd(id uint) tea.Cmd { } } +// resolveCartAddress sets the shipping address on the cart, creating it first +// if the address had no ID yet (i.e the user typed it in fresh rather than picking +// from saved addresses) +func (m Model) resolveCartAddress() error { + if m.ShippingInfo == nil { + return nil + } + if m.ShippingInfo.ID != 0 { + if err := m.APIClient.SetCartAddress(m.ShippingInfo.ID); err != nil { + return fmt.Errorf("failed to set address: %w", err) + } + return nil + } + saved, err := m.APIClient.CreateAddress(api.CreateAddressRequest{ + Name: m.ShippingInfo.Name, + Street: m.ShippingInfo.Street, + Street2: m.ShippingInfo.Street2, + City: m.ShippingInfo.City, + State: m.ShippingInfo.State, + Zip: m.ShippingInfo.Zip, + Country: m.ShippingInfo.Country, + Phone: m.ShippingInfo.Phone, + }) + if err != nil { + return fmt.Errorf("failed to save address: %w", err) + } + if err := m.APIClient.SetCartAddress(saved.ID); err != nil { + return fmt.Errorf("failed to set address: %w", err) + } + return nil +} + // checkoutWithSavedCard sets address and saved card on the cart, then converts. // Used when the user selects an existing card from the list (no tokenization needed). func (m Model) checkoutWithSavedCard() tea.Cmd { @@ -595,28 +627,8 @@ func (m Model) checkoutWithSavedCard() tea.Cmd { return CheckoutResultMsg{Err: fmt.Errorf("API client or card not available")} } - // 1. Set the shipping address on the cart - if m.ShippingInfo != nil && m.ShippingInfo.ID != 0 { - if err := m.APIClient.SetCartAddress(m.ShippingInfo.ID); err != nil { - return CheckoutResultMsg{Err: fmt.Errorf("failed to set address: %w", err)} - } - } else if m.ShippingInfo != nil { - saved, err := m.APIClient.CreateAddress(api.CreateAddressRequest{ - Name: m.ShippingInfo.Name, - Street: m.ShippingInfo.Street, - Street2: m.ShippingInfo.Street2, - City: m.ShippingInfo.City, - State: m.ShippingInfo.State, - Zip: m.ShippingInfo.Zip, - Country: m.ShippingInfo.Country, - Phone: m.ShippingInfo.Phone, - }) - if err != nil { - return CheckoutResultMsg{Err: fmt.Errorf("failed to save address: %w", err)} - } - if err := m.APIClient.SetCartAddress(saved.ID); err != nil { - return CheckoutResultMsg{Err: fmt.Errorf("failed to set address: %w", err)} - } + if err := m.resolveCartAddress(); err != nil { + return CheckoutResultMsg{Err: err} } // 2. Set the saved card on the cart @@ -682,29 +694,8 @@ func (m Model) saveCardAndConvert(form PaymentFormCompleteMsg) tea.Cmd { return CheckoutResultMsg{Err: fmt.Errorf("failed to save card: %w", err)} } - // 2. Set the shipping address on the cart - if m.ShippingInfo != nil && m.ShippingInfo.ID != 0 { - if err := m.APIClient.SetCartAddress(m.ShippingInfo.ID); err != nil { - return CheckoutResultMsg{Err: fmt.Errorf("failed to set address: %w", err)} - } - } else if m.ShippingInfo != nil { - // Address was entered fresh (not from saved list), save it first - saved, err := m.APIClient.CreateAddress(api.CreateAddressRequest{ - Name: m.ShippingInfo.Name, - Street: m.ShippingInfo.Street, - Street2: m.ShippingInfo.Street2, - City: m.ShippingInfo.City, - State: m.ShippingInfo.State, - Zip: m.ShippingInfo.Zip, - Country: m.ShippingInfo.Country, - Phone: m.ShippingInfo.Phone, - }) - if err != nil { - return CheckoutResultMsg{Err: fmt.Errorf("failed to save address: %w", err)} - } - if err := m.APIClient.SetCartAddress(saved.ID); err != nil { - return CheckoutResultMsg{Err: fmt.Errorf("failed to set address: %w", err)} - } + if err := m.resolveCartAddress(); err != nil { + return CheckoutResultMsg{Err: err} } // 3. Set the card on the cart