diff --git a/backend/internal/api/readingroom.go b/backend/internal/api/readingroom.go
index 0db4950..bd3c71f 100644
--- a/backend/internal/api/readingroom.go
+++ b/backend/internal/api/readingroom.go
@@ -14,6 +14,7 @@ import (
"margin.at/internal/cloudflare"
"margin.at/internal/config"
"margin.at/internal/db"
+ "margin.at/internal/logger"
"margin.at/internal/xrpc"
)
@@ -808,7 +809,12 @@ func (h *Handler) GetBillingStatus(w http.ResponseWriter, r *http.Request) {
return
}
- sub, _ := h.db.GetSubscription(r.Context(), session.DID)
+ sub, err := h.db.GetSubscription(r.Context(), session.DID)
+ if err != nil {
+ logger.Error("GetBillingStatus: failed to read subscription for %s: %v", session.DID, err)
+ WriteJSONError(w, http.StatusServiceUnavailable, "billing temporarily unavailable")
+ return
+ }
resp := BillingStatusResponse{}
if sub != nil {
resp.Status = sub.Status
@@ -817,6 +823,7 @@ func (h *Handler) GetBillingStatus(w http.ResponseWriter, r *http.Request) {
resp.HasSubscription = sub.Status == "active" || sub.Status == "trialing"
}
+ w.Header().Set("Cache-Control", "no-store")
WriteSuccess(w, resp)
}
diff --git a/backend/internal/db/db.go b/backend/internal/db/db.go
index e5d3c0c..3b31f52 100644
--- a/backend/internal/db/db.go
+++ b/backend/internal/db/db.go
@@ -9,6 +9,7 @@ import (
"strings"
"time"
+ "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/jackc/pgx/v5/stdlib"
"margin.at/internal/db/sqlcdb"
@@ -62,6 +63,8 @@ func New(dsn string) (*DB, error) {
cfg.MaxConnIdleTime = 15 * time.Minute
cfg.HealthCheckPeriod = 30 * time.Second
+ cfg.ConnConfig.DefaultQueryExecMode = pgx.QueryExecModeCacheDescribe
+
pool, err := pgxpool.NewWithConfig(context.Background(), cfg)
if err != nil {
return nil, fmt.Errorf("open database: %w", err)
@@ -108,37 +111,47 @@ func (db *DB) Close() error {
}
func (db *DB) AdvisoryLock(ctx context.Context, key int64, fn func() error) (bool, error) {
- conn, err := db.pool.Acquire(ctx)
+ tx, err := db.pool.Begin(ctx)
if err != nil {
- return false, fmt.Errorf("advisory lock: acquire conn: %w", err)
+ return false, fmt.Errorf("advisory lock: begin tx: %w", err)
}
- defer conn.Release()
+ defer tx.Rollback(ctx) //nolint:errcheck
- if _, err := conn.Exec(ctx, "SELECT pg_advisory_lock($1)", key); err != nil {
+ if _, err := tx.Exec(ctx, "SELECT pg_advisory_xact_lock($1)", key); err != nil {
return false, fmt.Errorf("advisory lock: acquire: %w", err)
}
- defer conn.Exec(context.Background(), "SELECT pg_advisory_unlock($1)", key) //nolint:errcheck
- return true, fn()
+ if err := fn(); err != nil {
+ return true, err
+ }
+ if err := tx.Commit(ctx); err != nil {
+ return true, fmt.Errorf("advisory lock: commit: %w", err)
+ }
+ return true, nil
}
func (db *DB) TryAdvisoryLock(ctx context.Context, key int64, fn func() error) (bool, error) {
- conn, err := db.pool.Acquire(ctx)
+ tx, err := db.pool.Begin(ctx)
if err != nil {
- return false, fmt.Errorf("try advisory lock: acquire conn: %w", err)
+ return false, fmt.Errorf("try advisory lock: begin tx: %w", err)
}
- defer conn.Release()
+ defer tx.Rollback(ctx) //nolint:errcheck
var acquired bool
- if err := conn.QueryRow(ctx, "SELECT pg_try_advisory_lock($1)", key).Scan(&acquired); err != nil {
+ if err := tx.QueryRow(ctx, "SELECT pg_try_advisory_xact_lock($1)", key).Scan(&acquired); err != nil {
return false, fmt.Errorf("try advisory lock: %w", err)
}
if !acquired {
return false, nil
}
- defer conn.Exec(context.Background(), "SELECT pg_advisory_unlock($1)", key) //nolint:errcheck
- return true, fn()
+ if err := fn(); err != nil {
+ return true, err
+ }
+ if err := tx.Commit(ctx); err != nil {
+ return true, fmt.Errorf("try advisory lock: commit: %w", err)
+ }
+ return true, nil
}
func ParseSelector(selectorJSON *string) (*Selector, error) {
diff --git a/backend/internal/db/queries_oauth.go b/backend/internal/db/queries_oauth.go
index 81a6e70..1dff502 100644
--- a/backend/internal/db/queries_oauth.go
+++ b/backend/internal/db/queries_oauth.go
@@ -251,14 +251,18 @@ func hashLockKey(name string) int64 {
func (db *DB) WithAdvisoryLock(ctx context.Context, name string, fn func(context.Context) error) error {
key := hashLockKey(name)
- conn, err := db.pool.Acquire(ctx)
+ tx, err := db.pool.Begin(ctx)
if err != nil {
- return fmt.Errorf("advisory lock: acquire conn: %w", err)
+ return fmt.Errorf("advisory lock: begin tx: %w", err)
}
- defer conn.Release()
- if _, err := conn.Exec(ctx, "SELECT pg_advisory_lock($1)", key); err != nil {
+ defer tx.Rollback(ctx) //nolint:errcheck
+
+ if _, err := tx.Exec(ctx, "SELECT pg_advisory_xact_lock($1)", key); err != nil {
return fmt.Errorf("advisory lock: %w", err)
}
- defer conn.Exec(context.Background(), "SELECT pg_advisory_unlock($1)", key) //nolint:errcheck
- return fn(ctx)
+
+ if err := fn(ctx); err != nil {
+ return err
+ }
+ return tx.Commit(ctx)
}
diff --git a/web/src/api/client.ts b/web/src/api/client.ts
index ad5d6c4..55acc53 100644
--- a/web/src/api/client.ts
+++ b/web/src/api/client.ts
@@ -1713,12 +1713,24 @@ export async function createPortal(): Promise