This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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 == "" {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user