Improve EventsDB error handling
This commit is contained in:
parent
2ede439005
commit
4df2c7ba9c
|
@ -122,17 +122,8 @@ async function storeEvent(event: DittoEvent, signal?: AbortSignal): Promise<void
|
|||
if (NKinds.ephemeral(event.kind)) return;
|
||||
const store = await Storages.db();
|
||||
|
||||
const [deletion] = await store.query(
|
||||
[{ kinds: [5], authors: [Conf.pubkey, event.pubkey], '#e': [event.id], limit: 1 }],
|
||||
{ signal },
|
||||
);
|
||||
|
||||
if (deletion) {
|
||||
return Promise.reject(new RelayError('blocked', 'event was deleted'));
|
||||
} else {
|
||||
await updateStats(event).catch(debug);
|
||||
await store.event(event, { signal }).catch(debug);
|
||||
}
|
||||
await updateStats(event).catch(debug);
|
||||
await store.event(event, { signal });
|
||||
}
|
||||
|
||||
/** Parse kind 0 metadata and track indexes in the database. */
|
||||
|
|
|
@ -6,10 +6,13 @@ import { Kysely } from 'kysely';
|
|||
import { Conf } from '@/config.ts';
|
||||
import { DittoDB } from '@/db/DittoDB.ts';
|
||||
import { DittoTables } from '@/db/DittoTables.ts';
|
||||
import { RelayError } from '@/RelayError.ts';
|
||||
import { EventsDB } from '@/storages/EventsDB.ts';
|
||||
import { genEvent } from '@/test.ts';
|
||||
|
||||
import event0 from '~/fixtures/events/event-0.json' with { type: 'json' };
|
||||
import event1 from '~/fixtures/events/event-1.json' with { type: 'json' };
|
||||
import { generateSecretKey } from 'nostr-tools';
|
||||
|
||||
/** Create in-memory database for testing. */
|
||||
const createDB = async () => {
|
||||
|
@ -140,16 +143,51 @@ Deno.test('admin can delete any event', async () => {
|
|||
assertEquals(await eventsDB.query([{ kinds: [1] }]), [two]);
|
||||
});
|
||||
|
||||
Deno.test('throws a RelayError when inserting an event deleted by the admin', async () => {
|
||||
const { eventsDB } = await createDB();
|
||||
|
||||
const event = genEvent();
|
||||
await eventsDB.event(event);
|
||||
|
||||
const deletion = genEvent({ kind: 5, tags: [['e', event.id]] }, Conf.seckey);
|
||||
await eventsDB.event(deletion);
|
||||
|
||||
await assertRejects(
|
||||
() => eventsDB.event(event),
|
||||
RelayError,
|
||||
'event deleted by admin',
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test('throws a RelayError when inserting an event deleted by a user', async () => {
|
||||
const { eventsDB } = await createDB();
|
||||
|
||||
const sk = generateSecretKey();
|
||||
|
||||
const event = genEvent({}, sk);
|
||||
await eventsDB.event(event);
|
||||
|
||||
const deletion = genEvent({ kind: 5, tags: [['e', event.id]] }, sk);
|
||||
await eventsDB.event(deletion);
|
||||
|
||||
await assertRejects(
|
||||
() => eventsDB.event(event),
|
||||
RelayError,
|
||||
'event deleted by user',
|
||||
);
|
||||
});
|
||||
|
||||
Deno.test('inserting replaceable events', async () => {
|
||||
const { eventsDB } = await createDB();
|
||||
|
||||
assertEquals((await eventsDB.count([{ kinds: [0], authors: [event0.pubkey] }])).count, 0);
|
||||
const event = event0;
|
||||
await eventsDB.event(event);
|
||||
|
||||
await eventsDB.event(event0);
|
||||
await assertRejects(() => eventsDB.event(event0));
|
||||
assertEquals((await eventsDB.count([{ kinds: [0], authors: [event0.pubkey] }])).count, 1);
|
||||
const olderEvent = { ...event, id: '123', created_at: event.created_at - 1 };
|
||||
await eventsDB.event(olderEvent);
|
||||
assertEquals(await eventsDB.query([{ kinds: [0], authors: [event.pubkey] }]), [event]);
|
||||
|
||||
const changeEvent = { ...event0, id: '123', created_at: event0.created_at + 1 };
|
||||
await eventsDB.event(changeEvent);
|
||||
assertEquals(await eventsDB.query([{ kinds: [0] }]), [changeEvent]);
|
||||
const newerEvent = { ...event, id: '123', created_at: event.created_at + 1 };
|
||||
await eventsDB.event(newerEvent);
|
||||
assertEquals(await eventsDB.query([{ kinds: [0] }]), [newerEvent]);
|
||||
});
|
||||
|
|
|
@ -11,6 +11,7 @@ import { purifyEvent } from '@/storages/hydrate.ts';
|
|||
import { getTagSet } from '@/tags.ts';
|
||||
import { isNostrId, isURL } from '@/utils.ts';
|
||||
import { abortError } from '@/utils/abort.ts';
|
||||
import { RelayError } from '@/RelayError.ts';
|
||||
|
||||
/** Function to decide whether or not to index a tag. */
|
||||
type TagCondition = ({ event, count, value }: {
|
||||
|
@ -52,12 +53,36 @@ class EventsDB implements NStore {
|
|||
async event(event: NostrEvent, _opts?: { signal?: AbortSignal }): Promise<void> {
|
||||
event = purifyEvent(event);
|
||||
this.console.debug('EVENT', JSON.stringify(event));
|
||||
|
||||
if (await this.isDeletedAdmin(event)) {
|
||||
throw new RelayError('blocked', 'event deleted by admin');
|
||||
}
|
||||
|
||||
await this.deleteEventsAdmin(event);
|
||||
return this.store.event(event);
|
||||
|
||||
try {
|
||||
await this.store.event(event);
|
||||
} catch (e) {
|
||||
if (e.message === 'Cannot add a deleted event') {
|
||||
throw new RelayError('blocked', 'event deleted by user');
|
||||
} else if (e.message === 'Cannot replace an event with an older event') {
|
||||
return;
|
||||
} else {
|
||||
this.console.debug('ERROR', e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Check if an event has been deleted by the admin. */
|
||||
private async isDeletedAdmin(event: NostrEvent): Promise<boolean> {
|
||||
const [deletion] = await this.query([
|
||||
{ kinds: [5], authors: [Conf.pubkey], '#e': [event.id], limit: 1 },
|
||||
]);
|
||||
return !!deletion;
|
||||
}
|
||||
|
||||
/** The DITTO_NSEC can delete any event from the database. NDatabase already handles user deletions. */
|
||||
async deleteEventsAdmin(event: NostrEvent): Promise<void> {
|
||||
private 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] }]);
|
||||
|
|
16
src/test.ts
16
src/test.ts
|
@ -1,7 +1,23 @@
|
|||
import { NostrEvent } from '@nostrify/nostrify';
|
||||
import { finalizeEvent, generateSecretKey } from 'nostr-tools';
|
||||
|
||||
import { purifyEvent } from '@/storages/hydrate.ts';
|
||||
|
||||
/** Import an event fixture by name in tests. */
|
||||
export async function eventFixture(name: string): Promise<NostrEvent> {
|
||||
const result = await import(`~/fixtures/events/${name}.json`, { with: { type: 'json' } });
|
||||
return structuredClone(result.default);
|
||||
}
|
||||
|
||||
/** Generate an event for use in tests. */
|
||||
export function genEvent(t: Partial<NostrEvent> = {}, sk: Uint8Array = generateSecretKey()): NostrEvent {
|
||||
const event = finalizeEvent({
|
||||
kind: 255,
|
||||
created_at: 0,
|
||||
content: '',
|
||||
tags: [],
|
||||
...t,
|
||||
}, sk);
|
||||
|
||||
return purifyEvent(event);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue