feat: implement nip-09 event deletion

This commit is contained in:
Ricardo Arturo Cabral Mejia 2022-08-16 04:10:00 +00:00
parent 246e472fc4
commit 9efdb8d209
No known key found for this signature in database
GPG Key ID: 5931EBF43A650245
11 changed files with 70 additions and 9 deletions

View File

@ -14,7 +14,7 @@ NIPs with a relay-specific implementation are listed here.
- [ ] NIP-03: OpenTimestams Attestations for Events
- [x] NIP-04: Encrypted Direct Message
- [ ] NIP-05: Mapping Nostr keys to DNS identifiers
- [ ] NIP-09: Event deletion
- [x] NIP-09: Event deletion
- [x] NIP-11: Relay information document
- [x] NIP-12: Generic tag queries
- [ ] NIP-13: Proof of Work

View File

@ -3,7 +3,9 @@ import { Pubkey, TagName } from './base'
export type EventId = string
export interface Tag {
export type Tag = TagBase | []
export interface TagBase {
0: TagName
[index: number]: string
}

View File

@ -1,5 +1,6 @@
import { PassThrough } from 'stream'
import { DBEvent, Event } from './event'
import { Pubkey } from './base'
import { DBEvent, Event, EventId } from './event'
import { SubscriptionFilter } from './subscription'
export type ExposedPromiseKeys = 'then' | 'catch' | 'finally'
@ -12,4 +13,5 @@ export interface IEventRepository {
create(event: Event): Promise<number>
upsert(event: Event): Promise<number>
findByFilters(filters: SubscriptionFilter[]): IQueryResult<DBEvent[]>
deleteByPubkeyAndIds(pubkey: Pubkey, ids: EventId[]): Promise<number>
}

View File

@ -32,7 +32,7 @@ export class WebServerAdapter extends EventEmitter implements IWebServerAdapter
description,
pubkey,
contact,
supported_nips: [1, 2, 4, 11, 12, 15, 16],
supported_nips: [1, 2, 4, 9, 11, 12, 15, 16],
software: packageJson.repository.url,
version: packageJson.version,
}

View File

@ -7,3 +7,8 @@ export enum EventKinds {
DELETE = 5,
REACTION = 7,
}
export enum EventTags {
Event = 'e',
Pubkey = 'p',
}

View File

@ -1,13 +1,14 @@
import { DefaultEventStrategy } from '../handlers/event-strategies/default-event-strategy'
import { EphemeralEventStrategy } from '../handlers/event-strategies/ephemeral-event-strategy'
import { NullEventStrategy } from '../handlers/event-strategies/null-event-strategy copy'
import { NullEventStrategy } from '../handlers/event-strategies/null-event-strategy'
import { ReplaceableEventStrategy } from '../handlers/event-strategies/replaceable-event-strategy'
import { Factory } from '../@types/base'
import { Event } from '../@types/event'
import { IEventStrategy } from '../@types/message-handlers'
import { IEventRepository } from '../@types/repositories'
import { isEphemeralEvent, isNullEvent, isReplaceableEvent } from '../utils/event'
import { isDeleteEvent, isEphemeralEvent, isNullEvent, isReplaceableEvent } from '../utils/event'
import { IWebSocketAdapter } from '../@types/adapters'
import { DeleteEventStrategy } from '../handlers/event-strategies/delete-event-strategy'
export const eventStrategyFactory = (
@ -19,6 +20,8 @@ export const eventStrategyFactory = (
return new EphemeralEventStrategy(adapter)
} else if (isNullEvent(event)) {
return new NullEventStrategy()
} else if (isDeleteEvent(event)) {
return new DeleteEventStrategy(eventRepository)
}
return new DefaultEventStrategy(adapter, eventRepository)

View File

@ -0,0 +1,33 @@
import { Event } from '../../@types/event'
import { IEventStrategy } from '../../@types/message-handlers'
import { IEventRepository } from '../../@types/repositories'
import { EventTags } from '../../constants/base'
export class DeleteEventStrategy implements IEventStrategy<Event, Promise<boolean>> {
public constructor(
private readonly eventRepository: IEventRepository,
) { }
public async execute(event: Event): Promise<boolean> {
try {
const eTags = event.tags.filter((tag) => tag[0] === EventTags.Event)
if (!eTags.length) {
return
}
await this.eventRepository.deleteByPubkeyAndIds(
event.pubkey,
eTags.map((tag) => tag[1])
)
return
} catch (error) {
console.error('Unable to handle event. Reason:', error)
return false
}
}
}

View File

@ -1,7 +1,7 @@
import { Knex } from 'knex'
import { __, applySpec, equals, modulo, omit, pipe, prop, cond, always, groupBy, T, evolve, forEach, isEmpty, forEachObjIndexed, isNil, complement, toPairs, filter, nth, ifElse, invoker } from 'ramda'
import { __, applySpec, equals, modulo, omit, pipe, prop, cond, always, groupBy, T, evolve, forEach, isEmpty, forEachObjIndexed, isNil, complement, toPairs, filter, nth, ifElse, invoker, identity } from 'ramda'
import { DBEvent, Event } from '../@types/event'
import { DBEvent, Event, EventId } from '../@types/event'
import { IEventRepository, IQueryResult } from '../@types/repositories'
import { SubscriptionFilter } from '../@types/subscription'
import { isGenericTagQuery } from '../utils/filter'
@ -156,4 +156,15 @@ export class EventRepository implements IEventRepository {
.where('events.event_created_at', '<', row.event_created_at)
.then(prop('rowCount') as () => number)
}
public async deleteByPubkeyAndIds(pubkey: string, ids: EventId[]): Promise<number> {
const query = this.dbClient('events')
.where({
event_pubkey: pubkey,
})
.whereIn('event_id', ids)
.delete()
return query.then(identity)
}
}

View File

@ -3,6 +3,7 @@ import { applySpec, pipe, prop } from 'ramda'
import { CanonicalEvent, Event } from '../@types/event'
import { SubscriptionFilter } from '../@types/subscription'
import { EventKinds } from '../constants/base'
import { isGenericTagQuery } from './filter'
import { fromBuffer } from './transform'
@ -95,3 +96,7 @@ export const isEphemeralEvent = (event: Event): boolean => {
export const isNullEvent = (event: Event): boolean => {
return event.kind === Number.MAX_SAFE_INTEGER
}
export const isDeleteEvent = (event: Event): boolean => {
return event.kind === EventKinds.DELETE
}

View File

@ -11,7 +11,7 @@ const { expect } = chai
import { EventRepository } from '../../../src/repositories/event-repository'
describe.only('EventRepository', () => {
describe('EventRepository', () => {
let repository: IEventRepository
let sandbox: sinon.SinonSandbox