diff --git a/src/controllers/api/timelines.ts b/src/controllers/api/timelines.ts index 9a209f5..e633ce3 100644 --- a/src/controllers/api/timelines.ts +++ b/src/controllers/api/timelines.ts @@ -48,7 +48,9 @@ async function renderStatuses(c: AppContext, filters: NostrFilter[]) { const events = await eventsDB .query(filters, { signal }) - .then((events) => hydrateEvents({ events, relations: ['author'], storage: eventsDB, signal })); + .then((events) => + hydrateEvents({ events, relations: ['author', 'author_stats', 'event_stats'], storage: eventsDB, signal }) + ); if (!events.length) { return c.json([]); diff --git a/src/storages/hydrate.ts b/src/storages/hydrate.ts index 8d152f0..6a92d80 100644 --- a/src/storages/hydrate.ts +++ b/src/storages/hydrate.ts @@ -1,3 +1,4 @@ +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'; @@ -22,6 +23,12 @@ async function hydrateEvents(opts: HydrateEventOpts): Promise { case 'author': await hydrateAuthors({ events, storage, signal }); break; + case 'author_stats': + await hydrateAuthorStats(events); + break; + case 'event_stats': + await hydrateEventStats(events); + break; } } @@ -41,6 +48,48 @@ async function hydrateAuthors(opts: Omit): Promis return events; } +async function hydrateAuthorStats(events: DittoEvent[]): Promise { + const results = await db + .selectFrom('author_stats') + .selectAll() + .where('pubkey', 'in', events.map((event) => event.pubkey)) + .execute(); + + for (const event of events) { + const stat = results.find((result) => result.pubkey === event.pubkey); + if (stat) { + event.author_stats = { + followers_count: Math.max(stat.followers_count, 0) || 0, + following_count: Math.max(stat.following_count, 0) || 0, + notes_count: Math.max(stat.notes_count, 0) || 0, + }; + } + } + + return events; +} + +async function hydrateEventStats(events: DittoEvent[]): Promise { + const results = await db + .selectFrom('event_stats') + .selectAll() + .where('event_id', 'in', events.map((event) => event.id)) + .execute(); + + for (const event of events) { + const stat = results.find((result) => result.event_id === event.id); + if (stat) { + event.event_stats = { + replies_count: Math.max(stat.replies_count, 0) || 0, + reposts_count: Math.max(stat.reposts_count, 0) || 0, + reactions_count: Math.max(stat.reactions_count, 0) || 0, + }; + } + } + + return events; +} + /** Return a normalized event without any non-standard keys. */ function purifyEvent(event: NostrEvent): NostrEvent { return {