Upgrade Nostrify to v0.19.1, fix phantom deletions

This commit is contained in:
Alex Gleason 2024-05-16 09:27:22 -05:00
parent b2c26c9374
commit 2ede439005
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
5 changed files with 110 additions and 31 deletions

View File

@ -21,7 +21,7 @@
"@db/sqlite": "jsr:@db/sqlite@^0.11.1",
"@isaacs/ttlcache": "npm:@isaacs/ttlcache@^1.4.1",
"@noble/secp256k1": "npm:@noble/secp256k1@^2.0.0",
"@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.19.0",
"@nostrify/nostrify": "jsr:@nostrify/nostrify@^0.19.1",
"@sentry/deno": "https://deno.land/x/sentry@7.112.2/index.mjs",
"@soapbox/kysely-deno-sqlite": "jsr:@soapbox/kysely-deno-sqlite@^2.1.0",
"@soapbox/stickynotes": "jsr:@soapbox/stickynotes@^0.4.0",

View File

@ -41,7 +41,7 @@ export class DittoDB {
}
/** Migrate the database to the latest version. */
private static async migrate(kysely: Kysely<DittoTables>) {
static async migrate(kysely: Kysely<DittoTables>) {
const migrator = new Migrator({
db: kysely,
provider: new FileMigrationProvider({

View File

@ -45,7 +45,6 @@ async function handleEvent(event: DittoEvent, signal: AbortSignal): Promise<void
await Promise.all([
storeEvent(event, signal),
parseMetadata(event, signal),
processDeletions(event, signal),
DVM.event(event),
trackHashtags(event),
fetchRelatedEvents(event),
@ -174,26 +173,6 @@ async function parseMetadata(event: NostrEvent, signal: AbortSignal): Promise<vo
}
}
/** Query to-be-deleted events, ensure their pubkey matches, then delete them from the database. */
async function processDeletions(event: NostrEvent, signal: AbortSignal): Promise<void> {
if (event.kind === 5) {
const ids = getTagSet(event.tags, 'e');
const store = await Storages.db();
if (event.pubkey === Conf.pubkey) {
await store.remove([{ ids: [...ids] }], { signal });
} else {
const events = await store.query(
[{ ids: [...ids], authors: [event.pubkey] }],
{ signal },
);
const deleteIds = events.map(({ id }) => id);
await store.remove([{ ids: deleteIds }], { signal });
}
}
}
/** Track whenever a hashtag is used, for processing trending tags. */
async function trackHashtags(event: NostrEvent): Promise<void> {
const date = nostrDate(event.created_at);

View File

@ -1,22 +1,39 @@
import { Database as Sqlite } from '@db/sqlite';
import { DenoSqlite3Dialect } from '@soapbox/kysely-deno-sqlite';
import { assertEquals, assertRejects } from '@std/assert';
import { Kysely } from 'kysely';
import { Conf } from '@/config.ts';
import { DittoDB } from '@/db/DittoDB.ts';
import { DittoTables } from '@/db/DittoTables.ts';
import { EventsDB } from '@/storages/EventsDB.ts';
import event0 from '~/fixtures/events/event-0.json' with { type: 'json' };
import event1 from '~/fixtures/events/event-1.json' with { type: 'json' };
import { EventsDB } from '@/storages/EventsDB.ts';
const kysely = await DittoDB.getInstance();
const eventsDB = new EventsDB(kysely);
/** Create in-memory database for testing. */
const createDB = async () => {
const kysely = new Kysely<DittoTables>({
dialect: new DenoSqlite3Dialect({
database: new Sqlite(':memory:'),
}),
});
const eventsDB = new EventsDB(kysely);
await DittoDB.migrate(kysely);
return { eventsDB, kysely };
};
Deno.test('count filters', async () => {
const { eventsDB } = await createDB();
assertEquals((await eventsDB.count([{ kinds: [1] }])).count, 0);
await eventsDB.event(event1);
assertEquals((await eventsDB.count([{ kinds: [1] }])).count, 1);
});
Deno.test('insert and filter events', async () => {
const { eventsDB } = await createDB();
await eventsDB.event(event1);
assertEquals(await eventsDB.query([{ kinds: [1] }]), [event1]);
@ -30,6 +47,8 @@ Deno.test('insert and filter events', async () => {
});
Deno.test('query events with domain search filter', async () => {
const { eventsDB, kysely } = await createDB();
await eventsDB.event(event1);
assertEquals(await eventsDB.query([{}]), [event1]);
@ -46,13 +65,84 @@ Deno.test('query events with domain search filter', async () => {
});
Deno.test('delete events', async () => {
await eventsDB.event(event1);
assertEquals(await eventsDB.query([{ kinds: [1] }]), [event1]);
await eventsDB.remove([{ kinds: [1] }]);
assertEquals(await eventsDB.query([{ kinds: [1] }]), []);
const { eventsDB } = await createDB();
const [one, two] = [
{ id: '1', kind: 1, pubkey: 'abc', content: 'hello world', created_at: 1, sig: '', tags: [] },
{ id: '2', kind: 1, pubkey: 'abc', content: 'yolo fam', created_at: 2, sig: '', tags: [] },
];
await eventsDB.event(one);
await eventsDB.event(two);
// Sanity check
assertEquals(await eventsDB.query([{ kinds: [1] }]), [two, one]);
await eventsDB.event({
kind: 5,
pubkey: one.pubkey,
tags: [['e', one.id]],
created_at: 0,
content: '',
id: '',
sig: '',
});
assertEquals(await eventsDB.query([{ kinds: [1] }]), [two]);
});
Deno.test("user cannot delete another user's event", async () => {
const { eventsDB } = await createDB();
const event = { id: '1', kind: 1, pubkey: 'abc', content: 'hello world', created_at: 1, sig: '', tags: [] };
await eventsDB.event(event);
// Sanity check
assertEquals(await eventsDB.query([{ kinds: [1] }]), [event]);
await eventsDB.event({
kind: 5,
pubkey: 'def', // different pubkey
tags: [['e', event.id]],
created_at: 0,
content: '',
id: '',
sig: '',
});
assertEquals(await eventsDB.query([{ kinds: [1] }]), [event]);
});
Deno.test('admin can delete any event', async () => {
const { eventsDB } = await createDB();
const [one, two] = [
{ id: '1', kind: 1, pubkey: 'abc', content: 'hello world', created_at: 1, sig: '', tags: [] },
{ id: '2', kind: 1, pubkey: 'abc', content: 'yolo fam', created_at: 2, sig: '', tags: [] },
];
await eventsDB.event(one);
await eventsDB.event(two);
// Sanity check
assertEquals(await eventsDB.query([{ kinds: [1] }]), [two, one]);
await eventsDB.event({
kind: 5,
pubkey: Conf.pubkey, // Admin pubkey
tags: [['e', one.id]],
created_at: 0,
content: '',
id: '',
sig: '',
});
assertEquals(await eventsDB.query([{ kinds: [1] }]), [two]);
});
Deno.test('inserting replaceable events', async () => {
const { eventsDB } = await createDB();
assertEquals((await eventsDB.count([{ kinds: [0], authors: [event0.pubkey] }])).count, 0);
await eventsDB.event(event0);

View File

@ -8,6 +8,7 @@ import { Conf } from '@/config.ts';
import { DittoTables } from '@/db/DittoTables.ts';
import { normalizeFilters } from '@/filter.ts';
import { purifyEvent } from '@/storages/hydrate.ts';
import { getTagSet } from '@/tags.ts';
import { isNostrId, isURL } from '@/utils.ts';
import { abortError } from '@/utils/abort.ts';
@ -51,9 +52,18 @@ class EventsDB implements NStore {
async event(event: NostrEvent, _opts?: { signal?: AbortSignal }): Promise<void> {
event = purifyEvent(event);
this.console.debug('EVENT', JSON.stringify(event));
await this.deleteEventsAdmin(event);
return this.store.event(event);
}
/** The DITTO_NSEC can delete any event from the database. NDatabase already handles user deletions. */
async deleteEventsAdmin(event: NostrEvent): Promise<void> {
if (event.kind === 5 && event.pubkey === Conf.pubkey) {
const ids = getTagSet(event.tags, 'e');
await this.remove([{ ids: [...ids] }]);
}
}
/** Get events for filters from the database. */
async query(filters: NostrFilter[], opts: { signal?: AbortSignal; limit?: number } = {}): Promise<NostrEvent[]> {
filters = await this.expandFilters(filters);