From ddb93af09fbc740602da1b1e87ae18682bd4dc87 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Thu, 20 Jun 2024 14:33:25 -0300 Subject: [PATCH 01/15] feat(DittoTables): create EventZapRow --- src/db/DittoTables.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/db/DittoTables.ts b/src/db/DittoTables.ts index aed8c8c2..e56a062e 100644 --- a/src/db/DittoTables.ts +++ b/src/db/DittoTables.ts @@ -7,6 +7,7 @@ export interface DittoTables { author_stats: AuthorStatsRow; event_stats: EventStatsRow; pubkey_domains: PubkeyDomainRow; + event_zaps: EventZapRow; } interface AuthorStatsRow { @@ -69,3 +70,11 @@ interface PubkeyDomainRow { domain: string; last_updated_at: number; } + +interface EventZapRow { + receipt_id: string; + target_event_id: string; + sender_pubkey: string; + amount: number; + comment: string; +} From bac0b488010740becae4d81fbcd2bafddfceccc9 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Thu, 20 Jun 2024 14:36:19 -0300 Subject: [PATCH 02/15] feat: add migration for event_zaps;create idx_event_zaps_id_amount --- src/db/migrations/027_add_zap_events.ts | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/db/migrations/027_add_zap_events.ts diff --git a/src/db/migrations/027_add_zap_events.ts b/src/db/migrations/027_add_zap_events.ts new file mode 100644 index 00000000..058e6baf --- /dev/null +++ b/src/db/migrations/027_add_zap_events.ts @@ -0,0 +1,26 @@ +import { Kysely } from 'kysely'; + +export async function up(db: Kysely): Promise { + await db.schema + .createTable('event_zaps') + .ifNotExists() + .addColumn('receipt_id', 'text', (col) => col.primaryKey()) + .addColumn('target_event_id', 'text', (col) => col.notNull()) + .addColumn('sender_pubkey', 'text', (col) => col.notNull()) + .addColumn('amount', 'integer', (col) => col.notNull()) + .addColumn('comment', 'text', (col) => col.notNull()) + .execute(); + + await db.schema + .createIndex('idx_event_zaps_id_amount') + .on('event_zaps') + .column('amount') + .column('target_event_id') + .ifNotExists() + .execute(); +} + +export async function down(db: Kysely): Promise { + await db.schema.dropIndex('idx_event_zaps_id_amount').execute(); + await db.schema.dropTable('event_zaps').execute(); +} From 1b4ebaccd82f5c8eb1763bc0b6891b178719bcba Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Thu, 20 Jun 2024 15:26:08 -0300 Subject: [PATCH 03/15] refactor: resolve import specifier via the active import map --- src/firehose.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/firehose.ts b/src/firehose.ts index 2c776fe4..f715c686 100644 --- a/src/firehose.ts +++ b/src/firehose.ts @@ -3,7 +3,7 @@ import { Stickynotes } from '@soapbox/stickynotes'; import { Storages } from '@/storages.ts'; import { nostrNow } from '@/utils.ts'; -import * as pipeline from './pipeline.ts'; +import * as pipeline from '@/pipeline.ts'; const console = new Stickynotes('ditto:firehose'); From 2d937a7378b4b3e5d4d7899d02fd9d4d721c9974 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 21 Jun 2024 18:20:10 -0300 Subject: [PATCH 04/15] refactor(event_zaps): rename amount to amount_millisats --- src/db/DittoTables.ts | 2 +- src/db/migrations/027_add_zap_events.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/db/DittoTables.ts b/src/db/DittoTables.ts index e56a062e..863ca61e 100644 --- a/src/db/DittoTables.ts +++ b/src/db/DittoTables.ts @@ -75,6 +75,6 @@ interface EventZapRow { receipt_id: string; target_event_id: string; sender_pubkey: string; - amount: number; + amount_millisats: number; comment: string; } diff --git a/src/db/migrations/027_add_zap_events.ts b/src/db/migrations/027_add_zap_events.ts index 058e6baf..fe2e1d20 100644 --- a/src/db/migrations/027_add_zap_events.ts +++ b/src/db/migrations/027_add_zap_events.ts @@ -3,11 +3,10 @@ import { Kysely } from 'kysely'; export async function up(db: Kysely): Promise { await db.schema .createTable('event_zaps') - .ifNotExists() .addColumn('receipt_id', 'text', (col) => col.primaryKey()) .addColumn('target_event_id', 'text', (col) => col.notNull()) .addColumn('sender_pubkey', 'text', (col) => col.notNull()) - .addColumn('amount', 'integer', (col) => col.notNull()) + .addColumn('amount_millisats', 'integer', (col) => col.notNull()) .addColumn('comment', 'text', (col) => col.notNull()) .execute(); @@ -21,6 +20,6 @@ export async function up(db: Kysely): Promise { } export async function down(db: Kysely): Promise { - await db.schema.dropIndex('idx_event_zaps_id_amount').execute(); + await db.schema.dropIndex('idx_event_zaps_id_amount').ifExists().execute(); await db.schema.dropTable('event_zaps').execute(); } From ec82e14410c57019ac1567a6c56aea8e44a85018 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 21 Jun 2024 18:35:26 -0300 Subject: [PATCH 05/15] fix: add amount_millisats in event_zaps index --- src/db/migrations/027_add_zap_events.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/migrations/027_add_zap_events.ts b/src/db/migrations/027_add_zap_events.ts index fe2e1d20..58b231fa 100644 --- a/src/db/migrations/027_add_zap_events.ts +++ b/src/db/migrations/027_add_zap_events.ts @@ -13,7 +13,7 @@ export async function up(db: Kysely): Promise { await db.schema .createIndex('idx_event_zaps_id_amount') .on('event_zaps') - .column('amount') + .column('amount_millisats') .column('target_event_id') .ifNotExists() .execute(); From 2c08b9a2f0a16a3cf275cd49bc8bcfefec890f55 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 21 Jun 2024 20:36:59 -0300 Subject: [PATCH 06/15] feat: create scavenger and handle kind 9735 --- src/utils/scavenger.ts | 48 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/utils/scavenger.ts diff --git a/src/utils/scavenger.ts b/src/utils/scavenger.ts new file mode 100644 index 00000000..0ffaa669 --- /dev/null +++ b/src/utils/scavenger.ts @@ -0,0 +1,48 @@ +import { NostrEvent, NSchema as n } from '@nostrify/nostrify'; +import { Kysely } from 'kysely'; +import { z } from 'zod'; + +import { DittoTables } from '@/db/DittoTables.ts'; +import { getAmount } from '@/utils/bolt11.ts'; + +interface ScavengerEventOpts { + savedEvent: Promise; + kysely: Kysely; +} + +/** Consumes the event already stored in the database and uses it to insert into a new custom table, if eligible. + * Scavenger is organism that eats dead or rotting biomass, such as animal flesh or plant material. */ +async function scavengerEvent({ savedEvent, kysely }: ScavengerEventOpts): Promise { + const event = await savedEvent; + if (!event) return; + + switch (event.kind) { + case 9735: + await handleEvent9735(kysely, event); + break; + } +} + +async function handleEvent9735(kysely: Kysely, event: NostrEvent) { + const zapRequestString = event?.tags?.find(([name]) => name === 'description')?.[1]; + if (!zapRequestString) return; + const zapRequest = n.json().pipe(n.event()).optional().catch(undefined).parse(zapRequestString); + if (!zapRequest) return; + + const amountSchema = z.coerce.number().int().nonnegative().catch(0); + const amount_millisats = amountSchema.parse(getAmount(event?.tags.find(([name]) => name === 'bolt11')?.[1])); + if (!amount_millisats || amount_millisats < 1) return; + + const zappedEventId = zapRequest.tags.find(([name]) => name === 'e')?.[1]; + if (!zappedEventId) return; + + await kysely.insertInto('event_zaps').values({ + receipt_id: event.id, + target_event_id: zappedEventId, + sender_pubkey: zapRequest.pubkey, + amount_millisats, + comment: zapRequest.content, + }).execute(); +} + +export { scavengerEvent }; From 1b30f10a9fec1f554d488a88d3825dd8d8f9f6e2 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 21 Jun 2024 21:39:18 -0300 Subject: [PATCH 07/15] test(scavenger): store valid data into event_zaps table --- src/utils/scavenger.test.ts | 55 +++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/utils/scavenger.test.ts diff --git a/src/utils/scavenger.test.ts b/src/utils/scavenger.test.ts new file mode 100644 index 00000000..a557c5b1 --- /dev/null +++ b/src/utils/scavenger.test.ts @@ -0,0 +1,55 @@ +import { assertEquals } from '@std/assert'; +import { generateSecretKey } from 'nostr-tools'; + +import { genEvent, getTestDB } from '@/test.ts'; +import { scavengerEvent } from '@/utils/scavenger.ts'; + +Deno.test('store one zap receipt in nostr_events; convert it into event_zaps table format and store it', async () => { + await using db = await getTestDB(); + const kysely = db.kysely; + + const sk = generateSecretKey(); + + const event = genEvent({ + 'id': '67b48a14fb66c60c8f9070bdeb37afdfcc3d08ad01989460448e4081eddda446', + 'pubkey': '9630f464cca6a5147aa8a35f0bcdd3ce485324e732fd39e09233b1d848238f31', + 'created_at': 1674164545, + 'kind': 9735, + 'tags': [ + ['p', '32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245'], + ['P', '97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322'], + ['e', '3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8'], + [ + 'bolt11', + 'lnbc10u1p3unwfusp5t9r3yymhpfqculx78u027lxspgxcr2n2987mx2j55nnfs95nxnzqpp5jmrh92pfld78spqs78v9euf2385t83uvpwk9ldrlvf6ch7tpascqhp5zvkrmemgth3tufcvflmzjzfvjt023nazlhljz2n9hattj4f8jq8qxqyjw5qcqpjrzjqtc4fc44feggv7065fqe5m4ytjarg3repr5j9el35xhmtfexc42yczarjuqqfzqqqqqqqqlgqqqqqqgq9q9qxpqysgq079nkq507a5tw7xgttmj4u990j7wfggtrasah5gd4ywfr2pjcn29383tphp4t48gquelz9z78p4cq7ml3nrrphw5w6eckhjwmhezhnqpy6gyf0', + ], + [ + 'description', + '{"pubkey":"97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322","content":"","id":"d9cc14d50fcb8c27539aacf776882942c1a11ea4472f8cdec1dea82fab66279d","created_at":1674164539,"sig":"77127f636577e9029276be060332ea565deaf89ff215a494ccff16ae3f757065e2bc59b2e8c113dd407917a010b3abd36c8d7ad84c0e3ab7dab3a0b0caa9835d","kind":9734,"tags":[["e","3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8"],["p","32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"],["relays","wss://relay.damus.io","wss://nostr-relay.wlvs.space","wss://nostr.fmt.wiz.biz","wss://relay.nostr.bg","wss://nostr.oxtr.dev","wss://nostr.v0l.io","wss://brb.io","wss://nostr.bitcoiner.social","ws://monad.jb55.com:8080","wss://relay.snort.social"]]}', + ], + ['preimage', '5d006d2cf1e73c7148e7519a4c68adc81642ce0e25a432b2434c99f97344c15f'], + ], + 'content': '', + }, sk); + + await db.store.event(event); + + // deno-lint-ignore require-await + await scavengerEvent({ savedEvent: (async () => event)(), kysely: kysely }); + + const zapReceipts = await kysely.selectFrom('nostr_events').selectAll().execute(); + const customEventZaps = await kysely.selectFrom('event_zaps').selectAll().execute(); + + assertEquals(zapReceipts.length, 1); // basic check + assertEquals(customEventZaps.length, 1); // basic check + + const expected = { + receipt_id: event.id, + target_event_id: '3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8', + sender_pubkey: '97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322', + amount_millisats: 1000000, + comment: '', + }; + + assertEquals(customEventZaps[0], expected); +}); From 771d7f79db09ae8f302b2302e094734c1860c6be Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 21 Jun 2024 21:45:55 -0300 Subject: [PATCH 08/15] refactor(scavenger): put SQL insert into try-catch block --- src/utils/scavenger.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/utils/scavenger.ts b/src/utils/scavenger.ts index 0ffaa669..d9874cb7 100644 --- a/src/utils/scavenger.ts +++ b/src/utils/scavenger.ts @@ -36,13 +36,17 @@ async function handleEvent9735(kysely: Kysely, event: NostrEvent) { const zappedEventId = zapRequest.tags.find(([name]) => name === 'e')?.[1]; if (!zappedEventId) return; - await kysely.insertInto('event_zaps').values({ - receipt_id: event.id, - target_event_id: zappedEventId, - sender_pubkey: zapRequest.pubkey, - amount_millisats, - comment: zapRequest.content, - }).execute(); + try { + await kysely.insertInto('event_zaps').values({ + receipt_id: event.id, + target_event_id: zappedEventId, + sender_pubkey: zapRequest.pubkey, + amount_millisats, + comment: zapRequest.content, + }).execute(); + } catch { + // receipt_id is unique, do nothing + } } export { scavengerEvent }; From 89b56539d177bcd1816b8928917fbf437b458a3e Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 21 Jun 2024 22:23:34 -0300 Subject: [PATCH 09/15] test(scavenger): code coverage 100.00% --- src/utils/scavenger.test.ts | 86 +++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/utils/scavenger.test.ts b/src/utils/scavenger.test.ts index a557c5b1..9d768999 100644 --- a/src/utils/scavenger.test.ts +++ b/src/utils/scavenger.test.ts @@ -36,6 +36,8 @@ Deno.test('store one zap receipt in nostr_events; convert it into event_zaps tab // deno-lint-ignore require-await await scavengerEvent({ savedEvent: (async () => event)(), kysely: kysely }); + // deno-lint-ignore require-await + await scavengerEvent({ savedEvent: (async () => event)(), kysely: kysely }); // just to trigger "Error: FOREIGN KEY constraint failed" const zapReceipts = await kysely.selectFrom('nostr_events').selectAll().execute(); const customEventZaps = await kysely.selectFrom('event_zaps').selectAll().execute(); @@ -53,3 +55,87 @@ Deno.test('store one zap receipt in nostr_events; convert it into event_zaps tab assertEquals(customEventZaps[0], expected); }); + +// The function tests below only handle the edge cases and don't assert anything +// If no error happens = ok + +Deno.test('savedEvent is undefined', async () => { + await using db = await getTestDB(); + const kysely = db.kysely; + + // deno-lint-ignore require-await + await scavengerEvent({ savedEvent: (async () => undefined)(), kysely: kysely }); + + // no error happened = ok +}); + +Deno.test('zap receipt does not have a "description" tag', async () => { + await using db = await getTestDB(); + const kysely = db.kysely; + + const sk = generateSecretKey(); + + const event = genEvent({ kind: 9735 }, sk); + + // deno-lint-ignore require-await + await scavengerEvent({ savedEvent: (async () => event)(), kysely: kysely }); + + // no error happened = ok +}); + +Deno.test('zap receipt does not have a zap request stringified value in the "description" tag', async () => { + await using db = await getTestDB(); + const kysely = db.kysely; + + const sk = generateSecretKey(); + + const event = genEvent({ kind: 9735, tags: [['description', 'yolo']] }, sk); + + // deno-lint-ignore require-await + await scavengerEvent({ savedEvent: (async () => event)(), kysely: kysely }); + + // no error happened = ok +}); + +Deno.test('zap receipt does not have a "bolt11" tag', async () => { + await using db = await getTestDB(); + const kysely = db.kysely; + + const sk = generateSecretKey(); + + const event = genEvent({ + kind: 9735, + tags: [[ + 'description', + '{"pubkey":"97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322","content":"","id":"d9cc14d50fcb8c27539aacf776882942c1a11ea4472f8cdec1dea82fab66279d","created_at":1674164539,"sig":"77127f636577e9029276be060332ea565deaf89ff215a494ccff16ae3f757065e2bc59b2e8c113dd407917a010b3abd36c8d7ad84c0e3ab7dab3a0b0caa9835d","kind":9734,"tags":[["e","3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8"],["p","32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"],["relays","wss://relay.damus.io","wss://nostr-relay.wlvs.space","wss://nostr.fmt.wiz.biz","wss://relay.nostr.bg","wss://nostr.oxtr.dev","wss://nostr.v0l.io","wss://brb.io","wss://nostr.bitcoiner.social","ws://monad.jb55.com:8080","wss://relay.snort.social"]]}', + ]], + }, sk); + + // deno-lint-ignore require-await + await scavengerEvent({ savedEvent: (async () => event)(), kysely: kysely }); + + // no error happened = ok +}); + +Deno.test('zap request inside zap receipt does not have an "e" tag', async () => { + await using db = await getTestDB(); + const kysely = db.kysely; + + const sk = generateSecretKey(); + + const event = genEvent({ + kind: 9735, + tags: [[ + 'bolt11', + 'lnbc10u1p3unwfusp5t9r3yymhpfqculx78u027lxspgxcr2n2987mx2j55nnfs95nxnzqpp5jmrh92pfld78spqs78v9euf2385t83uvpwk9ldrlvf6ch7tpascqhp5zvkrmemgth3tufcvflmzjzfvjt023nazlhljz2n9hattj4f8jq8qxqyjw5qcqpjrzjqtc4fc44feggv7065fqe5m4ytjarg3repr5j9el35xhmtfexc42yczarjuqqfzqqqqqqqqlgqqqqqqgq9q9qxpqysgq079nkq507a5tw7xgttmj4u990j7wfggtrasah5gd4ywfr2pjcn29383tphp4t48gquelz9z78p4cq7ml3nrrphw5w6eckhjwmhezhnqpy6gyf0', + ], [ + 'description', + '{"pubkey":"97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322","content":"","id":"d9cc14d50fcb8c27539aacf776882942c1a11ea4472f8cdec1dea82fab66279d","created_at":1674164539,"sig":"77127f636577e9029276be060332ea565deaf89ff215a494ccff16ae3f757065e2bc59b2e8c113dd407917a010b3abd36c8d7ad84c0e3ab7dab3a0b0caa9835d","kind":9734,"tags":[["p","32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"],["relays","wss://relay.damus.io","wss://nostr-relay.wlvs.space","wss://nostr.fmt.wiz.biz","wss://relay.nostr.bg","wss://nostr.oxtr.dev","wss://nostr.v0l.io","wss://brb.io","wss://nostr.bitcoiner.social","ws://monad.jb55.com:8080","wss://relay.snort.social"]]}', + ]], + }, sk); + + // deno-lint-ignore require-await + await scavengerEvent({ savedEvent: (async () => event)(), kysely: kysely }); + + // no error happened = ok +}); From 9731fc257299efe7a5f3663a3a4f4e5a88874f33 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 21 Jun 2024 22:25:34 -0300 Subject: [PATCH 10/15] feat: add scavenger to the pipeline --- src/pipeline.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pipeline.ts b/src/pipeline.ts index 40ba9c12..bd56f09e 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -17,6 +17,7 @@ import { verifyEventWorker } from '@/workers/verify.ts'; import { nip05Cache } from '@/utils/nip05.ts'; import { updateStats } from '@/utils/stats.ts'; import { getTagSet } from '@/utils/tags.ts'; +import { scavengerEvent } from '@/utils/scavenger.ts'; const debug = Debug('ditto:pipeline'); @@ -50,7 +51,7 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise { +async function storeEvent(event: DittoEvent, signal?: AbortSignal): Promise { if (NKinds.ephemeral(event.kind)) return; const store = await Storages.db(); const kysely = await DittoDB.getInstance(); await updateStats({ event, store, kysely }).catch(debug); await store.event(event, { signal }); + + return event; } /** Parse kind 0 metadata and track indexes in the database. */ From 0d7ef68353817977e8acc3a81091b0f9e0cdcaaa Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sun, 23 Jun 2024 13:38:40 -0300 Subject: [PATCH 11/15] feat: add pagination and sort by amount - zapped_by endpoint --- src/controllers/api/statuses.ts | 50 ++++++++++++++++----------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index 28e0778a..30550bbb 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -8,14 +8,21 @@ import { z } from 'zod'; import { type AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; import { DittoDB } from '@/db/DittoDB.ts'; -import { getAmount } from '@/utils/bolt11.ts'; import { getAncestors, getAuthor, getDescendants, getEvent } from '@/queries.ts'; import { getUnattachedMediaByIds } from '@/db/unattached-media.ts'; import { renderEventAccounts } from '@/views.ts'; import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts'; import { Storages } from '@/storages.ts'; import { hydrateEvents, purifyEvent } from '@/storages/hydrate.ts'; -import { createEvent, paginated, paginationSchema, parseBody, updateListEvent } from '@/utils/api.ts'; +import { + createEvent, + listPaginationSchema, + paginated, + paginatedList, + paginationSchema, + parseBody, + updateListEvent, +} from '@/utils/api.ts'; import { getInvoice, getLnurl } from '@/utils/lnurl.ts'; import { lookupPubkey } from '@/utils/lookup.ts'; import { addTag, deleteTag } from '@/utils/tags.ts'; @@ -545,33 +552,26 @@ const zapController: AppController = async (c) => { const zappedByController: AppController = async (c) => { const id = c.req.param('id'); + const params = listPaginationSchema.parse(c.req.query()); const store = await Storages.db(); - const amountSchema = z.coerce.number().int().nonnegative().catch(0); + const db = await DittoDB.getInstance(); - const events = (await store.query([{ kinds: [9735], '#e': [id], limit: 100 }])).map((event) => { - const zapRequestString = event.tags.find(([name]) => name === 'description')?.[1]; - if (!zapRequestString) return; - try { - const zapRequest = n.json().pipe(n.event()).parse(zapRequestString); - const amount = zapRequest?.tags.find(([name]: any) => name === 'amount')?.[1]; - if (!amount) { - const amount = getAmount(event?.tags.find(([name]) => name === 'bolt11')?.[1]); - if (!amount) return; - zapRequest.tags.push(['amount', amount]); - } - return zapRequest; - } catch { - return; - } - }).filter(Boolean) as DittoEvent[]; + const zaps = await db.selectFrom('event_zaps') + .selectAll() + .where('target_event_id', '=', id) + .orderBy('amount_millisats', 'desc') + .limit(params.limit) + .offset(params.offset).execute(); - await hydrateEvents({ events, store }); + const authors = await store.query([{ kinds: [0], authors: zaps.map((zap) => zap.sender_pubkey) }]); const results = (await Promise.all( - events.map(async (event) => { - const amount = amountSchema.parse(event.tags.find(([name]) => name === 'amount')?.[1]); - const comment = event?.content ?? ''; - const account = event?.author ? await renderAccount(event.author) : await accountFromPubkey(event.pubkey); + zaps.map(async (zap) => { + const amount = zap.amount_millisats; + const comment = zap.comment; + + const sender = authors.find((author) => author.pubkey === zap.sender_pubkey); + const account = sender ? await renderAccount(sender) : await accountFromPubkey(zap.sender_pubkey); return { comment, @@ -581,7 +581,7 @@ const zappedByController: AppController = async (c) => { }), )).filter(Boolean); - return c.json(results); + return paginatedList(c, params, results); }; export { From 05bf417bcccb0f75e6cc229d9c2879cbb74bf590 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sun, 23 Jun 2024 22:49:15 -0300 Subject: [PATCH 12/15] perf(event_zaps): make two separate indexes instead of a compound index --- src/db/migrations/027_add_zap_events.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/db/migrations/027_add_zap_events.ts b/src/db/migrations/027_add_zap_events.ts index 58b231fa..2fcc101c 100644 --- a/src/db/migrations/027_add_zap_events.ts +++ b/src/db/migrations/027_add_zap_events.ts @@ -11,15 +11,22 @@ export async function up(db: Kysely): Promise { .execute(); await db.schema - .createIndex('idx_event_zaps_id_amount') + .createIndex('idx_event_zaps_amount_millisats') .on('event_zaps') .column('amount_millisats') + .ifNotExists() + .execute(); + + await db.schema + .createIndex('idx_event_zaps_target_event_id') + .on('event_zaps') .column('target_event_id') .ifNotExists() .execute(); } export async function down(db: Kysely): Promise { - await db.schema.dropIndex('idx_event_zaps_id_amount').ifExists().execute(); + await db.schema.dropIndex('idx_event_zaps_amount_millisats').ifExists().execute(); + await db.schema.dropIndex('idx_event_zaps_target_event_id').ifExists().execute(); await db.schema.dropTable('event_zaps').execute(); } From e1ee3bd8e97665826426aada49f2867501dcd50d Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sun, 23 Jun 2024 23:45:32 -0300 Subject: [PATCH 13/15] refactor: remove scavenger, put logic directly into pipeline --- src/pipeline.test.ts | 125 ++++++++++++++++++++++++++++++++ src/pipeline.ts | 38 +++++++++- src/utils/scavenger.test.ts | 141 ------------------------------------ src/utils/scavenger.ts | 52 ------------- 4 files changed, 159 insertions(+), 197 deletions(-) create mode 100644 src/pipeline.test.ts delete mode 100644 src/utils/scavenger.ts diff --git a/src/pipeline.test.ts b/src/pipeline.test.ts new file mode 100644 index 00000000..64cb523b --- /dev/null +++ b/src/pipeline.test.ts @@ -0,0 +1,125 @@ +import { assertEquals } from '@std/assert'; +import { generateSecretKey } from 'nostr-tools'; + +import { genEvent, getTestDB } from '@/test.ts'; +import { handleZaps } from '@/pipeline.ts'; + +Deno.test('store one zap receipt in nostr_events; convert it into event_zaps table format and store it', async () => { + await using db = await getTestDB(); + const kysely = db.kysely; + + const sk = generateSecretKey(); + + const event = genEvent({ + 'id': '67b48a14fb66c60c8f9070bdeb37afdfcc3d08ad01989460448e4081eddda446', + 'pubkey': '9630f464cca6a5147aa8a35f0bcdd3ce485324e732fd39e09233b1d848238f31', + 'created_at': 1674164545, + 'kind': 9735, + 'tags': [ + ['p', '32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245'], + ['P', '97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322'], + ['e', '3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8'], + [ + 'bolt11', + 'lnbc10u1p3unwfusp5t9r3yymhpfqculx78u027lxspgxcr2n2987mx2j55nnfs95nxnzqpp5jmrh92pfld78spqs78v9euf2385t83uvpwk9ldrlvf6ch7tpascqhp5zvkrmemgth3tufcvflmzjzfvjt023nazlhljz2n9hattj4f8jq8qxqyjw5qcqpjrzjqtc4fc44feggv7065fqe5m4ytjarg3repr5j9el35xhmtfexc42yczarjuqqfzqqqqqqqqlgqqqqqqgq9q9qxpqysgq079nkq507a5tw7xgttmj4u990j7wfggtrasah5gd4ywfr2pjcn29383tphp4t48gquelz9z78p4cq7ml3nrrphw5w6eckhjwmhezhnqpy6gyf0', + ], + [ + 'description', + '{"pubkey":"97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322","content":"","id":"d9cc14d50fcb8c27539aacf776882942c1a11ea4472f8cdec1dea82fab66279d","created_at":1674164539,"sig":"77127f636577e9029276be060332ea565deaf89ff215a494ccff16ae3f757065e2bc59b2e8c113dd407917a010b3abd36c8d7ad84c0e3ab7dab3a0b0caa9835d","kind":9734,"tags":[["e","3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8"],["p","32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"],["relays","wss://relay.damus.io","wss://nostr-relay.wlvs.space","wss://nostr.fmt.wiz.biz","wss://relay.nostr.bg","wss://nostr.oxtr.dev","wss://nostr.v0l.io","wss://brb.io","wss://nostr.bitcoiner.social","ws://monad.jb55.com:8080","wss://relay.snort.social"]]}', + ], + ['preimage', '5d006d2cf1e73c7148e7519a4c68adc81642ce0e25a432b2434c99f97344c15f'], + ], + 'content': '', + }, sk); + + await db.store.event(event); + + await handleZaps(kysely, event); + await handleZaps(kysely, event); + + const zapReceipts = await kysely.selectFrom('nostr_events').selectAll().execute(); + const customEventZaps = await kysely.selectFrom('event_zaps').selectAll().execute(); + + assertEquals(zapReceipts.length, 1); // basic check + assertEquals(customEventZaps.length, 1); // basic check + + const expected = { + receipt_id: event.id, + target_event_id: '3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8', + sender_pubkey: '97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322', + amount_millisats: 1000000, + comment: '', + }; + + assertEquals(customEventZaps[0], expected); +}); + +// The function tests below only handle the edge cases and don't assert anything +// If no error happens = ok + +Deno.test('zap receipt does not have a "description" tag', async () => { + await using db = await getTestDB(); + const kysely = db.kysely; + + const sk = generateSecretKey(); + + const event = genEvent({ kind: 9735 }, sk); + + await handleZaps(kysely, event); + + // no error happened = ok +}); + +Deno.test('zap receipt does not have a zap request stringified value in the "description" tag', async () => { + await using db = await getTestDB(); + const kysely = db.kysely; + + const sk = generateSecretKey(); + + const event = genEvent({ kind: 9735, tags: [['description', 'yolo']] }, sk); + + await handleZaps(kysely, event); + + // no error happened = ok +}); + +Deno.test('zap receipt does not have a "bolt11" tag', async () => { + await using db = await getTestDB(); + const kysely = db.kysely; + + const sk = generateSecretKey(); + + const event = genEvent({ + kind: 9735, + tags: [[ + 'description', + '{"pubkey":"97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322","content":"","id":"d9cc14d50fcb8c27539aacf776882942c1a11ea4472f8cdec1dea82fab66279d","created_at":1674164539,"sig":"77127f636577e9029276be060332ea565deaf89ff215a494ccff16ae3f757065e2bc59b2e8c113dd407917a010b3abd36c8d7ad84c0e3ab7dab3a0b0caa9835d","kind":9734,"tags":[["e","3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8"],["p","32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"],["relays","wss://relay.damus.io","wss://nostr-relay.wlvs.space","wss://nostr.fmt.wiz.biz","wss://relay.nostr.bg","wss://nostr.oxtr.dev","wss://nostr.v0l.io","wss://brb.io","wss://nostr.bitcoiner.social","ws://monad.jb55.com:8080","wss://relay.snort.social"]]}', + ]], + }, sk); + + await handleZaps(kysely, event); + + // no error happened = ok +}); + +Deno.test('zap request inside zap receipt does not have an "e" tag', async () => { + await using db = await getTestDB(); + const kysely = db.kysely; + + const sk = generateSecretKey(); + + const event = genEvent({ + kind: 9735, + tags: [[ + 'bolt11', + 'lnbc10u1p3unwfusp5t9r3yymhpfqculx78u027lxspgxcr2n2987mx2j55nnfs95nxnzqpp5jmrh92pfld78spqs78v9euf2385t83uvpwk9ldrlvf6ch7tpascqhp5zvkrmemgth3tufcvflmzjzfvjt023nazlhljz2n9hattj4f8jq8qxqyjw5qcqpjrzjqtc4fc44feggv7065fqe5m4ytjarg3repr5j9el35xhmtfexc42yczarjuqqfzqqqqqqqqlgqqqqqqgq9q9qxpqysgq079nkq507a5tw7xgttmj4u990j7wfggtrasah5gd4ywfr2pjcn29383tphp4t48gquelz9z78p4cq7ml3nrrphw5w6eckhjwmhezhnqpy6gyf0', + ], [ + 'description', + '{"pubkey":"97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322","content":"","id":"d9cc14d50fcb8c27539aacf776882942c1a11ea4472f8cdec1dea82fab66279d","created_at":1674164539,"sig":"77127f636577e9029276be060332ea565deaf89ff215a494ccff16ae3f757065e2bc59b2e8c113dd407917a010b3abd36c8d7ad84c0e3ab7dab3a0b0caa9835d","kind":9734,"tags":[["p","32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"],["relays","wss://relay.damus.io","wss://nostr-relay.wlvs.space","wss://nostr.fmt.wiz.biz","wss://relay.nostr.bg","wss://nostr.oxtr.dev","wss://nostr.v0l.io","wss://brb.io","wss://nostr.bitcoiner.social","ws://monad.jb55.com:8080","wss://relay.snort.social"]]}', + ]], + }, sk); + + await handleZaps(kysely, event); + + // no error happened = ok +}); diff --git a/src/pipeline.ts b/src/pipeline.ts index b60ac9ed..4c942913 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -1,7 +1,8 @@ import { NKinds, NostrEvent, NSchema as n } from '@nostrify/nostrify'; import Debug from '@soapbox/stickynotes/debug'; -import { sql } from 'kysely'; +import { Kysely, sql } from 'kysely'; import { LRUCache } from 'lru-cache'; +import { z } from 'zod'; import { Conf } from '@/config.ts'; import { DittoDB } from '@/db/DittoDB.ts'; @@ -18,7 +19,8 @@ import { verifyEventWorker } from '@/workers/verify.ts'; import { nip05Cache } from '@/utils/nip05.ts'; import { updateStats } from '@/utils/stats.ts'; import { getTagSet } from '@/utils/tags.ts'; -import { scavengerEvent } from '@/utils/scavenger.ts'; +import { DittoTables } from '@/db/DittoTables.ts'; +import { getAmount } from '@/utils/bolt11.ts'; const debug = Debug('ditto:pipeline'); @@ -53,7 +55,8 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise { } } -export { handleEvent }; +/** Stores the event in the 'event_zaps' table */ +async function handleZaps(kysely: Kysely, event: NostrEvent) { + const zapRequestString = event?.tags?.find(([name]) => name === 'description')?.[1]; + if (!zapRequestString) return; + const zapRequest = n.json().pipe(n.event()).optional().catch(undefined).parse(zapRequestString); + if (!zapRequest) return; + + const amountSchema = z.coerce.number().int().nonnegative().catch(0); + const amount_millisats = amountSchema.parse(getAmount(event?.tags.find(([name]) => name === 'bolt11')?.[1])); + if (!amount_millisats || amount_millisats < 1) return; + + const zappedEventId = zapRequest.tags.find(([name]) => name === 'e')?.[1]; + if (!zappedEventId) return; + + try { + await kysely.insertInto('event_zaps').values({ + receipt_id: event.id, + target_event_id: zappedEventId, + sender_pubkey: zapRequest.pubkey, + amount_millisats, + comment: zapRequest.content, + }).execute(); + } catch { + // receipt_id is unique, do nothing + } +} + +export { handleEvent, handleZaps }; diff --git a/src/utils/scavenger.test.ts b/src/utils/scavenger.test.ts index 9d768999..e69de29b 100644 --- a/src/utils/scavenger.test.ts +++ b/src/utils/scavenger.test.ts @@ -1,141 +0,0 @@ -import { assertEquals } from '@std/assert'; -import { generateSecretKey } from 'nostr-tools'; - -import { genEvent, getTestDB } from '@/test.ts'; -import { scavengerEvent } from '@/utils/scavenger.ts'; - -Deno.test('store one zap receipt in nostr_events; convert it into event_zaps table format and store it', async () => { - await using db = await getTestDB(); - const kysely = db.kysely; - - const sk = generateSecretKey(); - - const event = genEvent({ - 'id': '67b48a14fb66c60c8f9070bdeb37afdfcc3d08ad01989460448e4081eddda446', - 'pubkey': '9630f464cca6a5147aa8a35f0bcdd3ce485324e732fd39e09233b1d848238f31', - 'created_at': 1674164545, - 'kind': 9735, - 'tags': [ - ['p', '32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245'], - ['P', '97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322'], - ['e', '3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8'], - [ - 'bolt11', - 'lnbc10u1p3unwfusp5t9r3yymhpfqculx78u027lxspgxcr2n2987mx2j55nnfs95nxnzqpp5jmrh92pfld78spqs78v9euf2385t83uvpwk9ldrlvf6ch7tpascqhp5zvkrmemgth3tufcvflmzjzfvjt023nazlhljz2n9hattj4f8jq8qxqyjw5qcqpjrzjqtc4fc44feggv7065fqe5m4ytjarg3repr5j9el35xhmtfexc42yczarjuqqfzqqqqqqqqlgqqqqqqgq9q9qxpqysgq079nkq507a5tw7xgttmj4u990j7wfggtrasah5gd4ywfr2pjcn29383tphp4t48gquelz9z78p4cq7ml3nrrphw5w6eckhjwmhezhnqpy6gyf0', - ], - [ - 'description', - '{"pubkey":"97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322","content":"","id":"d9cc14d50fcb8c27539aacf776882942c1a11ea4472f8cdec1dea82fab66279d","created_at":1674164539,"sig":"77127f636577e9029276be060332ea565deaf89ff215a494ccff16ae3f757065e2bc59b2e8c113dd407917a010b3abd36c8d7ad84c0e3ab7dab3a0b0caa9835d","kind":9734,"tags":[["e","3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8"],["p","32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"],["relays","wss://relay.damus.io","wss://nostr-relay.wlvs.space","wss://nostr.fmt.wiz.biz","wss://relay.nostr.bg","wss://nostr.oxtr.dev","wss://nostr.v0l.io","wss://brb.io","wss://nostr.bitcoiner.social","ws://monad.jb55.com:8080","wss://relay.snort.social"]]}', - ], - ['preimage', '5d006d2cf1e73c7148e7519a4c68adc81642ce0e25a432b2434c99f97344c15f'], - ], - 'content': '', - }, sk); - - await db.store.event(event); - - // deno-lint-ignore require-await - await scavengerEvent({ savedEvent: (async () => event)(), kysely: kysely }); - // deno-lint-ignore require-await - await scavengerEvent({ savedEvent: (async () => event)(), kysely: kysely }); // just to trigger "Error: FOREIGN KEY constraint failed" - - const zapReceipts = await kysely.selectFrom('nostr_events').selectAll().execute(); - const customEventZaps = await kysely.selectFrom('event_zaps').selectAll().execute(); - - assertEquals(zapReceipts.length, 1); // basic check - assertEquals(customEventZaps.length, 1); // basic check - - const expected = { - receipt_id: event.id, - target_event_id: '3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8', - sender_pubkey: '97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322', - amount_millisats: 1000000, - comment: '', - }; - - assertEquals(customEventZaps[0], expected); -}); - -// The function tests below only handle the edge cases and don't assert anything -// If no error happens = ok - -Deno.test('savedEvent is undefined', async () => { - await using db = await getTestDB(); - const kysely = db.kysely; - - // deno-lint-ignore require-await - await scavengerEvent({ savedEvent: (async () => undefined)(), kysely: kysely }); - - // no error happened = ok -}); - -Deno.test('zap receipt does not have a "description" tag', async () => { - await using db = await getTestDB(); - const kysely = db.kysely; - - const sk = generateSecretKey(); - - const event = genEvent({ kind: 9735 }, sk); - - // deno-lint-ignore require-await - await scavengerEvent({ savedEvent: (async () => event)(), kysely: kysely }); - - // no error happened = ok -}); - -Deno.test('zap receipt does not have a zap request stringified value in the "description" tag', async () => { - await using db = await getTestDB(); - const kysely = db.kysely; - - const sk = generateSecretKey(); - - const event = genEvent({ kind: 9735, tags: [['description', 'yolo']] }, sk); - - // deno-lint-ignore require-await - await scavengerEvent({ savedEvent: (async () => event)(), kysely: kysely }); - - // no error happened = ok -}); - -Deno.test('zap receipt does not have a "bolt11" tag', async () => { - await using db = await getTestDB(); - const kysely = db.kysely; - - const sk = generateSecretKey(); - - const event = genEvent({ - kind: 9735, - tags: [[ - 'description', - '{"pubkey":"97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322","content":"","id":"d9cc14d50fcb8c27539aacf776882942c1a11ea4472f8cdec1dea82fab66279d","created_at":1674164539,"sig":"77127f636577e9029276be060332ea565deaf89ff215a494ccff16ae3f757065e2bc59b2e8c113dd407917a010b3abd36c8d7ad84c0e3ab7dab3a0b0caa9835d","kind":9734,"tags":[["e","3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8"],["p","32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"],["relays","wss://relay.damus.io","wss://nostr-relay.wlvs.space","wss://nostr.fmt.wiz.biz","wss://relay.nostr.bg","wss://nostr.oxtr.dev","wss://nostr.v0l.io","wss://brb.io","wss://nostr.bitcoiner.social","ws://monad.jb55.com:8080","wss://relay.snort.social"]]}', - ]], - }, sk); - - // deno-lint-ignore require-await - await scavengerEvent({ savedEvent: (async () => event)(), kysely: kysely }); - - // no error happened = ok -}); - -Deno.test('zap request inside zap receipt does not have an "e" tag', async () => { - await using db = await getTestDB(); - const kysely = db.kysely; - - const sk = generateSecretKey(); - - const event = genEvent({ - kind: 9735, - tags: [[ - 'bolt11', - 'lnbc10u1p3unwfusp5t9r3yymhpfqculx78u027lxspgxcr2n2987mx2j55nnfs95nxnzqpp5jmrh92pfld78spqs78v9euf2385t83uvpwk9ldrlvf6ch7tpascqhp5zvkrmemgth3tufcvflmzjzfvjt023nazlhljz2n9hattj4f8jq8qxqyjw5qcqpjrzjqtc4fc44feggv7065fqe5m4ytjarg3repr5j9el35xhmtfexc42yczarjuqqfzqqqqqqqqlgqqqqqqgq9q9qxpqysgq079nkq507a5tw7xgttmj4u990j7wfggtrasah5gd4ywfr2pjcn29383tphp4t48gquelz9z78p4cq7ml3nrrphw5w6eckhjwmhezhnqpy6gyf0', - ], [ - 'description', - '{"pubkey":"97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322","content":"","id":"d9cc14d50fcb8c27539aacf776882942c1a11ea4472f8cdec1dea82fab66279d","created_at":1674164539,"sig":"77127f636577e9029276be060332ea565deaf89ff215a494ccff16ae3f757065e2bc59b2e8c113dd407917a010b3abd36c8d7ad84c0e3ab7dab3a0b0caa9835d","kind":9734,"tags":[["p","32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"],["relays","wss://relay.damus.io","wss://nostr-relay.wlvs.space","wss://nostr.fmt.wiz.biz","wss://relay.nostr.bg","wss://nostr.oxtr.dev","wss://nostr.v0l.io","wss://brb.io","wss://nostr.bitcoiner.social","ws://monad.jb55.com:8080","wss://relay.snort.social"]]}', - ]], - }, sk); - - // deno-lint-ignore require-await - await scavengerEvent({ savedEvent: (async () => event)(), kysely: kysely }); - - // no error happened = ok -}); diff --git a/src/utils/scavenger.ts b/src/utils/scavenger.ts deleted file mode 100644 index d9874cb7..00000000 --- a/src/utils/scavenger.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { NostrEvent, NSchema as n } from '@nostrify/nostrify'; -import { Kysely } from 'kysely'; -import { z } from 'zod'; - -import { DittoTables } from '@/db/DittoTables.ts'; -import { getAmount } from '@/utils/bolt11.ts'; - -interface ScavengerEventOpts { - savedEvent: Promise; - kysely: Kysely; -} - -/** Consumes the event already stored in the database and uses it to insert into a new custom table, if eligible. - * Scavenger is organism that eats dead or rotting biomass, such as animal flesh or plant material. */ -async function scavengerEvent({ savedEvent, kysely }: ScavengerEventOpts): Promise { - const event = await savedEvent; - if (!event) return; - - switch (event.kind) { - case 9735: - await handleEvent9735(kysely, event); - break; - } -} - -async function handleEvent9735(kysely: Kysely, event: NostrEvent) { - const zapRequestString = event?.tags?.find(([name]) => name === 'description')?.[1]; - if (!zapRequestString) return; - const zapRequest = n.json().pipe(n.event()).optional().catch(undefined).parse(zapRequestString); - if (!zapRequest) return; - - const amountSchema = z.coerce.number().int().nonnegative().catch(0); - const amount_millisats = amountSchema.parse(getAmount(event?.tags.find(([name]) => name === 'bolt11')?.[1])); - if (!amount_millisats || amount_millisats < 1) return; - - const zappedEventId = zapRequest.tags.find(([name]) => name === 'e')?.[1]; - if (!zappedEventId) return; - - try { - await kysely.insertInto('event_zaps').values({ - receipt_id: event.id, - target_event_id: zappedEventId, - sender_pubkey: zapRequest.pubkey, - amount_millisats, - comment: zapRequest.content, - }).execute(); - } catch { - // receipt_id is unique, do nothing - } -} - -export { scavengerEvent }; From fdb720386da698a2ca9cd15b8f3ece5ed1187d72 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sun, 23 Jun 2024 23:51:10 -0300 Subject: [PATCH 14/15] fix(handleZaps): reject all kinds but 9735 --- src/pipeline.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pipeline.ts b/src/pipeline.ts index 4c942913..a8d98acd 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -228,6 +228,8 @@ async function generateSetEvents(event: NostrEvent): Promise { /** Stores the event in the 'event_zaps' table */ async function handleZaps(kysely: Kysely, event: NostrEvent) { + if (event.kind !== 9735) return; + const zapRequestString = event?.tags?.find(([name]) => name === 'description')?.[1]; if (!zapRequestString) return; const zapRequest = n.json().pipe(n.event()).optional().catch(undefined).parse(zapRequestString); From 797c8668309497aa845d392a8697f20e4605f8b9 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Mon, 24 Jun 2024 12:07:33 -0300 Subject: [PATCH 15/15] refactor: storeEvent does not return event, move kysely into a variable above --- src/pipeline.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pipeline.ts b/src/pipeline.ts index a8d98acd..17998aa3 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -54,9 +54,11 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise { +async function storeEvent(event: DittoEvent, signal?: AbortSignal): Promise { if (NKinds.ephemeral(event.kind)) return; const store = await Storages.db(); const kysely = await DittoDB.getInstance(); await updateStats({ event, store, kysely }).catch(debug); await store.event(event, { signal }); - - return event; } /** Parse kind 0 metadata and track indexes in the database. */