diff --git a/src/db.ts b/src/db.ts index e1039dc..2d6b4d7 100644 --- a/src/db.ts +++ b/src/db.ts @@ -14,6 +14,7 @@ interface DittoDB { unattached_media: UnattachedMediaRow; author_stats: AuthorStatsRow; event_stats: EventStatsRow; + pubkey_domains: PubkeyDomainRow; } interface AuthorStatsRow { @@ -66,6 +67,11 @@ interface UnattachedMediaRow { uploaded_at: Date; } +interface PubkeyDomainRow { + pubkey: string; + domain: string; +} + const sqliteWorker = new SqliteWorker(); await sqliteWorker.open(Conf.dbPath); diff --git a/src/db/migrations/015_add_pubkey_domains.ts b/src/db/migrations/015_add_pubkey_domains.ts new file mode 100644 index 0000000..33bcc6c --- /dev/null +++ b/src/db/migrations/015_add_pubkey_domains.ts @@ -0,0 +1,21 @@ +import { Kysely } from '@/deps.ts'; + +export async function up(db: Kysely): Promise { + await db.schema + .createTable('pubkey_domains') + .ifNotExists() + .addColumn('pubkey', 'text', (col) => col.primaryKey()) + .addColumn('domain', 'text') + .execute(); + + await db.schema + .createIndex('pubkey_domains_domain_index') + .on('pubkey_domains') + .column('domain') + .ifNotExists() + .execute(); +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable('pubkey_domains').execute(); +} diff --git a/src/pipeline.ts b/src/pipeline.ts index 95c1a87..9d65b13 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -1,4 +1,6 @@ +import { NSchema as n } from '@soapbox/nspec'; import { Conf } from '@/config.ts'; +import { db } from '@/db.ts'; import { addRelays } from '@/db/relays.ts'; import { deleteAttachedMedia } from '@/db/unattached-media.ts'; import { Debug, LNURL, type NostrEvent } from '@/deps.ts'; @@ -15,6 +17,7 @@ import { TrendsWorker } from '@/workers/trends.ts'; import { verifyEventWorker } from '@/workers/verify.ts'; import { AdminSigner } from '@/signers/AdminSigner.ts'; import { lnurlCache } from '@/utils/lnurl.ts'; +import { nip05Cache } from '@/utils/nip05.ts'; const debug = Debug('ditto:pipeline'); @@ -30,6 +33,7 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise { + if (event.kind !== 0) return; + + // Parse metadata. + const metadata = n.json().pipe(n.metadata()).safeParse(event.content); + if (!metadata.success) return; + + // Get nip05. + const { nip05 } = metadata.data; + if (!nip05) return; + + // Fetch nip05. + const result = await nip05Cache.fetch(nip05, { signal }).catch(() => undefined); + if (!result) return; + + // Ensure pubkey matches event. + const { pubkey } = result; + if (pubkey !== event.pubkey) return; + + // Track pubkey domain. + const [, domain] = nip05.split('@'); + await db + .insertInto('pubkey_domains') + .values({ pubkey, domain }) + .execute() + .catch(debug); +} + /** Query to-be-deleted events, ensure their pubkey matches, then delete them from the database. */ async function processDeletions(event: NostrEvent, signal: AbortSignal): Promise { if (event.kind === 5) {