diff --git a/src/config.ts b/src/config.ts index e174c81..f007341 100644 --- a/src/config.ts +++ b/src/config.ts @@ -77,6 +77,10 @@ class Conf { static get testDatabaseUrl(): string { return Deno.env.get('TEST_DATABASE_URL') ?? 'memory://'; } + /** PGlite debug level. 0 disables logging. */ + static get pgliteDebug(): 0 | 1 | 2 | 3 | 4 | 5 { + return Number(Deno.env.get('PGLITE_DEBUG') || 0) as 0 | 1 | 2 | 3 | 4 | 5; + } static db = { /** Database query timeout configurations. */ timeouts: { diff --git a/src/controllers/api/accounts.ts b/src/controllers/api/accounts.ts index c946b69..e5037a0 100644 --- a/src/controllers/api/accounts.ts +++ b/src/controllers/api/accounts.ts @@ -381,7 +381,7 @@ const followersController: AppController = (c) => { const followingController: AppController = async (c) => { const pubkey = c.req.param('pubkey'); const pubkeys = await getFollowedPubkeys(pubkey); - return renderAccounts(c, pubkeys); + return renderAccounts(c, [...pubkeys]); }; /** https://docs.joinmastodon.org/methods/accounts/#block */ @@ -460,7 +460,7 @@ const familiarFollowersController: AppController = async (c) => { const follows = await getFollowedPubkeys(pubkey); const results = await Promise.all(ids.map(async (id) => { - const followLists = await store.query([{ kinds: [3], authors: follows, '#p': [id] }]) + const followLists = await store.query([{ kinds: [3], authors: [...follows], '#p': [id] }]) .then((events) => hydrateEvents({ events, store })); const accounts = await Promise.all( diff --git a/src/controllers/api/streaming.ts b/src/controllers/api/streaming.ts index 23a9440..adee37b 100644 --- a/src/controllers/api/streaming.ts +++ b/src/controllers/api/streaming.ts @@ -215,7 +215,7 @@ async function topicToFilter( // HACK: this puts the user's entire contacts list into RAM, // and then calls `matchFilters` over it. Refreshing the page // is required after following a new user. - return pubkey ? { kinds: [1, 6, 9735], authors: await getFeedPubkeys(pubkey) } : undefined; + return pubkey ? { kinds: [1, 6], authors: [...await getFeedPubkeys(pubkey)] } : undefined; } } diff --git a/src/controllers/api/timelines.ts b/src/controllers/api/timelines.ts index 5fce460..483676e 100644 --- a/src/controllers/api/timelines.ts +++ b/src/controllers/api/timelines.ts @@ -13,7 +13,7 @@ import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts'; const homeTimelineController: AppController = async (c) => { const params = c.get('pagination'); const pubkey = await c.get('signer')?.getPublicKey()!; - const authors = await getFeedPubkeys(pubkey); + const authors = [...await getFeedPubkeys(pubkey)]; return renderStatuses(c, [{ authors, kinds: [1, 6], ...params }]); }; diff --git a/src/db/DittoDB.ts b/src/db/DittoDB.ts index 445c3da..923a109 100644 --- a/src/db/DittoDB.ts +++ b/src/db/DittoDB.ts @@ -16,7 +16,7 @@ export class DittoDB { switch (protocol) { case 'file:': case 'memory:': - return DittoPglite.create(databaseUrl); + return DittoPglite.create(databaseUrl, opts); case 'postgres:': case 'postgresql:': return DittoPostgres.create(databaseUrl, opts); diff --git a/src/db/DittoDatabase.ts b/src/db/DittoDatabase.ts index 530d939..ec9a103 100644 --- a/src/db/DittoDatabase.ts +++ b/src/db/DittoDatabase.ts @@ -10,4 +10,5 @@ export interface DittoDatabase { export interface DittoDatabaseOpts { poolSize?: number; + debug?: 0 | 1 | 2 | 3 | 4 | 5; } diff --git a/src/db/adapters/DittoPglite.ts b/src/db/adapters/DittoPglite.ts index 0e93075..2455fc3 100644 --- a/src/db/adapters/DittoPglite.ts +++ b/src/db/adapters/DittoPglite.ts @@ -3,15 +3,18 @@ import { pg_trgm } from '@electric-sql/pglite/contrib/pg_trgm'; import { PgliteDialect } from '@soapbox/kysely-pglite'; import { Kysely } from 'kysely'; -import { DittoDatabase } from '@/db/DittoDatabase.ts'; +import { DittoDatabase, DittoDatabaseOpts } from '@/db/DittoDatabase.ts'; import { DittoTables } from '@/db/DittoTables.ts'; import { KyselyLogger } from '@/db/KyselyLogger.ts'; export class DittoPglite { - static create(databaseUrl: string): DittoDatabase { + static create(databaseUrl: string, opts?: DittoDatabaseOpts): DittoDatabase { const kysely = new Kysely({ dialect: new PgliteDialect({ - database: new PGlite(databaseUrl, { extensions: { pg_trgm } }), + database: new PGlite(databaseUrl, { + extensions: { pg_trgm }, + debug: opts?.debug, + }), }), log: KyselyLogger, }); diff --git a/src/pipeline.ts b/src/pipeline.ts index 8ca7ae5..aaa6ca0 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -40,9 +40,14 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise ${event.id}`); pipelineEventsCounter.inc({ kind: event.kind }); + if (isProtectedEvent(event)) { + throw new RelayError('invalid', 'protected event'); + } + if (event.kind !== 24133) { await policyFilter(event); } @@ -103,6 +108,11 @@ async function existsInDB(event: DittoEvent): Promise { return events.length > 0; } +/** Check whether the event has a NIP-70 `-` tag. */ +function isProtectedEvent(event: NostrEvent): boolean { + return event.tags.some(([name]) => name === '-'); +} + /** Hydrate the event with the user, if applicable. */ async function hydrateEvent(event: DittoEvent, signal: AbortSignal): Promise { await hydrateEvents({ events: [event], store: await Storages.db(), signal }); diff --git a/src/queries.ts b/src/queries.ts index 9ee86a3..3ca805a 100644 --- a/src/queries.ts +++ b/src/queries.ts @@ -56,16 +56,16 @@ const getFollows = async (pubkey: string, signal?: AbortSignal): Promise { +async function getFollowedPubkeys(pubkey: string, signal?: AbortSignal): Promise> { const event = await getFollows(pubkey, signal); - if (!event) return []; - return [...getTagSet(event.tags, 'p')]; + if (!event) return new Set(); + return getTagSet(event.tags, 'p'); } /** Get pubkeys the user follows, including the user's own pubkey. */ -async function getFeedPubkeys(pubkey: string): Promise { +async function getFeedPubkeys(pubkey: string): Promise> { const authors = await getFollowedPubkeys(pubkey); - return [...authors, pubkey]; + return authors.add(pubkey); } async function getAncestors(store: NStore, event: NostrEvent, result: NostrEvent[] = []): Promise { diff --git a/src/storages.ts b/src/storages.ts index cbafd5a..073b613 100644 --- a/src/storages.ts +++ b/src/storages.ts @@ -21,7 +21,10 @@ export class Storages { public static async database(): Promise { if (!this._database) { this._database = (async () => { - const db = DittoDB.create(Conf.databaseUrl, { poolSize: Conf.pg.poolSize }); + const db = DittoDB.create(Conf.databaseUrl, { + poolSize: Conf.pg.poolSize, + debug: Conf.pgliteDebug, + }); await DittoDB.migrate(db.kysely); return db; })();