From aec2cd3b9f6acec1808c987d52cd5cebd238d713 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Tue, 16 Jul 2024 16:26:22 -0300 Subject: [PATCH 01/53] chore: update dependencies --- deno.json | 2 +- deno.lock | 30 +++++++++++++++++++++++------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/deno.json b/deno.json index 7a4eb9d1..3affdef9 100644 --- a/deno.json +++ b/deno.json @@ -27,7 +27,7 @@ "@hono/hono": "jsr:@hono/hono@^4.4.6", "@isaacs/ttlcache": "npm:@isaacs/ttlcache@^1.4.1", "@noble/secp256k1": "npm:@noble/secp256k1@^2.0.0", - "@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.25.0", + "@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.26.0", "@scure/base": "npm:@scure/base@^1.1.6", "@sentry/deno": "https://deno.land/x/sentry@7.112.2/index.mjs", "@soapbox/kysely-deno-sqlite": "jsr:@soapbox/kysely-deno-sqlite@^2.1.0", diff --git a/deno.lock b/deno.lock index 233eee67..21898cf5 100644 --- a/deno.lock +++ b/deno.lock @@ -8,11 +8,11 @@ "jsr:@gleasonator/policy": "jsr:@gleasonator/policy@0.2.0", "jsr:@gleasonator/policy@0.2.0": "jsr:@gleasonator/policy@0.2.0", "jsr:@gleasonator/policy@0.4.0": "jsr:@gleasonator/policy@0.4.0", - "jsr:@hono/hono@^4.4.6": "jsr:@hono/hono@4.4.6", + "jsr:@hono/hono@^4.4.6": "jsr:@hono/hono@4.5.0", "jsr:@nostrify/nostrify@^0.22.1": "jsr:@nostrify/nostrify@0.22.5", "jsr:@nostrify/nostrify@^0.22.4": "jsr:@nostrify/nostrify@0.22.4", "jsr:@nostrify/nostrify@^0.22.5": "jsr:@nostrify/nostrify@0.22.5", - "jsr:@nostrify/nostrify@^0.25.0": "jsr:@nostrify/nostrify@0.25.0", + "jsr:@nostrify/nostrify@^0.26.0": "jsr:@nostrify/nostrify@0.26.0", "jsr:@soapbox/kysely-deno-sqlite@^2.1.0": "jsr:@soapbox/kysely-deno-sqlite@2.2.0", "jsr:@soapbox/stickynotes@^0.4.0": "jsr:@soapbox/stickynotes@0.4.0", "jsr:@std/assert@^0.217.0": "jsr:@std/assert@0.217.0", @@ -21,8 +21,9 @@ "jsr:@std/assert@^0.225.1": "jsr:@std/assert@0.225.3", "jsr:@std/bytes@^0.224.0": "jsr:@std/bytes@0.224.0", "jsr:@std/bytes@^1.0.0-rc.3": "jsr:@std/bytes@1.0.0", + "jsr:@std/bytes@^1.0.1-rc.3": "jsr:@std/bytes@1.0.2", "jsr:@std/crypto@^0.224.0": "jsr:@std/crypto@0.224.0", - "jsr:@std/dotenv@^0.224.0": "jsr:@std/dotenv@0.224.0", + "jsr:@std/dotenv@^0.224.0": "jsr:@std/dotenv@0.224.2", "jsr:@std/encoding@^0.221.0": "jsr:@std/encoding@0.221.0", "jsr:@std/encoding@^0.224.0": "jsr:@std/encoding@0.224.3", "jsr:@std/encoding@^0.224.1": "jsr:@std/encoding@0.224.3", @@ -30,7 +31,7 @@ "jsr:@std/fs@^0.221.0": "jsr:@std/fs@0.221.0", "jsr:@std/fs@^0.229.3": "jsr:@std/fs@0.229.3", "jsr:@std/internal@^1.0.0": "jsr:@std/internal@1.0.0", - "jsr:@std/io@^0.224": "jsr:@std/io@0.224.1", + "jsr:@std/io@^0.224": "jsr:@std/io@0.224.3", "jsr:@std/media-types@^0.224.1": "jsr:@std/media-types@0.224.1", "jsr:@std/path@0.217": "jsr:@std/path@0.217.0", "jsr:@std/path@^0.221.0": "jsr:@std/path@0.221.0", @@ -109,6 +110,9 @@ "@hono/hono@4.4.6": { "integrity": "aa557ca9930787ee86b9ca1730691f1ce1c379174c2cb244d5934db2b6314453" }, + "@hono/hono@4.5.0": { + "integrity": "4a410f7773ac4b5b0eb4520b26c7ab7795a271d57a9df7fa1953ded6b90ccaf7" + }, "@nostrify/nostrify@0.22.4": { "integrity": "1c8a7847e5773213044b491e85fd7cafae2ad194ce59da4d957d2b27c776b42d", "dependencies": [ @@ -138,8 +142,8 @@ "npm:zod@^3.23.8" ] }, - "@nostrify/nostrify@0.25.0": { - "integrity": "98f26f44e95ac87fc91b3f3809d38432e1a7f6aebf10380b2554b6f9526313c6", + "@nostrify/nostrify@0.26.0": { + "integrity": "8a4a49326f2d2bdf27e9ac6dc8c052bb37b31ae892350daa1df470070fddacd1", "dependencies": [ "jsr:@std/crypto@^0.224.0", "jsr:@std/encoding@^0.224.1", @@ -183,6 +187,9 @@ "@std/bytes@1.0.0": { "integrity": "9392e72af80adccaa1197912fa19990ed091cb98d5c9c4344b0c301b22d7c632" }, + "@std/bytes@1.0.2": { + "integrity": "fbdee322bbd8c599a6af186a1603b3355e59a5fb1baa139f8f4c3c9a1b3e3d57" + }, "@std/crypto@0.224.0": { "integrity": "154ef3ff08ef535562ef1a718718c5b2c5fc3808f0f9100daad69e829bfcdf2d", "dependencies": [ @@ -193,6 +200,9 @@ "@std/dotenv@0.224.0": { "integrity": "d9234cdf551507dcda60abb6c474289843741d8c07ee8ce540c60f5c1b220a1d" }, + "@std/dotenv@0.224.2": { + "integrity": "29081695357e4534696c9e986b2560be29c141ccf52daa32b6c20ff5b5c64ab9" + }, "@std/encoding@0.221.0": { "integrity": "d1dd76ef0dc5d14088411e6dc1dede53bf8308c95d1537df1214c97137208e45" }, @@ -227,6 +237,12 @@ "jsr:@std/bytes@^1.0.0-rc.3" ] }, + "@std/io@0.224.3": { + "integrity": "b402edeb99c6b3778d9ae3e9927bc9085b170b41e5a09bbb7064ab2ee394ae2f", + "dependencies": [ + "jsr:@std/bytes@^1.0.1-rc.3" + ] + }, "@std/media-types@0.224.1": { "integrity": "9e69a5daed37c5b5c6d3ce4731dc191f80e67f79bed392b0957d1d03b87f11e1" }, @@ -1732,7 +1748,7 @@ "jsr:@bradenmacdonald/s3-lite-client@^0.7.4", "jsr:@db/sqlite@^0.11.1", "jsr:@hono/hono@^4.4.6", - "jsr:@nostrify/nostrify@^0.25.0", + "jsr:@nostrify/nostrify@^0.26.0", "jsr:@soapbox/kysely-deno-sqlite@^2.1.0", "jsr:@soapbox/stickynotes@^0.4.0", "jsr:@std/assert@^0.225.1", From cc51917d61e21bae5f4f828eba02acccd9837df8 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Wed, 17 Jul 2024 10:37:54 -0300 Subject: [PATCH 02/53] fix(outbox): remove comment, use author pubkey --- src/utils/outbox.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/outbox.ts b/src/utils/outbox.ts index e5d7029c..72b83388 100644 --- a/src/utils/outbox.ts +++ b/src/utils/outbox.ts @@ -2,11 +2,11 @@ import { NStore } from '@nostrify/nostrify'; import { Conf } from '@/config.ts'; -export async function getRelays(store: NStore, _pubkey: string): Promise> { +export async function getRelays(store: NStore, pubkey: string): Promise> { const relays = new Set<`wss://${string}`>(); const events = await store.query([ - { kinds: [10002], authors: [/*pubkey, */ Conf.pubkey], limit: 2 }, + { kinds: [10002], authors: [pubkey, Conf.pubkey], limit: 2 }, ]); for (const event of events) { From 4d4273832a949c867ea28197bbb38032fa8f1984 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Wed, 17 Jul 2024 10:38:25 -0300 Subject: [PATCH 03/53] fixture: add kind 10002 --- fixtures/events/kind-10002-alex.json | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 fixtures/events/kind-10002-alex.json diff --git a/fixtures/events/kind-10002-alex.json b/fixtures/events/kind-10002-alex.json new file mode 100644 index 00000000..629e56cc --- /dev/null +++ b/fixtures/events/kind-10002-alex.json @@ -0,0 +1,34 @@ +{ + "kind": 10002, + "id": "68fc04e23b07219f153a10947663b9dd7b271acbc03b82200e364e35de3e0bdd", + "pubkey": "0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd", + "created_at": 1714969354, + "tags": [ + [ + "r", + "wss://gleasonator.dev/relay" + ], + [ + "r", + "wss://nosdrive.app/relay" + ], + [ + "r", + "wss://relay.mostr.pub/" + ], + [ + "r", + "wss://relay.primal.net/" + ], + [ + "r", + "wss://relay.snort.social/" + ], + [ + "r", + "wss://relay.damus.io/" + ] + ], + "content": "", + "sig": "cb7b1a75fe015d5c9481651379365bd5d098665b1bc7a453522177e2686eaa83581ec36f7a17429aad2541dad02c2c81023b81612f87f28fc57447fef1efab13" +} From 85a6089e36500de600143b12bae7c0e5d7a2a958 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Wed, 17 Jul 2024 10:37:04 -0300 Subject: [PATCH 04/53] test(outbox): coverage 100% --- src/utils/outbox.test.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/utils/outbox.test.ts diff --git a/src/utils/outbox.test.ts b/src/utils/outbox.test.ts new file mode 100644 index 00000000..62dac2d0 --- /dev/null +++ b/src/utils/outbox.test.ts @@ -0,0 +1,29 @@ +import { MockRelay } from '@nostrify/nostrify/test'; +import { eventFixture } from '@/test.ts'; +import { getRelays } from '@/utils/outbox.ts'; +import { assertEquals } from '@std/assert'; + +Deno.test('Get write relays - kind 10002', async () => { + const db = new MockRelay(); + + const relayListMetadata = await eventFixture('kind-10002-alex'); + + await db.event(relayListMetadata); + + const relays = await getRelays(db, relayListMetadata.pubkey); + + assertEquals(relays.size, 6); +}); + +Deno.test('Get write relays with invalid URL - kind 10002', async () => { + const db = new MockRelay(); + + const relayListMetadata = await eventFixture('kind-10002-alex'); + relayListMetadata.tags[0] = ['r', 'yolo']; + + await db.event(relayListMetadata); + + const relays = await getRelays(db, relayListMetadata.pubkey); + + assertEquals(relays.size, 5); +}); From f5ee5ea6a35de8eaf59c900c783e7a49da04589e Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Wed, 17 Jul 2024 10:53:31 -0300 Subject: [PATCH 05/53] test(RelayError): code coverage 100.00% --- src/RelayError.test.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/RelayError.test.ts diff --git a/src/RelayError.test.ts b/src/RelayError.test.ts new file mode 100644 index 00000000..742b799f --- /dev/null +++ b/src/RelayError.test.ts @@ -0,0 +1,23 @@ +import { assertThrows } from '@std/assert'; + +import { RelayError } from '@/RelayError.ts'; + +Deno.test('Construct a RelayError from the reason message', () => { + assertThrows( + () => { + throw RelayError.fromReason('duplicate: already exists'); + }, + RelayError, + 'duplicate: already exists', + ); +}); + +Deno.test('Throw a new RelayError if the OK message is false', () => { + assertThrows( + () => { + RelayError.assert(['OK', 'yolo', false, 'error: bla bla bla']); + }, + RelayError, + 'error: bla bla bla', + ); +}); From f4f0c5fb867c852485d3c6fe8f8e923a6a960ce2 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Wed, 17 Jul 2024 11:00:49 -0300 Subject: [PATCH 06/53] fix(storages): publish to write relays only as well --- src/storages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/storages.ts b/src/storages.ts index 4aaca1c2..418bdd5e 100644 --- a/src/storages.ts +++ b/src/storages.ts @@ -56,7 +56,7 @@ export class Storages { const tags = relayList?.tags ?? []; const activeRelays = tags.reduce((acc, [name, url, marker]) => { - if (name === 'r' && !marker) { + if (name === 'r' && (!marker || marker === 'write')) { acc.push(url); } return acc; From e01e0049e319e459fd7d90b83b77fb08966ed7f2 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Wed, 17 Jul 2024 11:40:08 -0300 Subject: [PATCH 07/53] fix: purify event before publishing to other relays --- src/utils/api.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/api.ts b/src/utils/api.ts index 355a7a12..069442c9 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -14,6 +14,7 @@ import { RelayError } from '@/RelayError.ts'; import { AdminSigner } from '@/signers/AdminSigner.ts'; import { Storages } from '@/storages.ts'; import { nostrNow } from '@/utils.ts'; +import { purifyEvent } from '@/storages/hydrate.ts'; const debug = Debug('ditto:api'); @@ -152,7 +153,7 @@ async function publishEvent(event: NostrEvent, c: AppContext): Promise Date: Wed, 17 Jul 2024 11:42:13 -0300 Subject: [PATCH 08/53] feat(storages): remove PoolStore, use NPool --- src/storages.ts | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/storages.ts b/src/storages.ts index 418bdd5e..e0039973 100644 --- a/src/storages.ts +++ b/src/storages.ts @@ -1,18 +1,17 @@ // deno-lint-ignore-file require-await -import { RelayPoolWorker } from 'nostr-relaypool'; - import { Conf } from '@/config.ts'; import { DittoDB } from '@/db/DittoDB.ts'; import { AdminStore } from '@/storages/AdminStore.ts'; import { EventsDB } from '@/storages/EventsDB.ts'; -import { PoolStore } from '@/storages/pool-store.ts'; import { SearchStore } from '@/storages/search-store.ts'; import { InternalRelay } from '@/storages/InternalRelay.ts'; +import { NPool, NRelay1 } from '@nostrify/nostrify'; +import { getRelays } from '@/utils/outbox.ts'; export class Storages { private static _db: Promise | undefined; private static _admin: Promise | undefined; - private static _client: Promise | undefined; + private static _client: Promise | undefined; private static _pubsub: Promise | undefined; private static _search: Promise | undefined; @@ -44,7 +43,7 @@ export class Storages { } /** Relay pool storage. */ - public static async client(): Promise { + public static async client(): Promise { if (!this._client) { this._client = (async () => { const db = await this.db(); @@ -64,22 +63,22 @@ export class Storages { console.log(`pool: connecting to ${activeRelays.length} relays.`); - const worker = new Worker('https://unpkg.com/nostr-relaypool2@0.6.34/lib/nostr-relaypool.worker.js', { - type: 'module', - }); + return new NPool({ + open(url) { + return new NRelay1(url); + }, + reqRouter: async (filters) => { + return new Map(activeRelays.map((relay) => { + return [relay, filters]; + })); + }, + eventRouter: async (event) => { + const relaySet = await getRelays(await Storages.db(), event.pubkey); + relaySet.delete(Conf.relay); - // @ts-ignore Wrong types. - const pool = new RelayPoolWorker(worker, activeRelays, { - autoReconnect: true, - // The pipeline verifies events. - skipVerification: true, - // The logging feature overwhelms the CPU and creates too many logs. - logErrorsAndNotices: false, - }); - - return new PoolStore({ - pool, - relays: activeRelays, + const relays = [...relaySet].slice(0, 4); + return relays; + }, }); })(); } From 31e5b897506024002fd1e33524d7d07c1f0f6c6c Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Wed, 17 Jul 2024 11:40:50 -0300 Subject: [PATCH 09/53] refactor: delete pool-store.ts file --- src/storages/pool-store.ts | 103 ------------------------------------- 1 file changed, 103 deletions(-) delete mode 100644 src/storages/pool-store.ts diff --git a/src/storages/pool-store.ts b/src/storages/pool-store.ts deleted file mode 100644 index 54565091..00000000 --- a/src/storages/pool-store.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { - NostrEvent, - NostrFilter, - NostrRelayCLOSED, - NostrRelayEOSE, - NostrRelayEVENT, - NRelay, - NSet, -} from '@nostrify/nostrify'; -import { Machina } from '@nostrify/nostrify/utils'; -import Debug from '@soapbox/stickynotes/debug'; -import { RelayPoolWorker } from 'nostr-relaypool'; -import { getFilterLimit, matchFilters } from 'nostr-tools'; - -import { Conf } from '@/config.ts'; -import { Storages } from '@/storages.ts'; -import { purifyEvent } from '@/storages/hydrate.ts'; -import { abortError } from '@/utils/abort.ts'; -import { getRelays } from '@/utils/outbox.ts'; - -interface PoolStoreOpts { - pool: InstanceType; - relays: WebSocket['url'][]; -} - -class PoolStore implements NRelay { - private debug = Debug('ditto:client'); - private pool: InstanceType; - private relays: WebSocket['url'][]; - - constructor(opts: PoolStoreOpts) { - this.pool = opts.pool; - this.relays = opts.relays; - } - - async event(event: NostrEvent, opts: { signal?: AbortSignal } = {}): Promise { - if (opts.signal?.aborted) return Promise.reject(abortError()); - - const relaySet = await getRelays(await Storages.db(), event.pubkey); - relaySet.delete(Conf.relay); - - const relays = [...relaySet].slice(0, 4); - - event = purifyEvent(event); - this.debug('EVENT', event, relays); - - this.pool.publish(event, relays); - return Promise.resolve(); - } - - async *req( - filters: NostrFilter[], - opts: { signal?: AbortSignal; limit?: number } = {}, - ): AsyncIterable { - this.debug('REQ', JSON.stringify(filters)); - - const uuid = crypto.randomUUID(); - const machina = new Machina(opts.signal); - - const unsub = this.pool.subscribe( - filters, - this.relays, - (event: NostrEvent | null) => { - if (event && matchFilters(filters, event)) { - machina.push(['EVENT', uuid, purifyEvent(event)]); - } - }, - undefined, - () => { - machina.push(['EOSE', uuid]); - }, - ); - - try { - for await (const msg of machina) { - yield msg; - } - } finally { - unsub(); - } - } - - async query(filters: NostrFilter[], opts: { signal?: AbortSignal; limit?: number } = {}): Promise { - const events = new NSet(); - - const limit = filters.reduce((result, filter) => result + getFilterLimit(filter), 0); - if (limit === 0) return []; - - for await (const msg of this.req(filters, opts)) { - if (msg[0] === 'EOSE') break; - if (msg[0] === 'EVENT') events.add(msg[2]); - if (msg[0] === 'CLOSED') throw new Error('Subscription closed'); - - if (events.size >= limit) { - break; - } - } - - return [...events]; - } -} - -export { PoolStore }; From 0f5c28deeb9558337510659e873a6f00026d690b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 17 Jul 2024 19:58:12 -0500 Subject: [PATCH 10/53] EventsDB: query tags by converting to ids filter --- src/storages/EventsDB.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/storages/EventsDB.ts b/src/storages/EventsDB.ts index abf076c7..656c17fc 100644 --- a/src/storages/EventsDB.ts +++ b/src/storages/EventsDB.ts @@ -287,10 +287,42 @@ class EventsDB implements NStore { // If this results in an empty kinds array, NDatabase will remove the filter before querying and return no results. filter.kinds = filter.kinds.filter((kind) => !NKinds.ephemeral(kind)); } + + // Convert tag filters into `ids` filter for performance reasons. + const tagEntries = Object.entries(filter).filter(EventsDB.isTagEntry); + if (tagEntries.length) { + const tagIds: string[] = await tagEntries + .map(([key, value]) => + this.kysely + .selectFrom('nostr_tags') + .select('event_id') + .distinct() + .where('name', '=', key.replace(/^#/, '')) + .where('value', 'in', value) + ) + .reduce((result, query) => result.intersect(query)) + .execute() + .then((rows) => rows.map(({ event_id }) => event_id)); + + if (tagIds.length) { + filter.ids = filter.ids ?? []; + filter.ids.push(...tagIds); + } + + for (const [key] of tagEntries) { + delete filter[key]; + } + } } return filters; } + + /** Check if the object entry is a valid tag filter. */ + private static isTagEntry(entry: [string, unknown]): entry is [`#${string}`, string[]] { + const [key, value] = entry; + return key.startsWith('#') && Array.isArray(value); + } } export { EventsDB }; From ff8d7ef9d4a65e5008b07c0e8e2123a574ce672c Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 17 Jul 2024 20:04:19 -0500 Subject: [PATCH 11/53] EventsDB: normalizeFilters before expanding tag queries --- src/storages/EventsDB.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/storages/EventsDB.ts b/src/storages/EventsDB.ts index 656c17fc..1265a98b 100644 --- a/src/storages/EventsDB.ts +++ b/src/storages/EventsDB.ts @@ -7,6 +7,7 @@ import { nip27 } from 'nostr-tools'; import { Conf } from '@/config.ts'; import { DittoTables } from '@/db/DittoTables.ts'; +import { normalizeFilters } from '@/filter.ts'; import { dbEventCounter, dbQueryCounter } from '@/metrics.ts'; import { RelayError } from '@/RelayError.ts'; import { purifyEvent } from '@/storages/hydrate.ts'; @@ -250,7 +251,7 @@ class EventsDB implements NStore { /** Converts filters to more performant, simpler filters that are better for SQLite. */ async expandFilters(filters: NostrFilter[]): Promise { - filters = structuredClone(filters); + filters = normalizeFilters(structuredClone(filters)); for (const filter of filters) { if (filter.search) { From 2d239fcaec3a7de1d485eaa81dd19f10229f68d1 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 18 Jul 2024 04:11:04 +0000 Subject: [PATCH 12/53] Revert "Merge branch 'tag-perf' into 'main'" This reverts merge request !425 --- src/storages/EventsDB.ts | 35 +---------------------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/src/storages/EventsDB.ts b/src/storages/EventsDB.ts index 1265a98b..abf076c7 100644 --- a/src/storages/EventsDB.ts +++ b/src/storages/EventsDB.ts @@ -7,7 +7,6 @@ import { nip27 } from 'nostr-tools'; import { Conf } from '@/config.ts'; import { DittoTables } from '@/db/DittoTables.ts'; -import { normalizeFilters } from '@/filter.ts'; import { dbEventCounter, dbQueryCounter } from '@/metrics.ts'; import { RelayError } from '@/RelayError.ts'; import { purifyEvent } from '@/storages/hydrate.ts'; @@ -251,7 +250,7 @@ class EventsDB implements NStore { /** Converts filters to more performant, simpler filters that are better for SQLite. */ async expandFilters(filters: NostrFilter[]): Promise { - filters = normalizeFilters(structuredClone(filters)); + filters = structuredClone(filters); for (const filter of filters) { if (filter.search) { @@ -288,42 +287,10 @@ class EventsDB implements NStore { // If this results in an empty kinds array, NDatabase will remove the filter before querying and return no results. filter.kinds = filter.kinds.filter((kind) => !NKinds.ephemeral(kind)); } - - // Convert tag filters into `ids` filter for performance reasons. - const tagEntries = Object.entries(filter).filter(EventsDB.isTagEntry); - if (tagEntries.length) { - const tagIds: string[] = await tagEntries - .map(([key, value]) => - this.kysely - .selectFrom('nostr_tags') - .select('event_id') - .distinct() - .where('name', '=', key.replace(/^#/, '')) - .where('value', 'in', value) - ) - .reduce((result, query) => result.intersect(query)) - .execute() - .then((rows) => rows.map(({ event_id }) => event_id)); - - if (tagIds.length) { - filter.ids = filter.ids ?? []; - filter.ids.push(...tagIds); - } - - for (const [key] of tagEntries) { - delete filter[key]; - } - } } return filters; } - - /** Check if the object entry is a valid tag filter. */ - private static isTagEntry(entry: [string, unknown]): entry is [`#${string}`, string[]] { - const [key, value] = entry; - return key.startsWith('#') && Array.isArray(value); - } } export { EventsDB }; From 7793db3e2c38397ca082a73b85dc010131e23b9a Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 17 Jul 2024 23:13:04 -0500 Subject: [PATCH 13/53] Upgrade Nostrify to v0.26.1 --- deno.json | 2 +- deno.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/deno.json b/deno.json index 3affdef9..9e48c1d7 100644 --- a/deno.json +++ b/deno.json @@ -27,7 +27,7 @@ "@hono/hono": "jsr:@hono/hono@^4.4.6", "@isaacs/ttlcache": "npm:@isaacs/ttlcache@^1.4.1", "@noble/secp256k1": "npm:@noble/secp256k1@^2.0.0", - "@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.26.0", + "@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.26.1", "@scure/base": "npm:@scure/base@^1.1.6", "@sentry/deno": "https://deno.land/x/sentry@7.112.2/index.mjs", "@soapbox/kysely-deno-sqlite": "jsr:@soapbox/kysely-deno-sqlite@^2.1.0", diff --git a/deno.lock b/deno.lock index 21898cf5..0f1ee264 100644 --- a/deno.lock +++ b/deno.lock @@ -12,7 +12,7 @@ "jsr:@nostrify/nostrify@^0.22.1": "jsr:@nostrify/nostrify@0.22.5", "jsr:@nostrify/nostrify@^0.22.4": "jsr:@nostrify/nostrify@0.22.4", "jsr:@nostrify/nostrify@^0.22.5": "jsr:@nostrify/nostrify@0.22.5", - "jsr:@nostrify/nostrify@^0.26.0": "jsr:@nostrify/nostrify@0.26.0", + "jsr:@nostrify/nostrify@^0.26.1": "jsr:@nostrify/nostrify@0.26.1", "jsr:@soapbox/kysely-deno-sqlite@^2.1.0": "jsr:@soapbox/kysely-deno-sqlite@2.2.0", "jsr:@soapbox/stickynotes@^0.4.0": "jsr:@soapbox/stickynotes@0.4.0", "jsr:@std/assert@^0.217.0": "jsr:@std/assert@0.217.0", @@ -142,8 +142,8 @@ "npm:zod@^3.23.8" ] }, - "@nostrify/nostrify@0.26.0": { - "integrity": "8a4a49326f2d2bdf27e9ac6dc8c052bb37b31ae892350daa1df470070fddacd1", + "@nostrify/nostrify@0.26.1": { + "integrity": "7079fc00fa46900808931e26ef9af509863014d4028d526652e2a7fbae89a6e6", "dependencies": [ "jsr:@std/crypto@^0.224.0", "jsr:@std/encoding@^0.224.1", @@ -1748,7 +1748,7 @@ "jsr:@bradenmacdonald/s3-lite-client@^0.7.4", "jsr:@db/sqlite@^0.11.1", "jsr:@hono/hono@^4.4.6", - "jsr:@nostrify/nostrify@^0.26.0", + "jsr:@nostrify/nostrify@^0.26.1", "jsr:@soapbox/kysely-deno-sqlite@^2.1.0", "jsr:@soapbox/stickynotes@^0.4.0", "jsr:@std/assert@^0.225.1", From 13f0d3f49e2be648e2efb4e72e7689a5f8836f7b Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 18 Jul 2024 22:01:16 -0500 Subject: [PATCH 14/53] Upgrade Nostrify to v0.26.2 --- deno.json | 2 +- deno.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/deno.json b/deno.json index 9e48c1d7..bca8aa1d 100644 --- a/deno.json +++ b/deno.json @@ -27,7 +27,7 @@ "@hono/hono": "jsr:@hono/hono@^4.4.6", "@isaacs/ttlcache": "npm:@isaacs/ttlcache@^1.4.1", "@noble/secp256k1": "npm:@noble/secp256k1@^2.0.0", - "@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.26.1", + "@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.26.2", "@scure/base": "npm:@scure/base@^1.1.6", "@sentry/deno": "https://deno.land/x/sentry@7.112.2/index.mjs", "@soapbox/kysely-deno-sqlite": "jsr:@soapbox/kysely-deno-sqlite@^2.1.0", diff --git a/deno.lock b/deno.lock index 0f1ee264..6c0bc580 100644 --- a/deno.lock +++ b/deno.lock @@ -12,7 +12,7 @@ "jsr:@nostrify/nostrify@^0.22.1": "jsr:@nostrify/nostrify@0.22.5", "jsr:@nostrify/nostrify@^0.22.4": "jsr:@nostrify/nostrify@0.22.4", "jsr:@nostrify/nostrify@^0.22.5": "jsr:@nostrify/nostrify@0.22.5", - "jsr:@nostrify/nostrify@^0.26.1": "jsr:@nostrify/nostrify@0.26.1", + "jsr:@nostrify/nostrify@^0.26.2": "jsr:@nostrify/nostrify@0.26.2", "jsr:@soapbox/kysely-deno-sqlite@^2.1.0": "jsr:@soapbox/kysely-deno-sqlite@2.2.0", "jsr:@soapbox/stickynotes@^0.4.0": "jsr:@soapbox/stickynotes@0.4.0", "jsr:@std/assert@^0.217.0": "jsr:@std/assert@0.217.0", @@ -142,8 +142,8 @@ "npm:zod@^3.23.8" ] }, - "@nostrify/nostrify@0.26.1": { - "integrity": "7079fc00fa46900808931e26ef9af509863014d4028d526652e2a7fbae89a6e6", + "@nostrify/nostrify@0.26.2": { + "integrity": "41a2aa076a92ea60dcf188c5be23c0809f73e753996c6456768c02d4fd6cfede", "dependencies": [ "jsr:@std/crypto@^0.224.0", "jsr:@std/encoding@^0.224.1", @@ -1748,7 +1748,7 @@ "jsr:@bradenmacdonald/s3-lite-client@^0.7.4", "jsr:@db/sqlite@^0.11.1", "jsr:@hono/hono@^4.4.6", - "jsr:@nostrify/nostrify@^0.26.1", + "jsr:@nostrify/nostrify@^0.26.2", "jsr:@soapbox/kysely-deno-sqlite@^2.1.0", "jsr:@soapbox/stickynotes@^0.4.0", "jsr:@std/assert@^0.225.1", From 8ec5feae13191878e5abff5001d8341bd8f53c7a Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Thu, 18 Jul 2024 22:48:11 -0500 Subject: [PATCH 15/53] Sort events by id after created_at --- deno.json | 2 +- deno.lock | 8 +++--- src/db/migrations/028_stable_sort.ts | 39 ++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 src/db/migrations/028_stable_sort.ts diff --git a/deno.json b/deno.json index bca8aa1d..51f24cf0 100644 --- a/deno.json +++ b/deno.json @@ -27,7 +27,7 @@ "@hono/hono": "jsr:@hono/hono@^4.4.6", "@isaacs/ttlcache": "npm:@isaacs/ttlcache@^1.4.1", "@noble/secp256k1": "npm:@noble/secp256k1@^2.0.0", - "@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.26.2", + "@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.26.3", "@scure/base": "npm:@scure/base@^1.1.6", "@sentry/deno": "https://deno.land/x/sentry@7.112.2/index.mjs", "@soapbox/kysely-deno-sqlite": "jsr:@soapbox/kysely-deno-sqlite@^2.1.0", diff --git a/deno.lock b/deno.lock index 6c0bc580..104ea5c9 100644 --- a/deno.lock +++ b/deno.lock @@ -12,7 +12,7 @@ "jsr:@nostrify/nostrify@^0.22.1": "jsr:@nostrify/nostrify@0.22.5", "jsr:@nostrify/nostrify@^0.22.4": "jsr:@nostrify/nostrify@0.22.4", "jsr:@nostrify/nostrify@^0.22.5": "jsr:@nostrify/nostrify@0.22.5", - "jsr:@nostrify/nostrify@^0.26.2": "jsr:@nostrify/nostrify@0.26.2", + "jsr:@nostrify/nostrify@^0.26.3": "jsr:@nostrify/nostrify@0.26.3", "jsr:@soapbox/kysely-deno-sqlite@^2.1.0": "jsr:@soapbox/kysely-deno-sqlite@2.2.0", "jsr:@soapbox/stickynotes@^0.4.0": "jsr:@soapbox/stickynotes@0.4.0", "jsr:@std/assert@^0.217.0": "jsr:@std/assert@0.217.0", @@ -142,8 +142,8 @@ "npm:zod@^3.23.8" ] }, - "@nostrify/nostrify@0.26.2": { - "integrity": "41a2aa076a92ea60dcf188c5be23c0809f73e753996c6456768c02d4fd6cfede", + "@nostrify/nostrify@0.26.3": { + "integrity": "3e13e30f4fa3f76dcbcf9178630a9b2871186eb1d226d66234c0cdfd4841f548", "dependencies": [ "jsr:@std/crypto@^0.224.0", "jsr:@std/encoding@^0.224.1", @@ -1748,7 +1748,7 @@ "jsr:@bradenmacdonald/s3-lite-client@^0.7.4", "jsr:@db/sqlite@^0.11.1", "jsr:@hono/hono@^4.4.6", - "jsr:@nostrify/nostrify@^0.26.2", + "jsr:@nostrify/nostrify@^0.26.3", "jsr:@soapbox/kysely-deno-sqlite@^2.1.0", "jsr:@soapbox/stickynotes@^0.4.0", "jsr:@std/assert@^0.225.1", diff --git a/src/db/migrations/028_stable_sort.ts b/src/db/migrations/028_stable_sort.ts new file mode 100644 index 00000000..191f32ca --- /dev/null +++ b/src/db/migrations/028_stable_sort.ts @@ -0,0 +1,39 @@ +import { Kysely } from 'kysely'; + +export async function up(db: Kysely): Promise { + await db.schema + .createIndex('nostr_events_created_at_kind') + .on('nostr_events') + .ifNotExists() + .columns(['created_at desc', 'id asc', 'kind']) + .execute(); + + await db.schema + .createIndex('nostr_events_kind_pubkey_created_at') + .on('nostr_events') + .ifNotExists() + .columns(['kind', 'pubkey', 'created_at desc', 'id asc']) + .execute(); + + await db.schema.dropIndex('idx_events_created_at_kind').execute(); + await db.schema.dropIndex('idx_events_kind_pubkey_created_at').execute(); +} + +export async function down(db: Kysely): Promise { + await db.schema.dropIndex('nostr_events_created_at_kind').execute(); + await db.schema.dropIndex('nostr_events_kind_pubkey_created_at').execute(); + + await db.schema + .createIndex('idx_events_created_at_kind') + .on('nostr_events') + .ifNotExists() + .columns(['created_at desc', 'kind']) + .execute(); + + await db.schema + .createIndex('idx_events_kind_pubkey_created_at') + .on('nostr_events') + .ifNotExists() + .columns(['kind', 'pubkey', 'created_at desc']) + .execute(); +} From dcec2ecdd0f59b75fa87aef5fe2dabc1d6713ddb Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 19 Jul 2024 13:19:04 -0300 Subject: [PATCH 16/53] feat: create isNumberFrom1To100 function --- src/utils.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index e9213ed1..96ae74c4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,6 @@ import { NostrEvent, NSchema as n } from '@nostrify/nostrify'; import { nip19 } from 'nostr-tools'; -import { z } from 'zod'; +import { boolean, z } from 'zod'; /** Get the current time in Nostr format. */ const nostrNow = (): number => Math.floor(Date.now() / 1000); @@ -93,12 +93,17 @@ function isURL(value: unknown): boolean { return z.string().url().safeParse(value).success; } +function isNumberFrom1To100(value: unknown): boolean { + return z.coerce.number().int().gte(1).lte(100).safeParse(value).success; +} + export { bech32ToPubkey, dedupeEvents, eventAge, findTag, isNostrId, + isNumberFrom1To100, isURL, type Nip05, nostrDate, From 11809637ee835fdb8b2c641fbd34cd77ce278eb4 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 19 Jul 2024 13:21:17 -0300 Subject: [PATCH 17/53] test(utils.ts): isNumberFrom1To100 function --- src/utils.test.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/utils.test.ts diff --git a/src/utils.test.ts b/src/utils.test.ts new file mode 100644 index 00000000..a48d0832 --- /dev/null +++ b/src/utils.test.ts @@ -0,0 +1,19 @@ +import { isNumberFrom1To100 } from '@/utils.ts'; +import { assertEquals } from 'https://deno.land/std@0.132.0/testing/asserts.ts'; + +Deno.test('Value is any number from 1 to 100', () => { + assertEquals(isNumberFrom1To100('latvia'), false); + assertEquals(isNumberFrom1To100(1.5), false); + assertEquals(isNumberFrom1To100(Infinity), false); + assertEquals(isNumberFrom1To100('Infinity'), false); + assertEquals(isNumberFrom1To100('0'), false); + assertEquals(isNumberFrom1To100(0), false); + assertEquals(isNumberFrom1To100(-1), false); + assertEquals(isNumberFrom1To100('-10'), false); + + for (let i = 1; i < 100; i++) { + assertEquals(isNumberFrom1To100(String(i)), true); + } + + assertEquals(isNumberFrom1To100('1e1'), true); +}); From c2225da8dd33d5e6d871705274c8510c66685cdd Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 19 Jul 2024 13:34:45 -0300 Subject: [PATCH 18/53] test(utls.ts): add more cases, isNumberFrom1To100 function --- src/utils.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils.test.ts b/src/utils.test.ts index a48d0832..8adf2935 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -10,6 +10,8 @@ Deno.test('Value is any number from 1 to 100', () => { assertEquals(isNumberFrom1To100(0), false); assertEquals(isNumberFrom1To100(-1), false); assertEquals(isNumberFrom1To100('-10'), false); + assertEquals(isNumberFrom1To100([]), false); + assertEquals(isNumberFrom1To100(undefined), false); for (let i = 1; i < 100; i++) { assertEquals(isNumberFrom1To100(String(i)), true); From cc6441d2397d45c963518e9ca3d9603f50cc1622 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 19 Jul 2024 15:22:35 -0300 Subject: [PATCH 19/53] refactor(utils.ts): remove unused imports --- src/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.ts b/src/utils.ts index 96ae74c4..a1c3b1d1 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,6 @@ import { NostrEvent, NSchema as n } from '@nostrify/nostrify'; import { nip19 } from 'nostr-tools'; -import { boolean, z } from 'zod'; +import { z } from 'zod'; /** Get the current time in Nostr format. */ const nostrNow = (): number => Math.floor(Date.now() / 1000); From c17db5844889801970eb6ade964f516f8c33e9d6 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 19 Jul 2024 16:26:50 -0300 Subject: [PATCH 20/53] feat(utils.ts): create isObjectEmpty function --- src/utils.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/utils.ts b/src/utils.ts index a1c3b1d1..de826ad6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -97,6 +97,13 @@ function isNumberFrom1To100(value: unknown): boolean { return z.coerce.number().int().gte(1).lte(100).safeParse(value).success; } +function isObjectEmpty(obj: object): boolean { + for (const prop in obj) { + if (Object.hasOwn(obj, prop)) return false; + } + return true; +} + export { bech32ToPubkey, dedupeEvents, @@ -104,6 +111,7 @@ export { findTag, isNostrId, isNumberFrom1To100, + isObjectEmpty, isURL, type Nip05, nostrDate, From 0c9e3e2b47913580bee76cbe5eed4b38b193d193 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 19 Jul 2024 16:27:10 -0300 Subject: [PATCH 21/53] test(utils.ts): isObjectEmpty function --- src/utils.test.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/utils.test.ts b/src/utils.test.ts index 8adf2935..1023c633 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -1,5 +1,5 @@ -import { isNumberFrom1To100 } from '@/utils.ts'; -import { assertEquals } from 'https://deno.land/std@0.132.0/testing/asserts.ts'; +import { isNumberFrom1To100, isObjectEmpty } from '@/utils.ts'; +import { assertEquals } from '@std/assert'; Deno.test('Value is any number from 1 to 100', () => { assertEquals(isNumberFrom1To100('latvia'), false); @@ -19,3 +19,11 @@ Deno.test('Value is any number from 1 to 100', () => { assertEquals(isNumberFrom1To100('1e1'), true); }); + +Deno.test('Object is empty', () => { + assertEquals(isObjectEmpty([1]), false); + assertEquals(isObjectEmpty({ 'yolo': 'no yolo' }), false); + + assertEquals(isObjectEmpty([]), true); + assertEquals(isObjectEmpty({}), true); +}); From 76a591ab6db38500d4ffded99938e2fdc8a0897b Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 19 Jul 2024 17:20:31 -0300 Subject: [PATCH 22/53] feat: create getZapSplits function --- src/utils/zap_split.ts | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/utils/zap_split.ts diff --git a/src/utils/zap_split.ts b/src/utils/zap_split.ts new file mode 100644 index 00000000..68aabc8a --- /dev/null +++ b/src/utils/zap_split.ts @@ -0,0 +1,35 @@ +import { NSchema as n, NStore } from '@nostrify/nostrify'; +import { isNumberFrom1To100 } from '@/utils.ts'; + +type Pubkey = string; +type ExtraMessage = string; +/** Number from 1 to 100, stringified. */ +type splitPercentages = string; + +type DittoZapSplits = { + [key: Pubkey]: [splitPercentages, ExtraMessage]; +}; + +/** Gets zap splits from NIP-78 in DittoZapSplits format. */ +export async function getZapSplits(store: NStore, pubkey: string): Promise { + const zapSplits: DittoZapSplits = {}; + + const [event] = await store.query([{ + authors: [pubkey], + kinds: [30078], + '#d': ['pub.ditto.zapSplits'], + limit: 1, + }]); + if (!event) return {}; + + for (const tag of event.tags) { + if ( + tag[0] === 'p' && n.id().safeParse(tag[1]).success && + isNumberFrom1To100(tag[2]) + ) { + zapSplits[tag[1]] = [tag[2], tag[3] ?? '']; + } + } + + return zapSplits; +} From 3bc3e7675d5d0d9f5748fe62ef409e7bb6b02b3b Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 19 Jul 2024 17:35:34 -0300 Subject: [PATCH 23/53] test: getZapSplits function --- src/utils/zap_split.test.ts | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/utils/zap_split.test.ts diff --git a/src/utils/zap_split.test.ts b/src/utils/zap_split.test.ts new file mode 100644 index 00000000..a439e3aa --- /dev/null +++ b/src/utils/zap_split.test.ts @@ -0,0 +1,36 @@ +import { assertEquals } from '@std/assert'; +import { generateSecretKey, getPublicKey } from 'nostr-tools'; + +import { genEvent } from '@/test.ts'; +import { getZapSplits } from '@/utils/zap_split.ts'; +import { getTestDB } from '@/test.ts'; + +Deno.test('Get zap splits in DittoZapSplits format', async () => { + const { store } = await getTestDB(); + + const sk = generateSecretKey(); + const pubkey = getPublicKey(sk); + + const event = genEvent({ + kind: 30078, + tags: [ + ['d', 'pub.ditto.zapSplits'], + ['p', '47259076c85f9240e852420d7213c95e95102f1de929fb60f33a2c32570c98c4', '2', 'Patrick developer'], + ['p', '0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd', '3', 'Alex creator of Ditto'], + ], + }, sk); + await store.event(event); + + const eventFromDb = await store.query([{ kinds: [30078], authors: [pubkey] }]); + + assertEquals(eventFromDb.length, 1); + + const zapSplits = await getZapSplits(store, pubkey); + + assertEquals(zapSplits, { + '0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd': ['3', 'Alex creator of Ditto'], + '47259076c85f9240e852420d7213c95e95102f1de929fb60f33a2c32570c98c4': ['2', 'Patrick developer'], + }); + + assertEquals(await getZapSplits(store, 'garbage'), {}); +}); From beee0e76e78f3c0548c8b42bc7227fc516356a48 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 19 Jul 2024 19:19:04 -0300 Subject: [PATCH 24/53] refactor: allow to return undefined in getZapSplits function --- src/utils/zap_split.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/zap_split.ts b/src/utils/zap_split.ts index 68aabc8a..38eecba4 100644 --- a/src/utils/zap_split.ts +++ b/src/utils/zap_split.ts @@ -6,12 +6,12 @@ type ExtraMessage = string; /** Number from 1 to 100, stringified. */ type splitPercentages = string; -type DittoZapSplits = { +export type DittoZapSplits = { [key: Pubkey]: [splitPercentages, ExtraMessage]; }; /** Gets zap splits from NIP-78 in DittoZapSplits format. */ -export async function getZapSplits(store: NStore, pubkey: string): Promise { +export async function getZapSplits(store: NStore, pubkey: string): Promise { const zapSplits: DittoZapSplits = {}; const [event] = await store.query([{ @@ -20,7 +20,7 @@ export async function getZapSplits(store: NStore, pubkey: string): Promise Date: Fri, 19 Jul 2024 19:19:35 -0300 Subject: [PATCH 25/53] test: update so getZapSplits function returns undefined --- src/utils/zap_split.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/zap_split.test.ts b/src/utils/zap_split.test.ts index a439e3aa..9678aba9 100644 --- a/src/utils/zap_split.test.ts +++ b/src/utils/zap_split.test.ts @@ -32,5 +32,5 @@ Deno.test('Get zap splits in DittoZapSplits format', async () => { '47259076c85f9240e852420d7213c95e95102f1de929fb60f33a2c32570c98c4': ['2', 'Patrick developer'], }); - assertEquals(await getZapSplits(store, 'garbage'), {}); + assertEquals(await getZapSplits(store, 'garbage'), undefined); }); From 1f9896bdbf951e552b6eff09efc40e4979af6cae Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 19 Jul 2024 19:30:31 -0300 Subject: [PATCH 26/53] test: getZapSplits function return empty object --- src/utils/zap_split.test.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/utils/zap_split.test.ts b/src/utils/zap_split.test.ts index 9678aba9..6ccf1ada 100644 --- a/src/utils/zap_split.test.ts +++ b/src/utils/zap_split.test.ts @@ -34,3 +34,27 @@ Deno.test('Get zap splits in DittoZapSplits format', async () => { assertEquals(await getZapSplits(store, 'garbage'), undefined); }); + +Deno.test('Zap split is empty', async () => { + const { store } = await getTestDB(); + + const sk = generateSecretKey(); + const pubkey = getPublicKey(sk); + + const event = genEvent({ + kind: 30078, + tags: [ + ['d', 'pub.ditto.zapSplits'], + ['p', 'baka'], + ], + }, sk); + await store.event(event); + + const eventFromDb = await store.query([{ kinds: [30078], authors: [pubkey] }]); + + assertEquals(eventFromDb.length, 1); + + const zapSplits = await getZapSplits(store, pubkey); + + assertEquals(zapSplits, {}); +}); From 4304715c1ee89a8ed71e240f8984393f30095455 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sat, 20 Jul 2024 13:38:56 -0500 Subject: [PATCH 27/53] pool: skip event verification --- src/storages.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/storages.ts b/src/storages.ts index e0039973..303a6eb7 100644 --- a/src/storages.ts +++ b/src/storages.ts @@ -65,7 +65,10 @@ export class Storages { return new NPool({ open(url) { - return new NRelay1(url); + return new NRelay1(url, { + // Skip event verification (it's done in the pipeline). + verifyEvent: () => true, + }); }, reqRouter: async (filters) => { return new Map(activeRelays.map((relay) => { From b3dba0f7932627b38649a5ab4527afbd225249be Mon Sep 17 00:00:00 2001 From: Siddharth Singh Date: Sun, 21 Jul 2024 03:19:05 +0530 Subject: [PATCH 28/53] update pinned version of postgres.js --- deno.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deno.json b/deno.json index 51f24cf0..190ce017 100644 --- a/deno.json +++ b/deno.json @@ -51,7 +51,7 @@ "iso-639-1": "npm:iso-639-1@2.1.15", "isomorphic-dompurify": "npm:isomorphic-dompurify@^2.11.0", "kysely": "npm:kysely@^0.27.3", - "postgres": "https://raw.githubusercontent.com/xyzshantaram/postgres.js/17469a9e5f025d112206c583a29275e93dfc1431/deno/mod.js", + "postgres": "https://raw.githubusercontent.com/xyzshantaram/postgres.js/8a9bbce88b3f6425ecaacd99a80372338b157a53/deno/mod.js", "kysely-postgres-js": "npm:kysely-postgres-js@2.0.0", "light-bolt11-decoder": "npm:light-bolt11-decoder", "linkify-plugin-hashtag": "npm:linkify-plugin-hashtag@^4.1.1", From 2acfcb3951fd1d23441c134e136db9b6c56c0215 Mon Sep 17 00:00:00 2001 From: Siddharth Singh Date: Sun, 21 Jul 2024 03:23:15 +0530 Subject: [PATCH 29/53] update lock file --- deno.lock | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/deno.lock b/deno.lock index 104ea5c9..5448018b 100644 --- a/deno.lock +++ b/deno.lock @@ -1729,6 +1729,18 @@ "https://raw.githubusercontent.com/xyzshantaram/postgres.js/17469a9e5f025d112206c583a29275e93dfc1431/deno/src/result.js": "001ff5e0c8d634674f483d07fbcd620a797e3101f842d6c20ca3ace936260465", "https://raw.githubusercontent.com/xyzshantaram/postgres.js/17469a9e5f025d112206c583a29275e93dfc1431/deno/src/subscribe.js": "9e4d0c3e573a6048e77ee2f15abbd5bcd17da9ca85a78c914553472c6d6c169b", "https://raw.githubusercontent.com/xyzshantaram/postgres.js/17469a9e5f025d112206c583a29275e93dfc1431/deno/src/types.js": "471f4a6c35412aa202a7c177c0a7e5a7c3bd225f01bbde67c947894c1b8bf6ed", + "https://raw.githubusercontent.com/xyzshantaram/postgres.js/8a9bbce88b3f6425ecaacd99a80372338b157a53/deno/mod.js": "cb68f17d6d90df318934deccdb469d740be0888e7a597a9e7eea7100ce36a252", + "https://raw.githubusercontent.com/xyzshantaram/postgres.js/8a9bbce88b3f6425ecaacd99a80372338b157a53/deno/polyfills.js": "318eb01f2b4cc33a46c59f3ddc11f22a56d6b1db8b7719b2ad7decee63a5bd47", + "https://raw.githubusercontent.com/xyzshantaram/postgres.js/8a9bbce88b3f6425ecaacd99a80372338b157a53/deno/src/bytes.js": "f2de43bdc8fa5dc4b169f2c70d5d8b053a3dea8f85ef011d7b27dec69e14ebb7", + "https://raw.githubusercontent.com/xyzshantaram/postgres.js/8a9bbce88b3f6425ecaacd99a80372338b157a53/deno/src/connection.js": "c63d53a0f35a7eb2670befef551f23fe914bbe9f0590de974e3e210c50527a29", + "https://raw.githubusercontent.com/xyzshantaram/postgres.js/8a9bbce88b3f6425ecaacd99a80372338b157a53/deno/src/errors.js": "85cfbed9a5ab0db41ab8e97b806c881af29807dfe99bc656fdf1a18c1c13b6c6", + "https://raw.githubusercontent.com/xyzshantaram/postgres.js/8a9bbce88b3f6425ecaacd99a80372338b157a53/deno/src/index.js": "4e8b09c7d0ce6e9eea386f59337867266498d5bb60ccd567d0bea5da03f6094d", + "https://raw.githubusercontent.com/xyzshantaram/postgres.js/8a9bbce88b3f6425ecaacd99a80372338b157a53/deno/src/large.js": "f3e770cdb7cc695f7b50687b4c6c4b7252129515486ec8def98b7582ee7c54ef", + "https://raw.githubusercontent.com/xyzshantaram/postgres.js/8a9bbce88b3f6425ecaacd99a80372338b157a53/deno/src/query.js": "67c45a5151032aa46b587abc15381fe4efd97c696e5c1b53082b8161309c4ee2", + "https://raw.githubusercontent.com/xyzshantaram/postgres.js/8a9bbce88b3f6425ecaacd99a80372338b157a53/deno/src/queue.js": "709624843223ea842bf095f6934080f19f1a059a51cbbf82e9827f3bb1bf2ca7", + "https://raw.githubusercontent.com/xyzshantaram/postgres.js/8a9bbce88b3f6425ecaacd99a80372338b157a53/deno/src/result.js": "001ff5e0c8d634674f483d07fbcd620a797e3101f842d6c20ca3ace936260465", + "https://raw.githubusercontent.com/xyzshantaram/postgres.js/8a9bbce88b3f6425ecaacd99a80372338b157a53/deno/src/subscribe.js": "9e4d0c3e573a6048e77ee2f15abbd5bcd17da9ca85a78c914553472c6d6c169b", + "https://raw.githubusercontent.com/xyzshantaram/postgres.js/8a9bbce88b3f6425ecaacd99a80372338b157a53/deno/src/types.js": "471f4a6c35412aa202a7c177c0a7e5a7c3bd225f01bbde67c947894c1b8bf6ed", "https://raw.githubusercontent.com/xyzshantaram/postgres.js/master/deno/mod.js": "cb68f17d6d90df318934deccdb469d740be0888e7a597a9e7eea7100ce36a252", "https://raw.githubusercontent.com/xyzshantaram/postgres.js/master/deno/polyfills.js": "318eb01f2b4cc33a46c59f3ddc11f22a56d6b1db8b7719b2ad7decee63a5bd47", "https://raw.githubusercontent.com/xyzshantaram/postgres.js/master/deno/src/bytes.js": "f2de43bdc8fa5dc4b169f2c70d5d8b053a3dea8f85ef011d7b27dec69e14ebb7", From 2fe9b9f98d7e1a103179365905a5828ca936959f Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sun, 21 Jul 2024 19:22:15 -0300 Subject: [PATCH 30/53] feat: get zap splits and return it in api/v1/instance endpoint --- src/controllers/api/instance.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/controllers/api/instance.ts b/src/controllers/api/instance.ts index faca9c9f..1d97b1e9 100644 --- a/src/controllers/api/instance.ts +++ b/src/controllers/api/instance.ts @@ -4,12 +4,29 @@ import { AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; import { Storages } from '@/storages.ts'; import { getInstanceMetadata } from '@/utils/instance.ts'; +import { DittoZapSplits, getZapSplits } from '@/utils/zap_split.ts'; +import { createAdminEvent } from '@/utils/api.ts'; const version = `3.0.0 (compatible; Ditto ${denoJson.version})`; const instanceV1Controller: AppController = async (c) => { const { host, protocol } = Conf.url; const meta = await getInstanceMetadata(await Storages.db(), c.req.raw.signal); + const store = c.get('store'); + + let zap_split: DittoZapSplits | undefined = await getZapSplits(store, Conf.pubkey); + if (!zap_split) { + const officialDittoAccountPubkey = '781a1527055f74c1f70230f10384609b34548f8ab6a0a6caa74025827f9fdae5'; + const officialDittoAccountMsg = 'Official Ditto Account'; + await createAdminEvent({ + kind: 30078, + tags: [ + ['d', 'pub.ditto.zapSplits'], + ['p', officialDittoAccountPubkey, '5', officialDittoAccountMsg], + ], + }, c); + zap_split = { [officialDittoAccountPubkey]: ['5', officialDittoAccountMsg] }; + } /** Protocol to use for WebSocket URLs, depending on the protocol of the `LOCAL_DOMAIN`. */ const wsProtocol = protocol === 'http:' ? 'ws:' : 'wss:'; @@ -68,6 +85,9 @@ const instanceV1Controller: AppController = async (c) => { }, }, rules: [], + ditto: { + zap_split, + }, }); }; From 2e66af26db2d5bed2cce39593afebc2b7f0b6111 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sun, 21 Jul 2024 19:21:33 -0300 Subject: [PATCH 31/53] feat: create updateZapSplitsController --- src/app.ts | 4 ++++ src/controllers/api/ditto.ts | 41 ++++++++++++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/app.ts b/src/app.ts index 1cb3746b..7538fea2 100644 --- a/src/app.ts +++ b/src/app.ts @@ -44,6 +44,7 @@ import { adminSetRelaysController, nameRequestController, nameRequestsController, + updateZapSplitsController, } from '@/controllers/api/ditto.ts'; import { emptyArrayController, emptyObjectController, notImplementedController } from '@/controllers/api/fallback.ts'; import { @@ -270,6 +271,9 @@ app.put('/api/v1/admin/ditto/relays', requireRole('admin'), adminSetRelaysContro app.post('/api/v1/ditto/names', requireSigner, nameRequestController); app.get('/api/v1/ditto/names', requireSigner, nameRequestsController); +app.put('/api/v1/admin/ditto/zap_splits', requireRole('admin'), updateZapSplitsController); +//app.delete('/api/v1/admin/ditto/zap_splits', requireRole('admin'), deleteZapSplitsController); + app.post('/api/v1/ditto/zap', requireSigner, zapController); app.get('/api/v1/ditto/statuses/:id{[0-9a-f]{64}}/zapped_by', zappedByController); diff --git a/src/controllers/api/ditto.ts b/src/controllers/api/ditto.ts index 841eb861..c3381a66 100644 --- a/src/controllers/api/ditto.ts +++ b/src/controllers/api/ditto.ts @@ -1,14 +1,17 @@ -import { NostrEvent, NostrFilter } from '@nostrify/nostrify'; +import { NostrEvent, NostrFilter, NSchema as n } from '@nostrify/nostrify'; import { z } from 'zod'; import { AppController } from '@/app.ts'; -import { Conf } from '@/config.ts'; -import { booleanParamSchema } from '@/schema.ts'; import { AdminSigner } from '@/signers/AdminSigner.ts'; +import { booleanParamSchema } from '@/schema.ts'; +import { Conf } from '@/config.ts'; import { Storages } from '@/storages.ts'; import { hydrateEvents } from '@/storages/hydrate.ts'; -import { createEvent, paginated, paginationSchema } from '@/utils/api.ts'; +import { createEvent, paginated, paginationSchema, parseBody } from '@/utils/api.ts'; import { renderNameRequest } from '@/views/ditto.ts'; +import { getZapSplits } from '@/utils/zap_split.ts'; +import { updateListAdminEvent } from '@/utils/api.ts'; +import { addTag } from '@/utils/tags.ts'; const markerSchema = z.enum(['read', 'write']); @@ -148,3 +151,33 @@ export const nameRequestsController: AppController = async (c) => { return paginated(c, orig, nameRequests); }; + +const zapSplitSchema = z.array(z.tuple([n.id(), z.number().int().min(1).max(100), z.string().max(500)])).min(1); + +export const updateZapSplitsController: AppController = async (c) => { + const body = await parseBody(c.req.raw); + const result = zapSplitSchema.safeParse(body); + const store = c.get('store'); + + if (!result.success) { + return c.json({ error: result.error }, 400); + } + + const zap_split = await getZapSplits(store, Conf.pubkey); + if (!zap_split) { + return c.json({ error: 'Zap split not activated, visit `/api/v1/instance` to activate it.' }, 404); + } + + const { data } = result; + + await updateListAdminEvent( + { kinds: [30078], authors: [Conf.pubkey], '#d': ['pub.ditto.zapSplits'], limit: 1 }, + (tags) => + data.reduce((accumulator, currentValue) => { + return addTag(accumulator, ['p', currentValue[0], String(currentValue[1]), currentValue[2]]); + }, tags), + c, + ); + + return c.json(200); +}; From 449a3497ba5aac442b2c335ddd46c86f12260e06 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sun, 21 Jul 2024 19:40:55 -0300 Subject: [PATCH 32/53] feat: create deleteZapSplitsController --- src/app.ts | 3 ++- src/controllers/api/ditto.ts | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/app.ts b/src/app.ts index 7538fea2..f9dad138 100644 --- a/src/app.ts +++ b/src/app.ts @@ -42,6 +42,7 @@ import { bookmarksController } from '@/controllers/api/bookmarks.ts'; import { adminRelaysController, adminSetRelaysController, + deleteZapSplitsController, nameRequestController, nameRequestsController, updateZapSplitsController, @@ -272,7 +273,7 @@ app.post('/api/v1/ditto/names', requireSigner, nameRequestController); app.get('/api/v1/ditto/names', requireSigner, nameRequestsController); app.put('/api/v1/admin/ditto/zap_splits', requireRole('admin'), updateZapSplitsController); -//app.delete('/api/v1/admin/ditto/zap_splits', requireRole('admin'), deleteZapSplitsController); +app.delete('/api/v1/admin/ditto/zap_splits', requireRole('admin'), deleteZapSplitsController); app.post('/api/v1/ditto/zap', requireSigner, zapController); app.get('/api/v1/ditto/statuses/:id{[0-9a-f]{64}}/zapped_by', zappedByController); diff --git a/src/controllers/api/ditto.ts b/src/controllers/api/ditto.ts index c3381a66..aebf3427 100644 --- a/src/controllers/api/ditto.ts +++ b/src/controllers/api/ditto.ts @@ -12,6 +12,7 @@ import { renderNameRequest } from '@/views/ditto.ts'; import { getZapSplits } from '@/utils/zap_split.ts'; import { updateListAdminEvent } from '@/utils/api.ts'; import { addTag } from '@/utils/tags.ts'; +import { deleteTag } from '@/utils/tags.ts'; const markerSchema = z.enum(['read', 'write']); @@ -181,3 +182,33 @@ export const updateZapSplitsController: AppController = async (c) => { return c.json(200); }; + +const deleteZapSplitSchema = z.array(n.id()).min(1); + +export const deleteZapSplitsController: AppController = async (c) => { + const body = await parseBody(c.req.raw); + const result = deleteZapSplitSchema.safeParse(body); + const store = c.get('store'); + + if (!result.success) { + return c.json({ error: result.error }, 400); + } + + const zap_split = await getZapSplits(store, Conf.pubkey); + if (!zap_split) { + return c.json({ error: 'Zap split not activated, visit `/api/v1/instance` to activate it.' }, 404); + } + + const { data } = result; + + await updateListAdminEvent( + { kinds: [30078], authors: [Conf.pubkey], '#d': ['pub.ditto.zapSplits'], limit: 1 }, + (tags) => + data.reduce((accumulator, currentValue) => { + return deleteTag(accumulator, ['p', currentValue]); + }, tags), + c, + ); + + return c.json(200); +}; From 5a5e8b7c5d522f00f02fad69c92a3429cb636d0b Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sun, 21 Jul 2024 20:11:26 -0300 Subject: [PATCH 33/53] feat(createStatusController): add 'zap' tag to event --- src/controllers/api/statuses.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index 4604981f..c56de586 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -29,6 +29,8 @@ import { addTag, deleteTag } from '@/utils/tags.ts'; import { asyncReplaceAll } from '@/utils/text.ts'; import { DittoEvent } from '@/interfaces/DittoEvent.ts'; import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; +import { isObjectEmpty } from '@/utils.ts'; +import { getZapSplits } from '@/utils/zap_split.ts'; const createStatusSchema = z.object({ in_reply_to_id: n.id().nullish(), @@ -71,6 +73,7 @@ const createStatusController: AppController = async (c) => { const body = await parseBody(c.req.raw); const result = createStatusSchema.safeParse(body); const kysely = await DittoDB.getInstance(); + const store = c.get('store'); if (!result.success) { return c.json({ error: 'Bad request', schema: result.error }, 400); @@ -173,14 +176,26 @@ const createStatusController: AppController = async (c) => { const quoteCompat = data.quote_id ? `\n\nnostr:${nip19.noteEncode(data.quote_id)}` : ''; const mediaCompat = mediaUrls.length ? `\n\n${mediaUrls.join('\n')}` : ''; + const author = await getAuthor(await c.get('signer')?.getPublicKey() as string); + + const meta = n.json().pipe(n.metadata()).catch({}).parse(author?.content); + const lnurl = getLnurl(meta); + const zap_split = await getZapSplits(store, Conf.pubkey); + if (lnurl && zap_split && isObjectEmpty(zap_split) === false) { + let totalSplit = 0; + for (const pubkey in zap_split) { + totalSplit += Number(zap_split[pubkey][0]); + tags.push(['zap', pubkey, Conf.relay, zap_split[pubkey][0]]); + } + tags.push(['zap', author?.pubkey as string, Conf.relay, Math.max(0, 100 - totalSplit).toString()]); + } + const event = await createEvent({ kind: 1, content: content + quoteCompat + mediaCompat, tags, }, c); - const author = await getAuthor(event.pubkey); - if (data.quote_id) { await hydrateEvents({ events: [event], @@ -189,7 +204,7 @@ const createStatusController: AppController = async (c) => { }); } - return c.json(await renderStatus({ ...event, author }, { viewerPubkey: await c.get('signer')?.getPublicKey() })); + return c.json(await renderStatus({ ...event, author }, { viewerPubkey: author?.pubkey })); }; const deleteStatusController: AppController = async (c) => { From 80e14c65c0ba9382c8b33af58d0ca945dd543875 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Tue, 23 Jul 2024 18:14:22 -0300 Subject: [PATCH 34/53] refactor: rename zap_split.ts to zap-split.ts, rename tests as well --- src/controllers/api/ditto.ts | 2 +- src/controllers/api/instance.ts | 2 +- src/controllers/api/statuses.ts | 2 +- src/utils/{zap_split.test.ts => zap-split.test.ts} | 2 +- src/utils/{zap_split.ts => zap-split.ts} | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename src/utils/{zap_split.test.ts => zap-split.test.ts} (97%) rename src/utils/{zap_split.ts => zap-split.ts} (100%) diff --git a/src/controllers/api/ditto.ts b/src/controllers/api/ditto.ts index aebf3427..a22dd1e5 100644 --- a/src/controllers/api/ditto.ts +++ b/src/controllers/api/ditto.ts @@ -9,7 +9,7 @@ import { Storages } from '@/storages.ts'; import { hydrateEvents } from '@/storages/hydrate.ts'; import { createEvent, paginated, paginationSchema, parseBody } from '@/utils/api.ts'; import { renderNameRequest } from '@/views/ditto.ts'; -import { getZapSplits } from '@/utils/zap_split.ts'; +import { getZapSplits } from '@/utils/zap-split.ts'; import { updateListAdminEvent } from '@/utils/api.ts'; import { addTag } from '@/utils/tags.ts'; import { deleteTag } from '@/utils/tags.ts'; diff --git a/src/controllers/api/instance.ts b/src/controllers/api/instance.ts index 1d97b1e9..6b8a86a3 100644 --- a/src/controllers/api/instance.ts +++ b/src/controllers/api/instance.ts @@ -4,7 +4,7 @@ import { AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; import { Storages } from '@/storages.ts'; import { getInstanceMetadata } from '@/utils/instance.ts'; -import { DittoZapSplits, getZapSplits } from '@/utils/zap_split.ts'; +import { DittoZapSplits, getZapSplits } from '@/utils/zap-split.ts'; import { createAdminEvent } from '@/utils/api.ts'; const version = `3.0.0 (compatible; Ditto ${denoJson.version})`; diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index c56de586..7b0b7820 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -30,7 +30,7 @@ import { asyncReplaceAll } from '@/utils/text.ts'; import { DittoEvent } from '@/interfaces/DittoEvent.ts'; import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; import { isObjectEmpty } from '@/utils.ts'; -import { getZapSplits } from '@/utils/zap_split.ts'; +import { getZapSplits } from '@/utils/zap-split.ts'; const createStatusSchema = z.object({ in_reply_to_id: n.id().nullish(), diff --git a/src/utils/zap_split.test.ts b/src/utils/zap-split.test.ts similarity index 97% rename from src/utils/zap_split.test.ts rename to src/utils/zap-split.test.ts index 6ccf1ada..bc9527df 100644 --- a/src/utils/zap_split.test.ts +++ b/src/utils/zap-split.test.ts @@ -2,7 +2,7 @@ import { assertEquals } from '@std/assert'; import { generateSecretKey, getPublicKey } from 'nostr-tools'; import { genEvent } from '@/test.ts'; -import { getZapSplits } from '@/utils/zap_split.ts'; +import { getZapSplits } from '@/utils/zap-split.ts'; import { getTestDB } from '@/test.ts'; Deno.test('Get zap splits in DittoZapSplits format', async () => { diff --git a/src/utils/zap_split.ts b/src/utils/zap-split.ts similarity index 100% rename from src/utils/zap_split.ts rename to src/utils/zap-split.ts From de32930c441f15498d34269180c2e39bef563a0c Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Tue, 23 Jul 2024 18:26:06 -0300 Subject: [PATCH 35/53] refactor: use exclamation mark (bang) instead of 'as string' cast --- src/controllers/api/statuses.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index 7b0b7820..7823949d 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -176,7 +176,7 @@ const createStatusController: AppController = async (c) => { const quoteCompat = data.quote_id ? `\n\nnostr:${nip19.noteEncode(data.quote_id)}` : ''; const mediaCompat = mediaUrls.length ? `\n\n${mediaUrls.join('\n')}` : ''; - const author = await getAuthor(await c.get('signer')?.getPublicKey() as string); + const author = await getAuthor(await c.get('signer')?.getPublicKey()!); const meta = n.json().pipe(n.metadata()).catch({}).parse(author?.content); const lnurl = getLnurl(meta); From a0c952b9b9a7bd764c28b0f7003ae2baa2a733cc Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Tue, 23 Jul 2024 20:15:44 -0300 Subject: [PATCH 36/53] refactor: do not use isObjectEmpty function in if condition, zap tag --- src/controllers/api/statuses.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index 7823949d..4b29aea2 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -29,7 +29,6 @@ import { addTag, deleteTag } from '@/utils/tags.ts'; import { asyncReplaceAll } from '@/utils/text.ts'; import { DittoEvent } from '@/interfaces/DittoEvent.ts'; import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; -import { isObjectEmpty } from '@/utils.ts'; import { getZapSplits } from '@/utils/zap-split.ts'; const createStatusSchema = z.object({ @@ -181,13 +180,15 @@ const createStatusController: AppController = async (c) => { const meta = n.json().pipe(n.metadata()).catch({}).parse(author?.content); const lnurl = getLnurl(meta); const zap_split = await getZapSplits(store, Conf.pubkey); - if (lnurl && zap_split && isObjectEmpty(zap_split) === false) { + if (lnurl && zap_split) { let totalSplit = 0; for (const pubkey in zap_split) { totalSplit += Number(zap_split[pubkey][0]); tags.push(['zap', pubkey, Conf.relay, zap_split[pubkey][0]]); } - tags.push(['zap', author?.pubkey as string, Conf.relay, Math.max(0, 100 - totalSplit).toString()]); + if (totalSplit) { + tags.push(['zap', author?.pubkey as string, Conf.relay, Math.max(0, 100 - totalSplit).toString()]); + } } const event = await createEvent({ From 7cdfb67b993ce66cb2071be33154f82c31c8f18a Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Tue, 23 Jul 2024 20:25:02 -0300 Subject: [PATCH 37/53] refactor: rename officialDittoAccountPubkey to dittoPubkey & officialDittoAccountMsg to dittoMsg --- src/controllers/api/instance.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/controllers/api/instance.ts b/src/controllers/api/instance.ts index 6b8a86a3..305f9e96 100644 --- a/src/controllers/api/instance.ts +++ b/src/controllers/api/instance.ts @@ -16,16 +16,16 @@ const instanceV1Controller: AppController = async (c) => { let zap_split: DittoZapSplits | undefined = await getZapSplits(store, Conf.pubkey); if (!zap_split) { - const officialDittoAccountPubkey = '781a1527055f74c1f70230f10384609b34548f8ab6a0a6caa74025827f9fdae5'; - const officialDittoAccountMsg = 'Official Ditto Account'; + const dittoPubkey = '781a1527055f74c1f70230f10384609b34548f8ab6a0a6caa74025827f9fdae5'; + const dittoMsg = 'Official Ditto Account'; await createAdminEvent({ kind: 30078, tags: [ ['d', 'pub.ditto.zapSplits'], - ['p', officialDittoAccountPubkey, '5', officialDittoAccountMsg], + ['p', dittoPubkey, '5', dittoMsg], ], }, c); - zap_split = { [officialDittoAccountPubkey]: ['5', officialDittoAccountMsg] }; + zap_split = { [dittoPubkey]: ['5', dittoMsg] }; } /** Protocol to use for WebSocket URLs, depending on the protocol of the `LOCAL_DOMAIN`. */ From 2ebaee880749c16898fcaeafaa81c1162f371966 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Tue, 23 Jul 2024 20:39:23 -0300 Subject: [PATCH 38/53] refactor: change DittoZapSplits data structure to use object fields instead of array --- src/utils/zap-split.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/zap-split.ts b/src/utils/zap-split.ts index 38eecba4..33cd9c4e 100644 --- a/src/utils/zap-split.ts +++ b/src/utils/zap-split.ts @@ -4,10 +4,10 @@ import { isNumberFrom1To100 } from '@/utils.ts'; type Pubkey = string; type ExtraMessage = string; /** Number from 1 to 100, stringified. */ -type splitPercentages = string; +type splitPercentages = number; export type DittoZapSplits = { - [key: Pubkey]: [splitPercentages, ExtraMessage]; + [key: Pubkey]: { amount: splitPercentages; message: ExtraMessage }; }; /** Gets zap splits from NIP-78 in DittoZapSplits format. */ @@ -27,7 +27,7 @@ export async function getZapSplits(store: NStore, pubkey: string): Promise Date: Tue, 23 Jul 2024 20:39:51 -0300 Subject: [PATCH 39/53] test(zap-split): update to be in accord with new data structure --- src/utils/zap-split.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/zap-split.test.ts b/src/utils/zap-split.test.ts index bc9527df..08454160 100644 --- a/src/utils/zap-split.test.ts +++ b/src/utils/zap-split.test.ts @@ -28,8 +28,8 @@ Deno.test('Get zap splits in DittoZapSplits format', async () => { const zapSplits = await getZapSplits(store, pubkey); assertEquals(zapSplits, { - '0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd': ['3', 'Alex creator of Ditto'], - '47259076c85f9240e852420d7213c95e95102f1de929fb60f33a2c32570c98c4': ['2', 'Patrick developer'], + '0461fcbecc4c3374439932d6b8f11269ccdb7cc973ad7a50ae362db135a474dd': { amount: 3, message: 'Alex creator of Ditto' }, + '47259076c85f9240e852420d7213c95e95102f1de929fb60f33a2c32570c98c4': { amount: 2, message: 'Patrick developer' }, }); assertEquals(await getZapSplits(store, 'garbage'), undefined); From c49460e37d854ac662b6f79b10bcc3d1267922b8 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Tue, 23 Jul 2024 20:41:02 -0300 Subject: [PATCH 40/53] refactor(instance.ts): use new zap_split data structure in ditto hard coded pubkey --- src/controllers/api/instance.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/api/instance.ts b/src/controllers/api/instance.ts index 305f9e96..120f6801 100644 --- a/src/controllers/api/instance.ts +++ b/src/controllers/api/instance.ts @@ -25,7 +25,7 @@ const instanceV1Controller: AppController = async (c) => { ['p', dittoPubkey, '5', dittoMsg], ], }, c); - zap_split = { [dittoPubkey]: ['5', dittoMsg] }; + zap_split = { [dittoPubkey]: { amount: 5, message: dittoMsg } }; } /** Protocol to use for WebSocket URLs, depending on the protocol of the `LOCAL_DOMAIN`. */ From 0e43d1e8a765f22477949d69191b84810bc1ae51 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Tue, 23 Jul 2024 22:40:38 -0300 Subject: [PATCH 41/53] refactor: add zap tag with new data structure --- src/controllers/api/statuses.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index 4b29aea2..e222608c 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -183,8 +183,8 @@ const createStatusController: AppController = async (c) => { if (lnurl && zap_split) { let totalSplit = 0; for (const pubkey in zap_split) { - totalSplit += Number(zap_split[pubkey][0]); - tags.push(['zap', pubkey, Conf.relay, zap_split[pubkey][0]]); + totalSplit += zap_split[pubkey].amount; + tags.push(['zap', pubkey, Conf.relay, zap_split[pubkey].amount.toString()]); } if (totalSplit) { tags.push(['zap', author?.pubkey as string, Conf.relay, Math.max(0, 100 - totalSplit).toString()]); From 86874e3a0869f21a40f690b64fa55a42fb140a29 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Wed, 24 Jul 2024 10:53:32 -0300 Subject: [PATCH 42/53] feat: create createZapSplitsIfNotExists() function --- src/utils/zap-split.ts | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/utils/zap-split.ts b/src/utils/zap-split.ts index 33cd9c4e..54fe04a5 100644 --- a/src/utils/zap-split.ts +++ b/src/utils/zap-split.ts @@ -1,5 +1,9 @@ +import { Conf } from '@/config.ts'; import { NSchema as n, NStore } from '@nostrify/nostrify'; -import { isNumberFrom1To100 } from '@/utils.ts'; +import { isNumberFrom1To100, nostrNow } from '@/utils.ts'; +import { Storages } from '@/storages.ts'; +import { AdminSigner } from '@/signers/AdminSigner.ts'; +import { handleEvent } from '@/pipeline.ts'; type Pubkey = string; type ExtraMessage = string; @@ -33,3 +37,25 @@ export async function getZapSplits(store: NStore, pubkey: string): Promise Date: Wed, 24 Jul 2024 10:54:20 -0300 Subject: [PATCH 43/53] feat: create startup file --- src/startup.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 src/startup.ts diff --git a/src/startup.ts b/src/startup.ts new file mode 100644 index 00000000..4ec75e03 --- /dev/null +++ b/src/startup.ts @@ -0,0 +1,16 @@ +// Starts up applications required to run before the HTTP server is on. + +import { Conf } from '@/config.ts'; +import { createZapSplitsIfNotExists } from '@/utils/zap-split.ts'; +import { cron } from '@/cron.ts'; +import { startFirehose } from '@/firehose.ts'; + +if (Conf.firehoseEnabled) { + startFirehose(); +} + +if (Conf.cronEnabled) { + cron(); +} + +await createZapSplitsIfNotExists(); From 23bb24929c0d693d3186904d21e1b5606e9412fd Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Wed, 24 Jul 2024 10:54:56 -0300 Subject: [PATCH 44/53] refactor: remove zap split creation from instanceV1Controller endpoint --- src/controllers/api/instance.ts | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/controllers/api/instance.ts b/src/controllers/api/instance.ts index 120f6801..5f7054ee 100644 --- a/src/controllers/api/instance.ts +++ b/src/controllers/api/instance.ts @@ -5,7 +5,6 @@ import { Conf } from '@/config.ts'; import { Storages } from '@/storages.ts'; import { getInstanceMetadata } from '@/utils/instance.ts'; import { DittoZapSplits, getZapSplits } from '@/utils/zap-split.ts'; -import { createAdminEvent } from '@/utils/api.ts'; const version = `3.0.0 (compatible; Ditto ${denoJson.version})`; @@ -14,19 +13,7 @@ const instanceV1Controller: AppController = async (c) => { const meta = await getInstanceMetadata(await Storages.db(), c.req.raw.signal); const store = c.get('store'); - let zap_split: DittoZapSplits | undefined = await getZapSplits(store, Conf.pubkey); - if (!zap_split) { - const dittoPubkey = '781a1527055f74c1f70230f10384609b34548f8ab6a0a6caa74025827f9fdae5'; - const dittoMsg = 'Official Ditto Account'; - await createAdminEvent({ - kind: 30078, - tags: [ - ['d', 'pub.ditto.zapSplits'], - ['p', dittoPubkey, '5', dittoMsg], - ], - }, c); - zap_split = { [dittoPubkey]: { amount: 5, message: dittoMsg } }; - } + const zap_split: DittoZapSplits | undefined = await getZapSplits(store, Conf.pubkey) ?? {}; /** Protocol to use for WebSocket URLs, depending on the protocol of the `LOCAL_DOMAIN`. */ const wsProtocol = protocol === 'http:' ? 'ws:' : 'wss:'; From ddd1972c831b025a0a891188bcee6df69c6560f3 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Wed, 24 Jul 2024 10:55:30 -0300 Subject: [PATCH 45/53] refactor(app.ts): move cron function and startFirehose function to startup.ts --- src/app.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/app.ts b/src/app.ts index f9dad138..690862eb 100644 --- a/src/app.ts +++ b/src/app.ts @@ -5,9 +5,6 @@ import { logger } from '@hono/hono/logger'; import { NostrEvent, NostrSigner, NStore, NUploader } from '@nostrify/nostrify'; import Debug from '@soapbox/stickynotes/debug'; -import { Conf } from '@/config.ts'; -import { cron } from '@/cron.ts'; -import { startFirehose } from '@/firehose.ts'; import { Time } from '@/utils/time.ts'; import { @@ -145,13 +142,6 @@ const app = new Hono({ strict: false }); const debug = Debug('ditto:http'); -if (Conf.firehoseEnabled) { - startFirehose(); -} -if (Conf.cronEnabled) { - cron(); -} - app.use('*', rateLimitMiddleware(300, Time.minutes(5))); app.use('/api/*', metricsMiddleware, logger(debug)); From 961da0f52cb09654955550d9dfc2a3cee12e7dd6 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Wed, 24 Jul 2024 10:56:12 -0300 Subject: [PATCH 46/53] refactor: change error message in updateZapSplitsController & deleteZapSplitsController --- src/controllers/api/ditto.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/api/ditto.ts b/src/controllers/api/ditto.ts index a22dd1e5..f90579dd 100644 --- a/src/controllers/api/ditto.ts +++ b/src/controllers/api/ditto.ts @@ -166,7 +166,7 @@ export const updateZapSplitsController: AppController = async (c) => { const zap_split = await getZapSplits(store, Conf.pubkey); if (!zap_split) { - return c.json({ error: 'Zap split not activated, visit `/api/v1/instance` to activate it.' }, 404); + return c.json({ error: 'Zap split not activated, restart the server.' }, 404); } const { data } = result; @@ -196,7 +196,7 @@ export const deleteZapSplitsController: AppController = async (c) => { const zap_split = await getZapSplits(store, Conf.pubkey); if (!zap_split) { - return c.json({ error: 'Zap split not activated, visit `/api/v1/instance` to activate it.' }, 404); + return c.json({ error: 'Zap split not activated, restart the server.' }, 404); } const { data } = result; From 85806f7ea8aef84ed642d799acb29a1ed5548343 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Wed, 24 Jul 2024 11:14:42 -0300 Subject: [PATCH 47/53] refactor: use object fields instead of tuple in zapSplitSchema --- src/controllers/api/ditto.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/controllers/api/ditto.ts b/src/controllers/api/ditto.ts index f90579dd..0f815350 100644 --- a/src/controllers/api/ditto.ts +++ b/src/controllers/api/ditto.ts @@ -153,7 +153,11 @@ export const nameRequestsController: AppController = async (c) => { return paginated(c, orig, nameRequests); }; -const zapSplitSchema = z.array(z.tuple([n.id(), z.number().int().min(1).max(100), z.string().max(500)])).min(1); +const zapSplitSchema = z.array(z.object({ + pubkey: n.id(), + amount: z.number().int().min(1).max(100), + message: z.string().max(500), +})).min(1); export const updateZapSplitsController: AppController = async (c) => { const body = await parseBody(c.req.raw); @@ -175,7 +179,7 @@ export const updateZapSplitsController: AppController = async (c) => { { kinds: [30078], authors: [Conf.pubkey], '#d': ['pub.ditto.zapSplits'], limit: 1 }, (tags) => data.reduce((accumulator, currentValue) => { - return addTag(accumulator, ['p', currentValue[0], String(currentValue[1]), currentValue[2]]); + return addTag(accumulator, ['p', currentValue.pubkey, currentValue.amount.toString(), currentValue.message]); }, tags), c, ); From f94ae7606c9c2c029e8f54a03e0d34bf7c652bc0 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Thu, 25 Jul 2024 11:11:44 -0300 Subject: [PATCH 48/53] refactor: change zapSplitSchema to z.record()) --- src/controllers/api/ditto.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/controllers/api/ditto.ts b/src/controllers/api/ditto.ts index 0f815350..d1ba002b 100644 --- a/src/controllers/api/ditto.ts +++ b/src/controllers/api/ditto.ts @@ -153,11 +153,13 @@ export const nameRequestsController: AppController = async (c) => { return paginated(c, orig, nameRequests); }; -const zapSplitSchema = z.array(z.object({ - pubkey: n.id(), - amount: z.number().int().min(1).max(100), - message: z.string().max(500), -})).min(1); +const zapSplitSchema = z.record( + n.id(), + z.object({ + amount: z.number().int().min(1).max(100), + message: z.string().max(500), + }), +); export const updateZapSplitsController: AppController = async (c) => { const body = await parseBody(c.req.raw); @@ -174,12 +176,17 @@ export const updateZapSplitsController: AppController = async (c) => { } const { data } = result; + const pubkeys = Object.keys(data); + + if (pubkeys.length < 1) { + return c.json(200); + } await updateListAdminEvent( { kinds: [30078], authors: [Conf.pubkey], '#d': ['pub.ditto.zapSplits'], limit: 1 }, (tags) => - data.reduce((accumulator, currentValue) => { - return addTag(accumulator, ['p', currentValue.pubkey, currentValue.amount.toString(), currentValue.message]); + pubkeys.reduce((accumulator, pubkey) => { + return addTag(accumulator, ['p', pubkey, data[pubkey].amount.toString(), data[pubkey].message]); }, tags), c, ); From 541b5b1c39aaf3bbdafeba7756b5c43231309a9e Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Thu, 25 Jul 2024 14:52:03 -0300 Subject: [PATCH 49/53] refactor: imports in alphabetical order --- src/controllers/api/statuses.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index e222608c..6a9ed1f5 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -6,10 +6,15 @@ import { nip19 } from 'nostr-tools'; import { z } from 'zod'; import { type AppController } from '@/app.ts'; +import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; +import { addTag, deleteTag } from '@/utils/tags.ts'; +import { asyncReplaceAll } from '@/utils/text.ts'; import { Conf } from '@/config.ts'; import { DittoDB } from '@/db/DittoDB.ts'; +import { DittoEvent } from '@/interfaces/DittoEvent.ts'; import { getAncestors, getAuthor, getDescendants, getEvent } from '@/queries.ts'; import { getUnattachedMediaByIds } from '@/db/unattached-media.ts'; +import { lookupPubkey } from '@/utils/lookup.ts'; import { renderEventAccounts } from '@/views.ts'; import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts'; import { Storages } from '@/storages.ts'; @@ -24,11 +29,6 @@ import { 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'; -import { asyncReplaceAll } from '@/utils/text.ts'; -import { DittoEvent } from '@/interfaces/DittoEvent.ts'; -import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; import { getZapSplits } from '@/utils/zap-split.ts'; const createStatusSchema = z.object({ From f3d521356d1ca64a3472ce1652755703763f9ad7 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Thu, 25 Jul 2024 14:52:52 -0300 Subject: [PATCH 50/53] refactor: get rid of isObjectEmpty function --- src/utils.test.ts | 11 ++--------- src/utils.ts | 8 -------- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/src/utils.test.ts b/src/utils.test.ts index 1023c633..d89472c6 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -1,6 +1,7 @@ -import { isNumberFrom1To100, isObjectEmpty } from '@/utils.ts'; import { assertEquals } from '@std/assert'; +import { isNumberFrom1To100 } from '@/utils.ts'; + Deno.test('Value is any number from 1 to 100', () => { assertEquals(isNumberFrom1To100('latvia'), false); assertEquals(isNumberFrom1To100(1.5), false); @@ -19,11 +20,3 @@ Deno.test('Value is any number from 1 to 100', () => { assertEquals(isNumberFrom1To100('1e1'), true); }); - -Deno.test('Object is empty', () => { - assertEquals(isObjectEmpty([1]), false); - assertEquals(isObjectEmpty({ 'yolo': 'no yolo' }), false); - - assertEquals(isObjectEmpty([]), true); - assertEquals(isObjectEmpty({}), true); -}); diff --git a/src/utils.ts b/src/utils.ts index de826ad6..a1c3b1d1 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -97,13 +97,6 @@ function isNumberFrom1To100(value: unknown): boolean { return z.coerce.number().int().gte(1).lte(100).safeParse(value).success; } -function isObjectEmpty(obj: object): boolean { - for (const prop in obj) { - if (Object.hasOwn(obj, prop)) return false; - } - return true; -} - export { bech32ToPubkey, dedupeEvents, @@ -111,7 +104,6 @@ export { findTag, isNostrId, isNumberFrom1To100, - isObjectEmpty, isURL, type Nip05, nostrDate, From 882f8009dc9f7ad84bcc16bedd5c654977afc141 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Thu, 25 Jul 2024 15:06:50 -0300 Subject: [PATCH 51/53] refactor: rename isNumberFrom1To100 to percentageSchema --- src/schema.test.ts | 22 ++++++++++++++++++++++ src/schema.ts | 12 +++++++++++- src/utils.test.ts | 22 ---------------------- src/utils.ts | 5 ----- src/utils/zap-split.ts | 11 ++++++----- 5 files changed, 39 insertions(+), 33 deletions(-) create mode 100644 src/schema.test.ts delete mode 100644 src/utils.test.ts diff --git a/src/schema.test.ts b/src/schema.test.ts new file mode 100644 index 00000000..c6b577de --- /dev/null +++ b/src/schema.test.ts @@ -0,0 +1,22 @@ +import { assertEquals } from '@std/assert'; + +import { percentageSchema } from '@/schema.ts'; + +Deno.test('Value is any percentage from 1 to 100', () => { + assertEquals(percentageSchema.safeParse('latvia' as unknown).success, false); + assertEquals(percentageSchema.safeParse(1.5).success, false); + assertEquals(percentageSchema.safeParse(Infinity).success, false); + assertEquals(percentageSchema.safeParse('Infinity').success, false); + assertEquals(percentageSchema.safeParse('0').success, false); + assertEquals(percentageSchema.safeParse(0).success, false); + assertEquals(percentageSchema.safeParse(-1).success, false); + assertEquals(percentageSchema.safeParse('-10').success, false); + assertEquals(percentageSchema.safeParse([]).success, false); + assertEquals(percentageSchema.safeParse(undefined).success, false); + + for (let i = 1; i < 100; i++) { + assertEquals(percentageSchema.safeParse(String(i)).success, true); + } + + assertEquals(percentageSchema.safeParse('1e1').success, true); +}); diff --git a/src/schema.ts b/src/schema.ts index d152a0d4..fc7efd01 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -38,4 +38,14 @@ const booleanParamSchema = z.enum(['true', 'false']).transform((value) => value /** Schema for `File` objects. */ const fileSchema = z.custom((value) => value instanceof File); -export { booleanParamSchema, decode64Schema, fileSchema, filteredArray, hashtagSchema, safeUrlSchema }; +const percentageSchema = z.coerce.number().int().gte(1).lte(100); + +export { + booleanParamSchema, + decode64Schema, + fileSchema, + filteredArray, + hashtagSchema, + percentageSchema, + safeUrlSchema, +}; diff --git a/src/utils.test.ts b/src/utils.test.ts deleted file mode 100644 index d89472c6..00000000 --- a/src/utils.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { assertEquals } from '@std/assert'; - -import { isNumberFrom1To100 } from '@/utils.ts'; - -Deno.test('Value is any number from 1 to 100', () => { - assertEquals(isNumberFrom1To100('latvia'), false); - assertEquals(isNumberFrom1To100(1.5), false); - assertEquals(isNumberFrom1To100(Infinity), false); - assertEquals(isNumberFrom1To100('Infinity'), false); - assertEquals(isNumberFrom1To100('0'), false); - assertEquals(isNumberFrom1To100(0), false); - assertEquals(isNumberFrom1To100(-1), false); - assertEquals(isNumberFrom1To100('-10'), false); - assertEquals(isNumberFrom1To100([]), false); - assertEquals(isNumberFrom1To100(undefined), false); - - for (let i = 1; i < 100; i++) { - assertEquals(isNumberFrom1To100(String(i)), true); - } - - assertEquals(isNumberFrom1To100('1e1'), true); -}); diff --git a/src/utils.ts b/src/utils.ts index a1c3b1d1..e9213ed1 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -93,17 +93,12 @@ function isURL(value: unknown): boolean { return z.string().url().safeParse(value).success; } -function isNumberFrom1To100(value: unknown): boolean { - return z.coerce.number().int().gte(1).lte(100).safeParse(value).success; -} - export { bech32ToPubkey, dedupeEvents, eventAge, findTag, isNostrId, - isNumberFrom1To100, isURL, type Nip05, nostrDate, diff --git a/src/utils/zap-split.ts b/src/utils/zap-split.ts index 54fe04a5..830c1e7c 100644 --- a/src/utils/zap-split.ts +++ b/src/utils/zap-split.ts @@ -1,9 +1,10 @@ -import { Conf } from '@/config.ts'; -import { NSchema as n, NStore } from '@nostrify/nostrify'; -import { isNumberFrom1To100, nostrNow } from '@/utils.ts'; -import { Storages } from '@/storages.ts'; import { AdminSigner } from '@/signers/AdminSigner.ts'; +import { Conf } from '@/config.ts'; import { handleEvent } from '@/pipeline.ts'; +import { NSchema as n, NStore } from '@nostrify/nostrify'; +import { nostrNow } from '@/utils.ts'; +import { percentageSchema } from '@/schema.ts'; +import { Storages } from '@/storages.ts'; type Pubkey = string; type ExtraMessage = string; @@ -29,7 +30,7 @@ export async function getZapSplits(store: NStore, pubkey: string): Promise Date: Thu, 25 Jul 2024 15:10:54 -0300 Subject: [PATCH 52/53] refactor: rename createZapSplitsIfNotExists to seedZapSplits --- src/startup.ts | 4 ++-- src/utils/zap-split.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/startup.ts b/src/startup.ts index 4ec75e03..21df4d50 100644 --- a/src/startup.ts +++ b/src/startup.ts @@ -1,7 +1,7 @@ // Starts up applications required to run before the HTTP server is on. import { Conf } from '@/config.ts'; -import { createZapSplitsIfNotExists } from '@/utils/zap-split.ts'; +import { seedZapSplits } from '@/utils/zap-split.ts'; import { cron } from '@/cron.ts'; import { startFirehose } from '@/firehose.ts'; @@ -13,4 +13,4 @@ if (Conf.cronEnabled) { cron(); } -await createZapSplitsIfNotExists(); +await seedZapSplits(); diff --git a/src/utils/zap-split.ts b/src/utils/zap-split.ts index 830c1e7c..553d9c8c 100644 --- a/src/utils/zap-split.ts +++ b/src/utils/zap-split.ts @@ -39,7 +39,7 @@ export async function getZapSplits(store: NStore, pubkey: string): Promise Date: Thu, 25 Jul 2024 15:44:05 -0300 Subject: [PATCH 53/53] fix(app.ts): import startup.ts file --- src/app.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app.ts b/src/app.ts index 690862eb..66c33424 100644 --- a/src/app.ts +++ b/src/app.ts @@ -110,6 +110,7 @@ import { import { errorHandler } from '@/controllers/error.ts'; import { metricsController } from '@/controllers/metrics.ts'; import { indexController } from '@/controllers/site.ts'; +import '@/startup.ts'; import { nodeInfoController, nodeInfoSchemaController } from '@/controllers/well-known/nodeinfo.ts'; import { nostrController } from '@/controllers/well-known/nostr.ts'; import { auth98Middleware, requireProof, requireRole } from '@/middleware/auth98Middleware.ts';