diff --git a/src/config.ts b/src/config.ts index e174c81a..f007341f 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 c946b697..e5037a02 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 23a94407..adee37b3 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 5fce4602..483676e1 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 445c3da2..923a109d 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 530d9391..ec9a103d 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 0e93075d..2455fc37 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 8ca7ae5f..aaa6ca07 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 9ee86a36..3ca805a8 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 cbafd5aa..073b6135 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; })();