From b3c749d26629854e3cc9c883200c1589aac0ab9f Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 5 Dec 2023 16:40:45 -0600 Subject: [PATCH 1/8] 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. */ From f9d3240fa8241475210182d2ba29e1bf58f9c176 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 5 Dec 2023 16:52:39 -0600 Subject: [PATCH 2/8] with_author --> with_authors --- src/db/events.ts | 2 +- src/filter.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/db/events.ts b/src/db/events.ts index d00ae2c..be46a1a 100644 --- a/src/db/events.ts +++ b/src/db/events.ts @@ -144,7 +144,7 @@ function getFilterQuery(filter: DittoFilter): EventQuery { : query.leftJoin('users', 'users.pubkey', 'events.pubkey').where('users.pubkey', 'is', null) as typeof query; } - if (filter.with_author) { + if (filter.with_authors) { // get kind 0 event associated with the `pubkey` field of the event query = query .leftJoin( diff --git a/src/filter.ts b/src/filter.ts index 10f0557..82c9be9 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -6,7 +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; + with_authors?: boolean; } /** Additional options to apply to the whole subscription. */ From e3d5b2ac4a908be700acffe4aa35ffe181f27d05 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 5 Dec 2023 17:15:08 -0600 Subject: [PATCH 3/8] Move with_authors to GetFilterOpts instead of DittoFilter --- src/db/events.ts | 49 ++++++++++++++++++++++++------------------------ src/filter.ts | 3 ++- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/db/events.ts b/src/db/events.ts index be46a1a..f8583a9 100644 --- a/src/db/events.ts +++ b/src/db/events.ts @@ -144,31 +144,6 @@ function getFilterQuery(filter: DittoFilter): EventQuery { : query.leftJoin('users', 'users.pubkey', 'events.pubkey').where('users.pubkey', 'is', null) as typeof query; } - if (filter.with_authors) { - // 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') @@ -197,6 +172,30 @@ async function getFilters( if (!filters.length) return Promise.resolve([]); let query = getFiltersQuery(filters); + if (opts.with_authors) { + 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', + ]) as typeof query; + } + if (typeof opts.limit === 'number') { query = query.limit(opts.limit); } diff --git a/src/filter.ts b/src/filter.ts index 82c9be9..9146186 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -6,7 +6,6 @@ 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_authors?: boolean; } /** Additional options to apply to the whole subscription. */ @@ -15,6 +14,8 @@ interface GetFiltersOpts { timeout?: number; /** Event limit for the whole subscription. */ limit?: number; + /** Whether to include a corresponding kind 0 event in the `authors` key of each event. */ + with_authors?: boolean; } function matchDittoFilter(filter: DittoFilter, event: Event, data: EventData): boolean { From 2478545cd34be6e0f721deae9ba97966afc40842 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 5 Dec 2023 17:27:52 -0600 Subject: [PATCH 4/8] with_authors --> with: With[] --- src/db/events.ts | 2 +- src/filter.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/db/events.ts b/src/db/events.ts index f8583a9..0adb071 100644 --- a/src/db/events.ts +++ b/src/db/events.ts @@ -172,7 +172,7 @@ async function getFilters( if (!filters.length) return Promise.resolve([]); let query = getFiltersQuery(filters); - if (opts.with_authors) { + if (opts.with?.includes('authors')) { query = query .leftJoin( (eb) => diff --git a/src/filter.ts b/src/filter.ts index 9146186..5c8e70c 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -8,6 +8,9 @@ interface DittoFilter extends Filter { local?: boolean; } +/** Additional properties that may be added to events. */ +type With = 'authors'; + /** Additional options to apply to the whole subscription. */ interface GetFiltersOpts { /** How long to wait (in milliseconds) until aborting the request. */ @@ -15,7 +18,7 @@ interface GetFiltersOpts { /** Event limit for the whole subscription. */ limit?: number; /** Whether to include a corresponding kind 0 event in the `authors` key of each event. */ - with_authors?: boolean; + with?: With[]; } function matchDittoFilter(filter: DittoFilter, event: Event, data: EventData): boolean { From 22b1d730eb2808ff4cbcf181492727d852bd8334 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 5 Dec 2023 19:40:40 -0600 Subject: [PATCH 5/8] with --> extra --- src/db/events.ts | 2 +- src/filter.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/db/events.ts b/src/db/events.ts index c78a6bf..0c1604e 100644 --- a/src/db/events.ts +++ b/src/db/events.ts @@ -172,7 +172,7 @@ async function getFilters( if (!filters.length) return Promise.resolve([]); let query = getFiltersQuery(filters); - if (opts.with?.includes('authors')) { + if (opts.extra?.includes('author')) { query = query .leftJoin( (eb) => diff --git a/src/filter.ts b/src/filter.ts index 5c8e70c..a258a66 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -8,8 +8,8 @@ interface DittoFilter extends Filter { local?: boolean; } -/** Additional properties that may be added to events. */ -type With = 'authors'; +/** Additional properties that may be added by Ditto to events. */ +type Extra = 'author'; /** Additional options to apply to the whole subscription. */ interface GetFiltersOpts { @@ -18,7 +18,7 @@ interface GetFiltersOpts { /** Event limit for the whole subscription. */ limit?: number; /** Whether to include a corresponding kind 0 event in the `authors` key of each event. */ - with?: With[]; + extra?: Extra[]; } function matchDittoFilter(filter: DittoFilter, event: Event, data: EventData): boolean { From 7d2813b21467fec8d56ab9154600b504df405bff Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 5 Dec 2023 22:06:27 -0600 Subject: [PATCH 6/8] extra --> relations, move it back to a filter option --- src/db/events.ts | 48 ++++++++++++++++++++++++------------------------ src/filter.ts | 11 ++++++----- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/src/db/events.ts b/src/db/events.ts index 0c1604e..e3ac876 100644 --- a/src/db/events.ts +++ b/src/db/events.ts @@ -144,6 +144,30 @@ function getFilterQuery(filter: DittoFilter): EventQuery { : query.leftJoin('users', 'users.pubkey', 'events.pubkey').where('users.pubkey', 'is', null) as typeof query; } + if (filter.relations?.includes('author')) { + 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', + ]) as typeof query; + } + if (filter.search) { query = query .innerJoin('events_fts', 'events_fts.id', 'events.id') @@ -172,30 +196,6 @@ async function getFilters( if (!filters.length) return Promise.resolve([]); let query = getFiltersQuery(filters); - if (opts.extra?.includes('author')) { - 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', - ]) as typeof query; - } - if (typeof opts.limit === 'number') { query = query.limit(opts.limit); } diff --git a/src/filter.ts b/src/filter.ts index a258a66..cb43e2c 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -3,22 +3,23 @@ import { type Event, type Filter, matchFilters } from '@/deps.ts'; import type { EventData } from '@/types.ts'; +/** Additional properties that may be added by Ditto to events. */ +type Relation = 'author'; + /** Custom filter interface that extends Nostr filters with extra options for Ditto. */ interface DittoFilter extends Filter { + /** Whether the event was authored by a local user. */ local?: boolean; + /** Additional fields to add to the returned event. */ + relations?: Relation[]; } -/** Additional properties that may be added by Ditto to events. */ -type Extra = 'author'; - /** Additional options to apply to the whole subscription. */ interface GetFiltersOpts { /** How long to wait (in milliseconds) until aborting the request. */ timeout?: number; /** Event limit for the whole subscription. */ limit?: number; - /** Whether to include a corresponding kind 0 event in the `authors` key of each event. */ - extra?: Extra[]; } function matchDittoFilter(filter: DittoFilter, event: Event, data: EventData): boolean { From a6947441fc96285aaa8776ac356d5c4f4afcba29 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Dec 2023 12:04:24 -0600 Subject: [PATCH 7/8] renderStatus: don't fetch the author, expect it to be passed in --- src/controllers/api/accounts.ts | 7 ++++--- src/controllers/api/search.ts | 12 +++++++----- src/controllers/api/statuses.ts | 11 ++++++----- src/controllers/api/streaming.ts | 5 +++-- src/controllers/api/timelines.ts | 5 ++++- src/db/events.ts | 2 +- src/filter.ts | 2 +- src/queries.ts | 13 ++++++++----- src/views/mastodon/notifications.ts | 4 +++- src/views/mastodon/statuses.ts | 8 +++----- 10 files changed, 40 insertions(+), 29 deletions(-) diff --git a/src/controllers/api/accounts.ts b/src/controllers/api/accounts.ts index 68e28fd..2896b96 100644 --- a/src/controllers/api/accounts.ts +++ b/src/controllers/api/accounts.ts @@ -1,7 +1,8 @@ import { type AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; import { insertUser } from '@/db/users.ts'; -import { type Filter, findReplyTag, nip19, z } from '@/deps.ts'; +import { findReplyTag, nip19, z } from '@/deps.ts'; +import { type DittoFilter } from '@/filter.ts'; import * as mixer from '@/mixer.ts'; import { getAuthor, getFollowedPubkeys, getFollows } from '@/queries.ts'; import { booleanParamSchema, fileSchema } from '@/schema.ts'; @@ -137,7 +138,7 @@ const accountStatusesController: AppController = async (c) => { return c.json([]); } - const filter: Filter<1> = { authors: [pubkey], kinds: [1], since, until, limit }; + const filter: DittoFilter<1> = { authors: [pubkey], kinds: [1], relations: ['author'], since, until, limit }; if (tagged) { filter['#t'] = [tagged]; } @@ -256,7 +257,7 @@ const favouritesController: AppController = async (c) => { .map((event) => event.tags.find((tag) => tag[0] === 'e')?.[1]) .filter((id): id is string => !!id); - const events1 = await mixer.getFilters([{ kinds: [1], ids }], { timeout: Time.seconds(1) }); + const events1 = await mixer.getFilters([{ kinds: [1], ids, relations: ['author'] }], { timeout: Time.seconds(1) }); const statuses = await Promise.all(events1.map((event) => renderStatus(event, c.get('pubkey')))); return paginated(c, events1, statuses); diff --git a/src/controllers/api/search.ts b/src/controllers/api/search.ts index 15417c8..b0f71ce 100644 --- a/src/controllers/api/search.ts +++ b/src/controllers/api/search.ts @@ -1,6 +1,7 @@ import { AppController } from '@/app.ts'; import * as eventsDB from '@/db/events.ts'; -import { type Event, type Filter, nip19, z } from '@/deps.ts'; +import { type Event, nip19, z } from '@/deps.ts'; +import { type DittoFilter } from '@/filter.ts'; import * as mixer from '@/mixer.ts'; import { booleanParamSchema } from '@/schema.ts'; import { nostrIdSchema } from '@/schemas/nostr.ts'; @@ -65,9 +66,10 @@ const searchController: AppController = async (c) => { function searchEvents({ q, type, limit, account_id }: SearchQuery): Promise { if (type === 'hashtags') return Promise.resolve([]); - const filter: Filter = { + const filter: DittoFilter = { kinds: typeToKinds(type), search: q, + relations: ['author'], limit, }; @@ -98,8 +100,8 @@ async function lookupEvent(query: SearchQuery): Promise { } /** Get filters to lookup the input value. */ -async function getLookupFilters({ q, type, resolve }: SearchQuery): Promise { - const filters: Filter[] = []; +async function getLookupFilters({ q, type, resolve }: SearchQuery): Promise { + const filters: DittoFilter[] = []; const accounts = !type || type === 'accounts'; const statuses = !type || type === 'statuses'; @@ -138,7 +140,7 @@ async function getLookupFilters({ q, type, resolve }: SearchQuery): Promise ({ ...filter, relations: ['author'] })); } export { searchController }; diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index 0f6be9c..114923d 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -1,7 +1,7 @@ import { type AppController } from '@/app.ts'; import { getUnattachedMediaByIds } from '@/db/unattached-media.ts'; import { type Event, ISO6391, z } from '@/deps.ts'; -import { getAncestors, getDescendants, getEvent } from '@/queries.ts'; +import { getAncestors, getAuthor, getDescendants, getEvent } from '@/queries.ts'; import { createEvent, paginationSchema, parseBody } from '@/utils/web.ts'; import { renderEventAccounts } from '@/views.ts'; import { renderStatus } from '@/views/mastodon/statuses.ts'; @@ -29,7 +29,7 @@ const createStatusSchema = z.object({ const statusController: AppController = async (c) => { const id = c.req.param('id'); - const event = await getEvent(id, { kind: 1 }); + const event = await getEvent(id, { kind: 1, relations: ['author'] }); if (event) { return c.json(await renderStatus(event, c.get('pubkey'))); } @@ -83,12 +83,13 @@ const createStatusController: AppController = async (c) => { tags, }, c); - return c.json(await renderStatus(event, c.get('pubkey'))); + const author = await getAuthor(event.pubkey); + return c.json(await renderStatus({ ...event, author }, c.get('pubkey'))); }; const contextController: AppController = async (c) => { const id = c.req.param('id'); - const event = await getEvent(id, { kind: 1 }); + const event = await getEvent(id, { kind: 1, relations: ['author'] }); async function renderStatuses(events: Event<1>[]) { const statuses = await Promise.all(events.map((event) => renderStatus(event, c.get('pubkey')))); @@ -109,7 +110,7 @@ const contextController: AppController = async (c) => { const favouriteController: AppController = async (c) => { const id = c.req.param('id'); - const target = await getEvent(id, { kind: 1 }); + const target = await getEvent(id, { kind: 1, relations: ['author'] }); if (target) { await createEvent({ diff --git a/src/controllers/api/streaming.ts b/src/controllers/api/streaming.ts index d7aa677..8cdd7d4 100644 --- a/src/controllers/api/streaming.ts +++ b/src/controllers/api/streaming.ts @@ -1,7 +1,7 @@ import { type AppController } from '@/app.ts'; import { z } from '@/deps.ts'; import { type DittoFilter } from '@/filter.ts'; -import { getFeedPubkeys } from '@/queries.ts'; +import { getAuthor, getFeedPubkeys } from '@/queries.ts'; import { Sub } from '@/subs.ts'; import { bech32ToPubkey } from '@/utils.ts'; import { renderStatus } from '@/views/mastodon/statuses.ts'; @@ -63,7 +63,8 @@ const streamingController: AppController = (c) => { if (filter) { for await (const event of Sub.sub(socket, '1', [filter])) { - const status = await renderStatus(event, pubkey); + const author = await getAuthor(event.pubkey); + const status = await renderStatus({ ...event, author }, pubkey); if (status) { send('update', status); } diff --git a/src/controllers/api/timelines.ts b/src/controllers/api/timelines.ts index 712f741..d3ffcdc 100644 --- a/src/controllers/api/timelines.ts +++ b/src/controllers/api/timelines.ts @@ -34,7 +34,10 @@ const hashtagTimelineController: AppController = (c) => { /** Render statuses for timelines. */ async function renderStatuses(c: AppContext, filters: DittoFilter<1>[]) { - const events = await mixer.getFilters(filters, { timeout: Time.seconds(1) }); + const events = await mixer.getFilters( + filters.map((filter) => ({ ...filter, relations: ['author'] })), + { timeout: Time.seconds(1) }, + ); if (!events.length) { return c.json([]); diff --git a/src/db/events.ts b/src/db/events.ts index e3ac876..a941e65 100644 --- a/src/db/events.ts +++ b/src/db/events.ts @@ -304,4 +304,4 @@ function buildUserSearchContent(event: Event<0>): string { return [name, nip05, about].filter(Boolean).join('\n'); } -export { countFilters, deleteFilters, getFilters, insertEvent }; +export { countFilters, deleteFilters, type DittoEvent, getFilters, insertEvent }; diff --git a/src/filter.ts b/src/filter.ts index cb43e2c..e4e336f 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -44,4 +44,4 @@ function matchDittoFilters(filters: DittoFilter[], event: Event, data: EventData return false; } -export { type DittoFilter, type GetFiltersOpts, matchDittoFilters }; +export { type DittoFilter, type GetFiltersOpts, matchDittoFilters, type Relation }; diff --git a/src/queries.ts b/src/queries.ts index 95bbaab..de9bd33 100644 --- a/src/queries.ts +++ b/src/queries.ts @@ -1,5 +1,6 @@ import * as eventsDB from '@/db/events.ts'; -import { type Event, type Filter, findReplyTag } from '@/deps.ts'; +import { type Event, findReplyTag } from '@/deps.ts'; +import { type DittoFilter, type Relation } from '@/filter.ts'; import * as mixer from '@/mixer.ts'; interface GetEventOpts { @@ -7,6 +8,8 @@ interface GetEventOpts { timeout?: number; /** Event kind. */ kind?: K; + /** Relations to include on the event. */ + relations?: Relation[]; } /** Get a Nostr event by its ID. */ @@ -14,8 +17,8 @@ const getEvent = async ( id: string, opts: GetEventOpts = {}, ): Promise | undefined> => { - const { kind, timeout = 1000 } = opts; - const filter: Filter = { ids: [id], limit: 1 }; + const { kind, relations, timeout = 1000 } = opts; + const filter: DittoFilter = { ids: [id], relations, limit: 1 }; if (kind) { filter.kinds = [kind]; } @@ -57,7 +60,7 @@ async function getAncestors(event: Event<1>, result = [] as Event<1>[]): Promise const inReplyTo = replyTag ? replyTag[1] : undefined; if (inReplyTo) { - const parentEvent = await getEvent(inReplyTo, { kind: 1 }); + const parentEvent = await getEvent(inReplyTo, { kind: 1, relations: ['author'] }); if (parentEvent) { result.push(parentEvent); @@ -70,7 +73,7 @@ async function getAncestors(event: Event<1>, result = [] as Event<1>[]): Promise } function getDescendants(eventId: string): Promise[]> { - return mixer.getFilters([{ kinds: [1], '#e': [eventId] }], { limit: 200, timeout: 2000 }); + return mixer.getFilters([{ kinds: [1], '#e': [eventId], relations: ['author'] }], { limit: 200, timeout: 2000 }); } /** Returns whether the pubkey is followed by a local user. */ diff --git a/src/views/mastodon/notifications.ts b/src/views/mastodon/notifications.ts index 59e5665..0e6b3c4 100644 --- a/src/views/mastodon/notifications.ts +++ b/src/views/mastodon/notifications.ts @@ -1,4 +1,5 @@ import { type Event } from '@/deps.ts'; +import { getAuthor } from '@/queries.ts'; import { nostrDate } from '@/utils.ts'; import { accountFromPubkey } from '@/views/mastodon/accounts.ts'; import { renderStatus } from '@/views/mastodon/statuses.ts'; @@ -11,7 +12,8 @@ function renderNotification(event: Event, viewerPubkey?: string) { } async function renderNotificationMention(event: Event<1>, viewerPubkey?: string) { - const status = await renderStatus(event, viewerPubkey); + const author = await getAuthor(event.pubkey); + const status = await renderStatus({ ...event, author }, viewerPubkey); if (!status) return; return { diff --git a/src/views/mastodon/statuses.ts b/src/views/mastodon/statuses.ts index 131dcd6..86bdd13 100644 --- a/src/views/mastodon/statuses.ts +++ b/src/views/mastodon/statuses.ts @@ -2,7 +2,7 @@ import { isCWTag } from 'https://gitlab.com/soapbox-pub/mostr/-/raw/c67064aee5ad import { Conf } from '@/config.ts'; import * as eventsDB from '@/db/events.ts'; -import { type Event, findReplyTag, nip19 } from '@/deps.ts'; +import { findReplyTag, nip19 } from '@/deps.ts'; import { getMediaLinks, parseNoteContent } from '@/note.ts'; import { getAuthor } from '@/queries.ts'; import { jsonMediaDataSchema } from '@/schemas/nostr.ts'; @@ -12,10 +12,8 @@ import { accountFromPubkey, renderAccount } from '@/views/mastodon/accounts.ts'; import { DittoAttachment, renderAttachment } from '@/views/mastodon/attachments.ts'; import { renderEmojis } from '@/views/mastodon/emojis.ts'; -async function renderStatus(event: Event<1>, viewerPubkey?: string) { - const profile = await getAuthor(event.pubkey); - const account = profile ? await renderAccount(profile) : await accountFromPubkey(event.pubkey); - +async function renderStatus(event: eventsDB.DittoEvent<1>, viewerPubkey?: string) { + const account = event.author ? await renderAccount(event.author) : await accountFromPubkey(event.pubkey); const replyTag = findReplyTag(event); const mentionedPubkeys = [ From f50a78f978a082cc777371f6954a78db415470e9 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Wed, 6 Dec 2023 13:06:13 -0600 Subject: [PATCH 8/8] db/events: don't return `author` unless it exists --- src/db/events.ts | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/db/events.ts b/src/db/events.ts index a941e65..b8db69a 100644 --- a/src/db/events.ts +++ b/src/db/events.ts @@ -200,26 +200,31 @@ async function getFilters( query = query.limit(opts.limit); } - 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 - ? { + return (await query.execute()).map((row) => { + const event: DittoEvent = { + id: row.id, + kind: row.kind as K, + pubkey: row.pubkey, + content: row.content, + created_at: row.created_at, + tags: JSON.parse(row.tags), + sig: row.sig, + }; + + if (row.author_id) { + event.author = { id: row.author_id, - kind: row.author_kind!, + kind: row.author_kind! as 0, 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)); + }; + } + + return event; + }); } /** Delete events based on filters from the database. */