From d87ed2cbca541d8f5241c8e515597792c97b8d05 Mon Sep 17 00:00:00 2001
From: scanash00
Date: Wed, 8 Jul 2026 22:53:35 -0800
Subject: [PATCH] fiix
---
backend/internal/api/readingroom.go | 9 ++++-
backend/internal/db/db.go | 37 +++++++++++++------
backend/internal/db/queries_oauth.go | 16 +++++---
web/src/api/client.ts | 26 +++++++++----
.../components/navigation/RightSidebar.tsx | 2 +-
web/src/views/Pro.tsx | 3 +-
web/src/views/core/ReadingRoomSettings.tsx | 2 +-
web/src/views/core/Settings.tsx | 10 ++++-
8 files changed, 75 insertions(+), 30 deletions(-)
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 {
}
export async function getBillingStatus(): Promise {
- try {
- const res = await apiRequest("/api/billing/status");
- if (!res.ok) return null;
- return await res.json();
- } catch (e) {
- console.error("Failed to fetch billing status:", e);
- return null;
+ const MAX_ATTEMPTS = 3;
+ for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
+ try {
+ const res = await apiRequest("/api/billing/status");
+ if (res.status >= 500 && attempt < MAX_ATTEMPTS - 1) {
+ await new Promise((r) => setTimeout(r, 300 * (attempt + 1)));
+ continue;
+ }
+ if (!res.ok) return null;
+ return await res.json();
+ } catch (e) {
+ if (attempt < MAX_ATTEMPTS - 1) {
+ await new Promise((r) => setTimeout(r, 300 * (attempt + 1)));
+ continue;
+ }
+ console.error("Failed to fetch billing status:", e);
+ return null;
+ }
}
+ return null;
}
diff --git a/web/src/components/navigation/RightSidebar.tsx b/web/src/components/navigation/RightSidebar.tsx
index ba8fa4f..2177920 100644
--- a/web/src/components/navigation/RightSidebar.tsx
+++ b/web/src/components/navigation/RightSidebar.tsx
@@ -152,7 +152,7 @@ export default function RightSidebar({ onNavigate }: RightSidebarProps) {
getBillingStatus().then((b) => setBilling(b));
}, [user]);
- const showProCard = !billing?.hasSubscription;
+ const showProCard = billing !== null && !billing.hasSubscription;
const extensionLink =
browser === "firefox"
diff --git a/web/src/views/Pro.tsx b/web/src/views/Pro.tsx
index 4df03a7..89a5950 100644
--- a/web/src/views/Pro.tsx
+++ b/web/src/views/Pro.tsx
@@ -41,6 +41,7 @@ export default function Pro() {
}, []);
const hasSub = billing?.hasSubscription ?? false;
+ const billingUnknown = !!user && billing === null;
const handleCheckout = async (plan: Plan) => {
if (!user) {
@@ -154,7 +155,7 @@ export default function Pro() {
- {!ready ? (
+ {!ready || billingUnknown ? (
) : hasSub ? (
<>
diff --git a/web/src/views/core/ReadingRoomSettings.tsx b/web/src/views/core/ReadingRoomSettings.tsx
index 84372ec..6e53a76 100644
--- a/web/src/views/core/ReadingRoomSettings.tsx
+++ b/web/src/views/core/ReadingRoomSettings.tsx
@@ -171,7 +171,7 @@ export default function ReadingRoomSettings() {
);
- if (loading) {
+ if (loading || billing === null) {
return (
{header}
diff --git a/web/src/views/core/Settings.tsx b/web/src/views/core/Settings.tsx
index 2b01418..94f187d 100644
--- a/web/src/views/core/Settings.tsx
+++ b/web/src/views/core/Settings.tsx
@@ -769,7 +769,15 @@ export default function Settings() {
{t("settings.sections.billing")}
- {billing?.hasSubscription ? (
+ {billing === null ? (
+
+ ) : billing.hasSubscription ? (