diff --git a/src/db/DittoTables.ts b/src/db/DittoTables.ts index ec21170e..dade81ae 100644 --- a/src/db/DittoTables.ts +++ b/src/db/DittoTables.ts @@ -14,6 +14,7 @@ export interface DittoTables extends NPostgresSchema { type NostrEventsRow = NPostgresSchema['nostr_events'] & { language: string | null; + mime_type: string | null; }; interface AuthorStatsRow { diff --git a/src/db/migrations/042_add_mime_type.ts b/src/db/migrations/042_add_mime_type.ts new file mode 100644 index 00000000..1878ebb1 --- /dev/null +++ b/src/db/migrations/042_add_mime_type.ts @@ -0,0 +1,40 @@ +import { Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + await db.schema + .alterTable('nostr_events') + .addColumn('mime_type', 'text').execute(); + + await db.schema + .createIndex('nostr_events_mime_type_prefix_idx') + .on('nostr_events') + .expression(sql`split_part(mime_type, '/', 1)`) + .column('mime_type') + .ifNotExists() + .execute(); + + await db.schema + .createIndex('nostr_events_mime_type_hash_idx') + .on('nostr_events') + .column('mime_type') + .using('hash') + .ifNotExists() + .execute(); +} + +export async function down(db: Kysely): Promise { + await db.schema + .alterTable('nostr_events') + .dropColumn('mime_type') + .execute(); + + await db.schema + .dropIndex('nostr_events_mime_type_prefix_idx') + .on('nostr_events') + .execute(); + + await db.schema + .dropIndex('nostr_events_mime_type_hash_idx') + .on('nostr_events') + .execute(); +} diff --git a/src/pipeline.ts b/src/pipeline.ts index 5becff20..1dba2d9a 100644 --- a/src/pipeline.ts +++ b/src/pipeline.ts @@ -103,6 +103,7 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise @@ -248,6 +249,25 @@ async function setLanguage(event: NostrEvent): Promise { } } +/** Update the event in the database and set its MIME type. */ +async function setMimeType(event: NostrEvent): Promise { + const imeta = event.tags.find(([value]) => value === 'imeta'); + if (!imeta) return; + + const mime_type = imeta.find((value) => value?.split(' ')[0] === 'm')?.split(' ')[1]; + if (!mime_type) return; + + const kysely = await Storages.kysely(); + try { + await kysely.updateTable('nostr_events') + .set('mime_type', mime_type) + .where('id', '=', event.id) + .execute(); + } catch { + // do nothing + } +} + /** Determine if the event is being received in a timely manner. */ function isFresh(event: NostrEvent): boolean { return eventAge(event) < Time.minutes(1); diff --git a/src/storages/EventsDB.ts b/src/storages/EventsDB.ts index 6dccdcb2..2e0bc6f6 100644 --- a/src/storages/EventsDB.ts +++ b/src/storages/EventsDB.ts @@ -158,16 +158,41 @@ class EventsDB extends NPostgres { }) as SelectQueryBuilder; const languages = new Set(); + let exact_mime_type: string | undefined; + let partial_mime_type: string | undefined; + let only_media: boolean | undefined; for (const token of tokens) { if (typeof token === 'object' && token.key === 'language') { languages.add(token.value); } + if (typeof token === 'object' && token.key === 'exact_mime_type') { + exact_mime_type = token.value; + } + if (typeof token === 'object' && token.key === 'partial_mime_type') { + partial_mime_type = token.value; + } + if (typeof token === 'object' && token.key === 'only_media') { + if (token.value === 'true') only_media = true; + if (token.value === 'false') only_media = false; + } } if (languages.size) { query = query.where('language', 'in', [...languages]); } + if (exact_mime_type) { + query = query.where('mime_type', '=', exact_mime_type); + } + if (partial_mime_type) { + query = query.where( + (eb) => eb.fn('split_part', [eb.ref('mime_type'), eb.val('/'), eb.val(1)]), + '=', + partial_mime_type, + ); + } + if (only_media) query = query.where('mime_type', 'is not', null); + if (only_media === false) query = query.where('mime_type', 'is', null); return query; }