From 0f4c76c7615be9963e098cb9840a49b6cc6e6ed0 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 12 Apr 2024 14:26:45 -0300 Subject: [PATCH 1/5] refactor(fetch test): resolve import specifier via the active import map --- src/workers/fetch.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/workers/fetch.test.ts b/src/workers/fetch.test.ts index d9a6102..e7283b7 100644 --- a/src/workers/fetch.test.ts +++ b/src/workers/fetch.test.ts @@ -1,6 +1,6 @@ import { assertEquals, assertRejects } from '@/deps-test.ts'; -import { fetchWorker } from './fetch.ts'; +import { fetchWorker } from '@/workers/fetch.ts'; Deno.test({ name: 'fetchWorker', From 581ecd67d6ab1a14ea223536525f9998a2c686d7 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 12 Apr 2024 21:34:32 -0300 Subject: [PATCH 2/5] refactor(server): resolve import specifier via the active import map --- src/server.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server.ts b/src/server.ts index 2a1906b..f433cde 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,5 +1,5 @@ -import './precheck.ts'; -import './sentry.ts'; -import app from './app.ts'; +import '@/precheck.ts'; +import '@/sentry.ts'; +import app from '@/app.ts'; Deno.serve(app.fetch); From 24efca5ea0ad6b265463c51b70b6e3e483587ce1 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Fri, 12 Apr 2024 21:51:57 -0300 Subject: [PATCH 3/5] perf: hydrate repost event in home timeline --- src/controllers/api/statuses.ts | 2 +- src/controllers/api/streaming.ts | 2 +- src/controllers/api/timelines.ts | 9 +++++++-- src/storages/hydrate.ts | 22 ++++++++++++++++++++++ src/views/mastodon/statuses.ts | 12 ++++++++++-- 5 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index afd3673..66018ac 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -226,7 +226,7 @@ const reblogStatusController: AppController = async (c) => { tags: [['e', event.id], ['p', event.pubkey]], }, c); - const status = await renderReblog(reblogEvent); + const status = await renderReblog(reblogEvent, { loadOriginalPostEvent: true }); return c.json(status); }; diff --git a/src/controllers/api/streaming.ts b/src/controllers/api/streaming.ts index ed6ccdd..becf46a 100644 --- a/src/controllers/api/streaming.ts +++ b/src/controllers/api/streaming.ts @@ -64,7 +64,7 @@ const streamingController: AppController = (c) => { if (filter) { for await (const event of Sub.sub(socket, '1', [filter])) { if (event.kind === 6) { - const status = await renderReblog(event); + const status = await renderReblog(event, { loadOriginalPostEvent: true }); if (status) { send('update', status); } diff --git a/src/controllers/api/timelines.ts b/src/controllers/api/timelines.ts index 677bc60..0809835 100644 --- a/src/controllers/api/timelines.ts +++ b/src/controllers/api/timelines.ts @@ -49,7 +49,12 @@ async function renderStatuses(c: AppContext, filters: NostrFilter[]) { const events = await eventsDB .query(filters, { signal }) .then((events) => - hydrateEvents({ events, relations: ['author', 'author_stats', 'event_stats'], storage: eventsDB, signal }) + hydrateEvents({ + events, + relations: ['author', 'author_stats', 'event_stats', 'repost'], + storage: eventsDB, + signal, + }) ); if (!events.length) { @@ -58,7 +63,7 @@ async function renderStatuses(c: AppContext, filters: NostrFilter[]) { const statuses = (await Promise.all(events.map((event) => { if (event.kind === 6) { - return renderReblog(event); + return renderReblog(event, {}); } return renderStatus(event, c.get('pubkey')); }))).filter((boolean) => boolean); diff --git a/src/storages/hydrate.ts b/src/storages/hydrate.ts index e56ebe6..06cd37c 100644 --- a/src/storages/hydrate.ts +++ b/src/storages/hydrate.ts @@ -3,6 +3,7 @@ import { db } from '@/db.ts'; import { type NostrEvent, type NStore } from '@/deps.ts'; import { type DittoEvent } from '@/interfaces/DittoEvent.ts'; import { type DittoRelation } from '@/interfaces/DittoFilter.ts'; +import { eventsDB } from '@/storages.ts'; interface HydrateEventOpts { events: DittoEvent[]; @@ -33,6 +34,9 @@ async function hydrateEvents(opts: HydrateEventOpts): Promise { case 'user': await hydrateUsers({ events, storage, signal }); break; + case 'repost': + await hydrateRepostEvents(events); + break; } } @@ -111,6 +115,24 @@ async function hydrateEventStats(events: DittoEvent[]): Promise { return events; } +async function hydrateRepostEvents(events: DittoEvent[]): Promise { + const results = await eventsDB.query([{ kinds: [1], ids: events.map((event) => event.id) }]); + + for (const event of events) { + if (event.kind === 6) { + const originalPostId = event.tags.find(([name]) => name === 'e')?.[1]; + if (!originalPostId) continue; + + const originalPostEvent = results.find((event) => event.id === originalPostId); + if (!originalPostEvent) continue; + + event.repost = originalPostEvent; + } + } + + return events; +} + /** Return a normalized event without any non-standard keys. */ function purifyEvent(event: NostrEvent): NostrEvent { return { diff --git a/src/views/mastodon/statuses.ts b/src/views/mastodon/statuses.ts index 6762e82..b9ea949 100644 --- a/src/views/mastodon/statuses.ts +++ b/src/views/mastodon/statuses.ts @@ -100,13 +100,21 @@ async function renderStatus(event: DittoEvent, viewerPubkey?: string) { }; } -async function renderReblog(event: DittoEvent) { +type reblogOpts = { + loadOriginalPostEvent?: boolean; +}; + +async function renderReblog(event: DittoEvent, opts: reblogOpts) { + const { loadOriginalPostEvent } = opts; + if (!event.author) return; const repostId = event.tags.find(([name]) => name === 'e')?.[1]; if (!repostId) return; - event.repost = await getEvent(repostId, { kind: 1 }); + if (loadOriginalPostEvent) { + event.repost = await getEvent(repostId, { kind: 1 }); + } if (!event.repost) return; const reblog = await renderStatus(event.repost); From 37bee709cdaa4f12447f9078517fd6447ab52f87 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sat, 13 Apr 2024 18:16:15 -0300 Subject: [PATCH 4/5] perf: hydrate repost events in reblog endpoint & streaming --- src/controllers/api/statuses.ts | 11 ++++++++++- src/controllers/api/streaming.ts | 11 ++++++++++- src/controllers/api/timelines.ts | 2 +- src/storages/hydrate.ts | 19 +++++++++++++++---- src/views/mastodon/statuses.ts | 13 ++----------- 5 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index 66018ac..11f612c 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -12,6 +12,7 @@ import { getLnurl } from '@/utils/lnurl.ts'; import { nip05Cache } from '@/utils/nip05.ts'; import { asyncReplaceAll } from '@/utils/text.ts'; import { eventsDB } from '@/storages.ts'; +import { hydrateEvents } from '@/storages/hydrate.ts'; const createStatusSchema = z.object({ in_reply_to_id: z.string().regex(/[0-9a-f]{64}/).nullish(), @@ -212,6 +213,7 @@ const favouritedByController: AppController = (c) => { /** https://docs.joinmastodon.org/methods/statuses/#boost */ const reblogStatusController: AppController = async (c) => { const eventId = c.req.param('id'); + const { signal } = c.req.raw; const event = await getEvent(eventId, { kind: 1, @@ -226,7 +228,14 @@ const reblogStatusController: AppController = async (c) => { tags: [['e', event.id], ['p', event.pubkey]], }, c); - const status = await renderReblog(reblogEvent, { loadOriginalPostEvent: true }); + await hydrateEvents({ + events: [reblogEvent, event], + relations: ['repost', 'author'], + storage: eventsDB, + signal: signal, + }); + + const status = await renderReblog(reblogEvent); return c.json(status); }; diff --git a/src/controllers/api/streaming.ts b/src/controllers/api/streaming.ts index becf46a..643ac01 100644 --- a/src/controllers/api/streaming.ts +++ b/src/controllers/api/streaming.ts @@ -6,6 +6,8 @@ import { getFeedPubkeys } from '@/queries.ts'; import { Sub } from '@/subs.ts'; import { bech32ToPubkey } from '@/utils.ts'; import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts'; +import { hydrateEvents } from '@/storages/hydrate.ts'; +import { eventsDB } from '@/storages.ts'; const debug = Debug('ditto:streaming'); @@ -64,7 +66,14 @@ const streamingController: AppController = (c) => { if (filter) { for await (const event of Sub.sub(socket, '1', [filter])) { if (event.kind === 6) { - const status = await renderReblog(event, { loadOriginalPostEvent: true }); + await hydrateEvents({ + events: [event], + relations: ['repost', 'author'], + storage: eventsDB, + signal: AbortSignal.timeout(1000), + }); + + const status = await renderReblog(event); if (status) { send('update', status); } diff --git a/src/controllers/api/timelines.ts b/src/controllers/api/timelines.ts index 0809835..c54abd4 100644 --- a/src/controllers/api/timelines.ts +++ b/src/controllers/api/timelines.ts @@ -63,7 +63,7 @@ async function renderStatuses(c: AppContext, filters: NostrFilter[]) { const statuses = (await Promise.all(events.map((event) => { if (event.kind === 6) { - return renderReblog(event, {}); + return renderReblog(event); } return renderStatus(event, c.get('pubkey')); }))).filter((boolean) => boolean); diff --git a/src/storages/hydrate.ts b/src/storages/hydrate.ts index 06cd37c..0876f90 100644 --- a/src/storages/hydrate.ts +++ b/src/storages/hydrate.ts @@ -3,7 +3,6 @@ import { db } from '@/db.ts'; import { type NostrEvent, type NStore } from '@/deps.ts'; import { type DittoEvent } from '@/interfaces/DittoEvent.ts'; import { type DittoRelation } from '@/interfaces/DittoFilter.ts'; -import { eventsDB } from '@/storages.ts'; interface HydrateEventOpts { events: DittoEvent[]; @@ -35,7 +34,7 @@ async function hydrateEvents(opts: HydrateEventOpts): Promise { await hydrateUsers({ events, storage, signal }); break; case 'repost': - await hydrateRepostEvents(events); + await hydrateRepostEvents({ events, storage, signal }); break; } } @@ -115,8 +114,19 @@ async function hydrateEventStats(events: DittoEvent[]): Promise { return events; } -async function hydrateRepostEvents(events: DittoEvent[]): Promise { - const results = await eventsDB.query([{ kinds: [1], ids: events.map((event) => event.id) }]); +async function hydrateRepostEvents(opts: Omit): Promise { + const { events, storage, signal } = opts; + const results = await storage.query([{ + kinds: [1], + ids: events.map((event) => { + if (event.kind === 6) { + const originalPostId = event.tags.find(([name]) => name === 'e')?.[1]; + if (!originalPostId) return event.id; + else return originalPostId; + } + return event.id; + }), + }]); for (const event of events) { if (event.kind === 6) { @@ -126,6 +136,7 @@ async function hydrateRepostEvents(events: DittoEvent[]): Promise const originalPostEvent = results.find((event) => event.id === originalPostId); if (!originalPostEvent) continue; + await hydrateAuthors({ events: [originalPostEvent], storage: storage, signal: signal }); event.repost = originalPostEvent; } } diff --git a/src/views/mastodon/statuses.ts b/src/views/mastodon/statuses.ts index b9ea949..19ce381 100644 --- a/src/views/mastodon/statuses.ts +++ b/src/views/mastodon/statuses.ts @@ -4,7 +4,7 @@ import { Conf } from '@/config.ts'; import { nip19 } from '@/deps.ts'; import { type DittoEvent } from '@/interfaces/DittoEvent.ts'; import { getMediaLinks, parseNoteContent } from '@/note.ts'; -import { getAuthor, getEvent } from '@/queries.ts'; +import { getAuthor } from '@/queries.ts'; import { jsonMediaDataSchema } from '@/schemas/nostr.ts'; import { eventsDB } from '@/storages.ts'; import { findReplyTag } from '@/tags.ts'; @@ -100,21 +100,12 @@ async function renderStatus(event: DittoEvent, viewerPubkey?: string) { }; } -type reblogOpts = { - loadOriginalPostEvent?: boolean; -}; - -async function renderReblog(event: DittoEvent, opts: reblogOpts) { - const { loadOriginalPostEvent } = opts; - +async function renderReblog(event: DittoEvent) { if (!event.author) return; const repostId = event.tags.find(([name]) => name === 'e')?.[1]; if (!repostId) return; - if (loadOriginalPostEvent) { - event.repost = await getEvent(repostId, { kind: 1 }); - } if (!event.repost) return; const reblog = await renderStatus(event.repost); From 15853d79dfb3d37db347c3d7d78855f8ffc10af0 Mon Sep 17 00:00:00 2001 From: "P. Reis" Date: Sat, 13 Apr 2024 18:31:30 -0300 Subject: [PATCH 5/5] refactor: remove kind 1 event from hydrateEvents in reblog controller --- 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 11f612c..7e4a4c3 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -229,7 +229,7 @@ const reblogStatusController: AppController = async (c) => { }, c); await hydrateEvents({ - events: [reblogEvent, event], + events: [reblogEvent], relations: ['repost', 'author'], storage: eventsDB, signal: signal,