diff --git a/packages/ditto/app.ts b/packages/ditto/app.ts index 931b3825..af0f9cfb 100644 --- a/packages/ditto/app.ts +++ b/packages/ditto/app.ts @@ -194,7 +194,7 @@ const pgstore = new DittoPgStore({ }); const pool = new DittoPool({ conf, relay: pgstore }); -const relay = new DittoRelayStore({ db, conf, relay: pgstore }); +const relay = new DittoRelayStore({ db, conf, pool, relay: pgstore }); await seedZapSplits({ conf, relay }); diff --git a/packages/ditto/storages/DittoRelayStore.test.ts b/packages/ditto/storages/DittoRelayStore.test.ts index 9940ba05..689ddab4 100644 --- a/packages/ditto/storages/DittoRelayStore.test.ts +++ b/packages/ditto/storages/DittoRelayStore.test.ts @@ -134,9 +134,29 @@ Deno.test('Admin revokes nip05 grant and nip05 column gets null', async () => { assertEquals(nullRow?.nip05_hostname, null); }); +Deno.test('fetchRelated', async () => { + await using test = setupTest(); + const { pool, store } = test; + + const post = genEvent({ kind: 1, content: 'hi' }); + const reply = genEvent({ kind: 1, content: 'wussup?', tags: [['e', post.id], ['p', post.pubkey]] }); + + await pool.event(post); + await pool.event(reply); + + await store.event(reply); + + await waitFor(async () => { + const { count } = await test.store.count([{ ids: [post.id] }]); + return count > 0; + }, 3000); +}); + function setupTest(cb?: (req: Request) => Response | Promise) { const conf = new DittoConf(Deno.env); const db = new DittoPolyPg(conf.databaseUrl); + + const pool = new MockRelay(); const relay = new MockRelay(); const mockFetch: typeof fetch = async (input, init) => { @@ -148,12 +168,13 @@ function setupTest(cb?: (req: Request) => Response | Promise) { } }; - const store = new DittoRelayStore({ conf, db, relay, fetch: mockFetch }); + const store = new DittoRelayStore({ conf, db, pool, relay, fetch: mockFetch }); return { db, - store, conf, + pool, + store, [Symbol.asyncDispose]: async () => { await store[Symbol.asyncDispose](); await db[Symbol.asyncDispose](); diff --git a/packages/ditto/storages/DittoRelayStore.ts b/packages/ditto/storages/DittoRelayStore.ts index 2f5a1fb7..7ff0b07f 100644 --- a/packages/ditto/storages/DittoRelayStore.ts +++ b/packages/ditto/storages/DittoRelayStore.ts @@ -46,6 +46,7 @@ import { renderWebPushNotification } from '@/views/mastodon/push.ts'; interface DittoRelayStoreOpts { db: DittoDB; conf: DittoConf; + pool: NRelay; relay: NRelay; fetch?: typeof fetch; } @@ -193,7 +194,12 @@ export class DittoRelayStore implements NRelay { this.prewarmLinkPreview(event, signal), this.generateSetEvents(event), ]) - .then(() => this.webPush(event)) + .then(() => + Promise.allSettled([ + this.webPush(event), + this.fetchRelated(event), + ]) + ) .catch(() => {}); } } @@ -375,6 +381,39 @@ export class DittoRelayStore implements NRelay { } } + private async fetchRelated(event: NostrEvent): Promise { + const ids = new Set(); + + for (const tag of event.tags) { + const [name, value] = tag; + + if ((name === 'e' || name === 'q') && isNostrId(value) && !this.encounters.has(value)) { + ids.add(value); + } + } + + const { db, pool } = this.opts; + + if (ids.size) { + const query = db.kysely + .selectFrom('nostr_events') + .select('id') + .where('id', 'in', [...ids]); + + for (const row of await query.execute().catch(() => [])) { + ids.delete(row.id); + } + } + + if (ids.size) { + const signal = AbortSignal.timeout(1000); + + for (const event of await pool.query([{ ids: [...ids] }], { signal }).catch(() => [])) { + await this.event(event).catch(() => {}); + } + } + } + private async prewarmLinkPreview(event: NostrEvent, signal?: AbortSignal): Promise { const { firstUrl } = parseNoteContent(stripimeta(event.content, event.tags), [], this.opts); diff --git a/scripts/db-populate-nip05.ts b/scripts/db-populate-nip05.ts index 49866579..f282b674 100644 --- a/scripts/db-populate-nip05.ts +++ b/scripts/db-populate-nip05.ts @@ -1,5 +1,6 @@ import { Semaphore } from '@core/asyncutil'; import { NostrEvent } from '@nostrify/nostrify'; +import { MockRelay } from '@nostrify/nostrify/test'; import { DittoConf } from '@ditto/conf'; import { DittoPolyPg } from '@ditto/db'; @@ -11,7 +12,7 @@ const conf = new DittoConf(Deno.env); const db = new DittoPolyPg(conf.databaseUrl); const pgstore = new DittoPgStore({ db, conf }); -const relaystore = new DittoRelayStore({ conf, db, relay: pgstore }); +const relaystore = new DittoRelayStore({ conf, db, pool: new MockRelay(), relay: pgstore }); const sem = new Semaphore(5);