feat: add mime_type column in nostr_events, add the following NIP 50 search extensions:

exact_mime_type, example: 'exact_mime_type:image/png' (uses hash index)
partial_mime_type, example 'partial_mime_type:image' (uses b-tree index)
only_media, example 'only_media:true' (sometimes uses index)
This commit is contained in:
P. Reis 2025-01-22 17:01:10 -03:00
parent 51fc0c9cc9
commit b037be44a4
4 changed files with 86 additions and 0 deletions

View file

@ -14,6 +14,7 @@ export interface DittoTables extends NPostgresSchema {
type NostrEventsRow = NPostgresSchema['nostr_events'] & { type NostrEventsRow = NPostgresSchema['nostr_events'] & {
language: string | null; language: string | null;
mime_type: string | null;
}; };
interface AuthorStatsRow { interface AuthorStatsRow {

View file

@ -0,0 +1,40 @@
import { Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
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<any>): Promise<void> {
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();
}

View file

@ -103,6 +103,7 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise<void
handleZaps(kysely, event), handleZaps(kysely, event),
parseMetadata(event, signal), parseMetadata(event, signal),
setLanguage(event), setLanguage(event),
setMimeType(event),
generateSetEvents(event), generateSetEvents(event),
]) ])
.then(() => .then(() =>
@ -248,6 +249,25 @@ async function setLanguage(event: NostrEvent): Promise<void> {
} }
} }
/** Update the event in the database and set its MIME type. */
async function setMimeType(event: NostrEvent): Promise<void> {
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. */ /** Determine if the event is being received in a timely manner. */
function isFresh(event: NostrEvent): boolean { function isFresh(event: NostrEvent): boolean {
return eventAge(event) < Time.minutes(1); return eventAge(event) < Time.minutes(1);

View file

@ -158,16 +158,41 @@ class EventsDB extends NPostgres {
}) as SelectQueryBuilder<DittoTables, 'nostr_events', DittoTables['nostr_events']>; }) as SelectQueryBuilder<DittoTables, 'nostr_events', DittoTables['nostr_events']>;
const languages = new Set<string>(); const languages = new Set<string>();
let exact_mime_type: string | undefined;
let partial_mime_type: string | undefined;
let only_media: boolean | undefined;
for (const token of tokens) { for (const token of tokens) {
if (typeof token === 'object' && token.key === 'language') { if (typeof token === 'object' && token.key === 'language') {
languages.add(token.value); 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) { if (languages.size) {
query = query.where('language', 'in', [...languages]); 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; return query;
} }