Skip to content
Merged
14 changes: 13 additions & 1 deletion db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,16 @@ CREATE TABLE IF NOT EXISTS discount_codes (
);

CREATE INDEX IF NOT EXISTS idx_discount_codes_pubkey ON discount_codes(pubkey);
CREATE INDEX IF NOT EXISTS idx_discount_codes_code ON discount_codes(code);
CREATE INDEX IF NOT EXISTS idx_discount_codes_code ON discount_codes(code);

-- Failed relay publish tracking table
CREATE TABLE IF NOT EXISTS failed_relay_publishes (
event_id TEXT PRIMARY KEY,
relays TEXT NOT NULL,
event_data TEXT,
created_at BIGINT NOT NULL,
retry_count INTEGER DEFAULT 0
);

CREATE INDEX IF NOT EXISTS idx_failed_relay_publishes_created_at ON failed_relay_publishes(created_at ASC);
CREATE INDEX IF NOT EXISTS idx_failed_relay_publishes_retry_count ON failed_relay_publishes(retry_count);
10 changes: 10 additions & 0 deletions pages/api/db/clear-failed-publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ export default async function handler(

client = await dbPool.connect();

await client.query(`
CREATE TABLE IF NOT EXISTS failed_relay_publishes (
event_id TEXT PRIMARY KEY,
relays TEXT NOT NULL,
event_data TEXT,
created_at BIGINT NOT NULL,
retry_count INTEGER DEFAULT 0
)
`);

if (incrementRetry) {
// Increment retry count
await client.query(
Expand Down
45 changes: 33 additions & 12 deletions pages/api/db/get-failed-publishes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,45 @@ export default async function handler(
try {
client = await dbPool.connect();

await client.query(`
CREATE TABLE IF NOT EXISTS failed_relay_publishes (
event_id TEXT PRIMARY KEY,
relays TEXT NOT NULL,
event_data TEXT,
created_at BIGINT NOT NULL,
retry_count INTEGER DEFAULT 0
)
`);

await client.query(`
ALTER TABLE failed_relay_publishes
ADD COLUMN IF NOT EXISTS event_data TEXT
`);

// Get all failed publishes with retry count < 5 (limit retries)
const result = await client.query(
`SELECT fp.event_id, fp.relays, fp.retry_count, e.event_data
FROM failed_relay_publishes fp
LEFT JOIN events e ON fp.event_id = e.id
WHERE fp.retry_count < 5
ORDER BY fp.created_at ASC
`SELECT event_id, relays, retry_count, event_data
FROM failed_relay_publishes
WHERE retry_count < 5
AND event_data IS NOT NULL
ORDER BY created_at ASC
LIMIT 50`
);

const failedPublishes = result.rows
.filter((row: any) => row.event_data)
.map((row: any) => ({
eventId: row.event_id,
relays: JSON.parse(row.relays),
event: JSON.parse(row.event_data),
retryCount: row.retry_count,
}));
.map((row: any) => {
try {
return {
eventId: row.event_id,
relays: JSON.parse(row.relays),
event: JSON.parse(row.event_data),
retryCount: row.retry_count,
};
} catch {
return null;
}
})
.filter(Boolean);

return res.status(200).json(failedPublishes);
} catch (error) {
Expand Down
20 changes: 16 additions & 4 deletions pages/api/db/track-failed-publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default async function handler(
let client;

try {
const { eventId, relays } = req.body;
const { eventId, relays, event } = req.body;

if (!eventId || !relays || !Array.isArray(relays)) {
return res.status(400).json({ error: "Invalid request body" });
Expand All @@ -26,19 +26,31 @@ export default async function handler(
CREATE TABLE IF NOT EXISTS failed_relay_publishes (
event_id TEXT PRIMARY KEY,
relays TEXT NOT NULL,
event_data TEXT,
created_at BIGINT NOT NULL,
retry_count INTEGER DEFAULT 0
)
`);

await client.query(`
ALTER TABLE failed_relay_publishes
ADD COLUMN IF NOT EXISTS event_data TEXT
`);

// Insert or update the failed publish record
await client.query(
`INSERT INTO failed_relay_publishes (event_id, relays, created_at, retry_count)
VALUES ($1, $2, $3, 0)
`INSERT INTO failed_relay_publishes (event_id, relays, event_data, created_at, retry_count)
VALUES ($1, $2, $3, $4, 0)
ON CONFLICT (event_id) DO UPDATE SET
relays = EXCLUDED.relays,
event_data = EXCLUDED.event_data,
created_at = EXCLUDED.created_at`,
[eventId, JSON.stringify(relays), Math.floor(Date.now() / 1000)]
[
eventId,
JSON.stringify(relays),
event ? JSON.stringify(event) : null,
Math.floor(Date.now() / 1000),
]
);

return res.status(200).json({ success: true });
Expand Down
5 changes: 3 additions & 2 deletions utils/db/db-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,14 @@ export async function deleteEventsFromDatabase(

export async function trackFailedRelayPublish(
eventId: string,
relays: string[]
relays: string[],
event?: NostrEvent
): Promise<void> {
try {
await fetch("/api/db/track-failed-publish", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ eventId, relays }),
body: JSON.stringify({ eventId, relays, event }),
});
} catch (error) {
console.error("Failed to track failed relay publish:", error);
Expand Down
19 changes: 19 additions & 0 deletions utils/db/db-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,18 @@ async function initializeTables(): Promise<void> {

CREATE INDEX IF NOT EXISTS idx_discount_codes_pubkey ON discount_codes(pubkey);
CREATE INDEX IF NOT EXISTS idx_discount_codes_code ON discount_codes(code);

-- Failed relay publish tracking table
CREATE TABLE IF NOT EXISTS failed_relay_publishes (
event_id TEXT PRIMARY KEY,
relays TEXT NOT NULL,
event_data TEXT,
created_at BIGINT NOT NULL,
retry_count INTEGER DEFAULT 0
);

CREATE INDEX IF NOT EXISTS idx_failed_relay_publishes_created_at ON failed_relay_publishes(created_at ASC);
CREATE INDEX IF NOT EXISTS idx_failed_relay_publishes_retry_count ON failed_relay_publishes(retry_count);
`);

// Migration: Add is_read, order_status, order_id columns to existing message_events tables
Expand Down Expand Up @@ -210,6 +222,13 @@ async function initializeTables(): Promise<void> {
ALTER TABLE message_events ADD COLUMN order_id TEXT DEFAULT NULL;
CREATE INDEX IF NOT EXISTS idx_message_events_order_id ON message_events(order_id);
END IF;

IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'failed_relay_publishes' AND column_name = 'event_data'
) THEN
ALTER TABLE failed_relay_publishes ADD COLUMN event_data TEXT;
END IF;
END $$;
`);

Expand Down
11 changes: 7 additions & 4 deletions utils/nostr/nostr-helper-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,8 @@ export async function sendGiftWrappedMessageEvent(
const { trackFailedRelayPublish } = await import("@/utils/db/db-client");
await trackFailedRelayPublish(
giftWrappedMessageEvent.id,
allWriteRelays
allWriteRelays,
giftWrappedMessageEvent
).catch(console.error);
}
}
Expand Down Expand Up @@ -1006,9 +1007,11 @@ export async function finalizeAndSendNostrEvent(
error
);
const { trackFailedRelayPublish } = await import("@/utils/db/db-client");
await trackFailedRelayPublish(signedEvent.id, allWriteRelays).catch(
console.error
);
await trackFailedRelayPublish(
signedEvent.id,
allWriteRelays,
signedEvent
).catch(console.error);
}

// return the signed event to caller so we know generated IDs
Expand Down