From b3c749d26629854e3cc9c883200c1589aac0ab9f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 5 Dec 2023 16:40:45 -0600 Subject: [PATCH] db/events: add support for `with_author` filter --- src/db/events.ts | 77 +++++++++++++++++++++++++++++++++++++++++++----- src/deps.ts | 1 + src/filter.ts | 1 + 3 files changed, 72 insertions(+), 7 deletions(-) diff --git a/src/db/events.ts b/src/db/events.ts index 5749d94..d00ae2c 100644 --- a/src/db/events.ts +++ b/src/db/events.ts @@ -1,5 +1,5 @@ -import { db } from '@/db.ts'; -import { type Event } from '@/deps.ts'; +import { db, type DittoDB } from '@/db.ts'; +import { type Event, type SelectQueryBuilder } from '@/deps.ts'; import { isParameterizedReplaceableKind } from '@/kinds.ts'; import { jsonMetaContentSchema } from '@/schemas/nostr.ts'; import { EventData } from '@/types.ts'; @@ -72,8 +72,25 @@ function insertEvent(event: Event, data: EventData): Promise { }); } +type EventQuery = SelectQueryBuilder; + /** Build the query for a filter. */ -function getFilterQuery(filter: DittoFilter) { +function getFilterQuery(filter: DittoFilter): EventQuery { let query = db .selectFrom('events') .select([ @@ -127,6 +144,31 @@ function getFilterQuery(filter: DittoFilter) { : query.leftJoin('users', 'users.pubkey', 'events.pubkey').where('users.pubkey', 'is', null) as typeof query; } + if (filter.with_author) { + // get kind 0 event associated with the `pubkey` field of the event + query = query + .leftJoin( + (eb) => + eb + .selectFrom('events') + .selectAll() + .where('kind', '=', 0) + .orderBy('created_at', 'desc') + .groupBy('pubkey') + .as('authors'), + (join) => join.onRef('authors.pubkey', '=', 'events.pubkey'), + ) + .select([ + 'authors.id as author_id', + 'authors.kind as author_kind', + 'authors.pubkey as author_pubkey', + 'authors.content as author_content', + 'authors.tags as author_tags', + 'authors.created_at as author_created_at', + 'authors.sig as author_sig', + ]); + } + if (filter.search) { query = query .innerJoin('events_fts', 'events_fts.id', 'events.id') @@ -143,11 +185,15 @@ function getFiltersQuery(filters: DittoFilter[]) { .reduce((result, query) => result.union(query)); } +interface DittoEvent extends Event { + author?: Event<0>; +} + /** Get events for filters from the database. */ async function getFilters( filters: DittoFilter[], opts: GetFiltersOpts = {}, -): Promise[]> { +): Promise[]> { if (!filters.length) return Promise.resolve([]); let query = getFiltersQuery(filters); @@ -155,9 +201,26 @@ async function getFilters( query = query.limit(opts.limit); } - return (await query.execute()).map((event) => ( - { ...event, tags: JSON.parse(event.tags) } as Event - )); + return (await query.execute()).map((row) => ({ + id: row.id, + kind: row.kind, + pubkey: row.pubkey, + content: row.content, + created_at: row.created_at, + tags: JSON.parse(row.tags), + author: row.author_id + ? { + id: row.author_id, + kind: row.author_kind!, + pubkey: row.author_pubkey!, + content: row.author_content!, + created_at: row.author_created_at!, + tags: JSON.parse(row.author_tags!), + sig: row.author_sig!, + } + : undefined, + sig: row.sig, + } as DittoEvent)); } /** Delete events based on filters from the database. */ diff --git a/src/deps.ts b/src/deps.ts index dbf3b46..f1cedc5 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -67,6 +67,7 @@ export { Migrator, type NullableInsertKeys, type QueryResult, + type SelectQueryBuilder, sql, } from 'npm:kysely@^0.26.3'; export { PolySqliteDialect } from 'https://gitlab.com/soapbox-pub/kysely-deno-sqlite/-/raw/v2.0.0/mod.ts'; diff --git a/src/filter.ts b/src/filter.ts index 4bc806a..10f0557 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -6,6 +6,7 @@ import type { EventData } from '@/types.ts'; /** Custom filter interface that extends Nostr filters with extra options for Ditto. */ interface DittoFilter extends Filter { local?: boolean; + with_author?: boolean; } /** Additional options to apply to the whole subscription. */