Skip to content

Commit

Permalink
fix: deleting parameterized replaceable event before event (#354)
Browse files Browse the repository at this point in the history
  • Loading branch information
cameri authored Jan 12, 2024
1 parent e4cecc8 commit 030bbb3
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 75 deletions.
1 change: 0 additions & 1 deletion src/@types/repositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ export interface IEventRepository {
create(event: Event): Promise<number>
upsert(event: Event): Promise<number>
findByFilters(filters: SubscriptionFilter[]): IQueryResult<DBEvent[]>
insertStubs(pubkey: string, eventIdsToDelete: EventId[]): Promise<number>
deleteByPubkeyAndIds(pubkey: Pubkey, ids: EventId[]): Promise<number>
}

Expand Down
8 changes: 1 addition & 7 deletions src/handlers/event-strategies/delete-event-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,10 @@ export class DeleteEventStrategy implements IEventStrategy<Event, Promise<void>>
)

if (eventIdsToDelete.length) {
const count = await this.eventRepository.deleteByPubkeyAndIds(
await this.eventRepository.deleteByPubkeyAndIds(
event.pubkey,
eventIdsToDelete
)
if (!count) {
await this.eventRepository.insertStubs(
event.pubkey,
eventIdsToDelete,
)
}
}

const count = await this.eventRepository.create(event)
Expand Down
26 changes: 1 addition & 25 deletions src/repositories/event-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
forEach,
forEachObjIndexed,
groupBy,
identity,
ifElse,
invoker,
is,
Expand Down Expand Up @@ -229,6 +228,7 @@ export class EventRepository implements IEventRepository {
prop(EventExpirationTimeMetadataKey as any),
always(null),
),
deleted_at: always(null),
})(event)

const query = this.masterDbClient('events')
Expand All @@ -250,30 +250,6 @@ export class EventRepository implements IEventRepository {
} as Promise<number>
}

public insertStubs(pubkey: string, eventIdsToDelete: EventId[]): Promise<number> {
debug('inserting stubs for %s: %o', pubkey, eventIdsToDelete)
const date = new Date()
return this.masterDbClient('events').insert(
eventIdsToDelete.map(
applySpec({
event_id: pipe(identity, toBuffer),
event_pubkey: pipe(always(pubkey), toBuffer),
event_created_at: always(Math.floor(date.getTime() / 1000)),
event_kind: always(5),
event_tags: always('[]'),
event_content: always(''),
event_signature: pipe(always(''), toBuffer),
event_delegator: always(null),
event_deduplication: pipe(always([pubkey, 5]), toJSON),
expires_at: always(null),
deleted_at: always(date.toISOString()),
})
)
)
.onConflict()
.ignore() as Promise<any>
}

public deleteByPubkeyAndIds(pubkey: string, eventIdsToDelete: EventId[]): Promise<number> {
debug('deleting events from %s: %o', pubkey, eventIdsToDelete)

Expand Down
6 changes: 1 addition & 5 deletions test/integration/features/nip-09/nip-09.feature
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,10 @@ Feature: NIP-09
And Alice drafts a text_note event with content "Twitter > Nostr"
When Alice sends a delete event for their last event
And Alice sends their last draft event successfully
And Alice subscribes to author Alice
Then Alice receives 1 delete event from Alice and EOSE

Scenario: Alice sends a delete before deleted set_metadata
Given someone called Alice
And someone called Bob
And Alice drafts a set_metadata event
When Alice sends a delete event for their last event
And Alice sends their last draft event unsuccessfully
And Alice subscribes to author Alice
Then Alice receives 1 delete event from Alice and EOSE
Then Alice sends their last draft event successfully
21 changes: 21 additions & 0 deletions test/integration/features/nip-33/nip-33.feature
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,24 @@ Feature: NIP-33 Parameterized replaceable events
And Alice sends a parameterized_replaceable_event_1 event with content "third" and tag d containing "friends"
And Bob subscribes to author Alice
Then Bob receives a parameterized_replaceable_event_1 event from Alice with content "third" and tag d containing "friends"

Scenario: Alice deletes a parameterized replaceable event
Given someone called Alice
When Alice sends a parameterized_replaceable_event_1 event with content "exercise" and tag d containing "2023-resolutions"
And Alice sends a delete event for their last event
And Alice subscribes to author Alice
Then Alice receives 1 delete event from Alice and EOSE

Scenario: Alice deletes and replaces a parameterized replaceable event
Given someone called Alice
And Alice sends a parameterized_replaceable_event_1 event with content "gym" and tag d containing "2024-resolutions"
And Alice sends a delete event for their last event
When Alice sends a parameterized_replaceable_event_1 event with content "exercise" and tag d containing "2024-resolutions"
And Alice subscribes to parameterized_replaceable_event_1 events from Alice
Then Alice receives a parameterized_replaceable_event_1 event from Alice with content "exercise" and tag d containing "2024-resolutions"

Scenario: Alice deletes before sending parameterized replaceable event
Given someone called Alice
And Alice drafts a parameterized_replaceable_event_2 event with content "don't worry about it" and tag d containing "topsycrets"
When Alice sends a delete event for their last event
And Alice sends their last draft event successfully
42 changes: 40 additions & 2 deletions test/integration/features/nip-33/nip-33.feature.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Then, When } from '@cucumber/cucumber'
import { Then, When, World } from '@cucumber/cucumber'
import { expect } from 'chai'
import WebSocket from 'ws'

import { createEvent, sendEvent, waitForEventCount, waitForNextEvent } from '../helpers'
import { createEvent, createSubscription, sendEvent, waitForEventCount, waitForNextEvent } from '../helpers'
import { EventKinds, EventTags } from '../../../../src/constants/base'
import { Event } from '../../../../src/@types/event'
import { isDraft } from '../shared'

When(/^(\w+) sends a parameterized_replaceable_event_0 event with content "([^"]+)" and tag (\w) containing "([^"]+)"$/, async function(
name: string,
Expand Down Expand Up @@ -108,3 +109,40 @@ Then(/(\w+) receives (\d+) parameterized_replaceable_event_0 events? from (\w+)
expect(events[0].pubkey).to.equal(this.parameters.identities[author].pubkey)
expect(events[0].content).to.equal(content)
})

When(/^(\w+) subscribes to parameterized_replaceable_event_1 events from (\w+)$/, async function (this: World<Record<string, any>>, name: string, author: string) {
const ws = this.parameters.clients[name] as WebSocket
const authorPubkey = this.parameters.identities[author].pubkey
const subscription = {
name: `test-${Math.random()}`,
filters: [
{ kinds: [EventKinds.PARAMETERIZED_REPLACEABLE_FIRST + 1], authors: [authorPubkey] },
],
}
this.parameters.subscriptions[name].push(subscription)

await createSubscription(ws, subscription.name, subscription.filters)
})


Then(/^(\w+) drafts a parameterized_replaceable_event_2 event with content "([^"]+?)" and tag (\w+) containing "([^"]+?)"$/, async function (
name: string,
content: string,
tagName: string,
tagValue: string,
) {
const { pubkey, privkey } = this.parameters.identities[name]

const event: Event = await createEvent({
pubkey,
kind: EventKinds.PARAMETERIZED_REPLACEABLE_FIRST + 2,
content,
tags: [
[tagName, tagValue],
],
}, privkey)

event[isDraft] = true

this.parameters.events[name].push(event)
})
15 changes: 0 additions & 15 deletions test/unit/handlers/event-strategies/delete-event-strategy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ describe('DeleteEventStrategy', () => {
let webSocketEmitStub: Sinon.SinonStub
let eventRepositoryCreateStub: Sinon.SinonStub
let eventRepositoryDeleteByPubkeyAndIdsStub: Sinon.SinonStub
let eventRepositoryInsertStubsStub: Sinon.SinonStub

let strategy: IEventStrategy<Event, Promise<void>>

Expand All @@ -43,7 +42,6 @@ describe('DeleteEventStrategy', () => {

eventRepositoryCreateStub = sandbox.stub(EventRepository.prototype, 'create')
eventRepositoryDeleteByPubkeyAndIdsStub = sandbox.stub(EventRepository.prototype, 'deleteByPubkeyAndIds')
eventRepositoryInsertStubsStub = sandbox.stub(EventRepository.prototype, 'insertStubs')

webSocketEmitStub = sandbox.stub()
webSocket = {
Expand All @@ -67,18 +65,6 @@ describe('DeleteEventStrategy', () => {
expect(eventRepositoryCreateStub).to.have.been.calledOnceWithExactly(event)
})

it('inserts stubs', async () => {
await strategy.execute(event)

expect(eventRepositoryInsertStubsStub).to.have.been.calledOnceWithExactly(
event.pubkey,
[
'0000000000000000000000000000000000000000000000000000000000000000',
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
]
)
})

it('deletes events if it has e tags', async () => {
await strategy.execute(event)

Expand Down Expand Up @@ -135,7 +121,6 @@ describe('DeleteEventStrategy', () => {

expect(eventRepositoryCreateStub).to.have.been.calledOnceWithExactly(event)
expect(eventRepositoryDeleteByPubkeyAndIdsStub).not.to.have.been.called
expect(eventRepositoryInsertStubsStub).to.not.have.been.called
expect(webSocketEmitStub).not.to.have.been.called
})
})
Expand Down
22 changes: 2 additions & 20 deletions test/unit/repositories/event-repository.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,24 +439,6 @@ describe('EventRepository', () => {
})
})

describe('insertStubs', () => {
let clock: sinon.SinonFakeTimers

beforeEach(() => {
clock = sinon.useFakeTimers(1673835425)
})

afterEach(() => {
clock.restore()
})

it('insert stubs by pubkey & event ids', () => {
const query = repository.insertStubs('001122', ['aabbcc', 'ddeeff']).toString()

expect(query).to.equal('insert into "events" ("deleted_at", "event_content", "event_created_at", "event_deduplication", "event_delegator", "event_id", "event_kind", "event_pubkey", "event_signature", "event_tags", "expires_at") values (\'1970-01-20T08:57:15.425Z\', \'\', 1673835, \'["001122",5]\', NULL, X\'aabbcc\', 5, X\'001122\', X\'\', \'[]\', NULL), (\'1970-01-20T08:57:15.425Z\', \'\', 1673835, \'["001122",5]\', NULL, X\'ddeeff\', 5, X\'001122\', X\'\', \'[]\', NULL) on conflict do nothing')
})
})

describe('deleteByPubkeyAndIds', () => {
it('marks event as deleted by pubkey & event_id if not deleted', () => {
const query = repository.deleteByPubkeyAndIds('001122', ['aabbcc', 'ddeeff']).toString()
Expand All @@ -480,7 +462,7 @@ describe('EventRepository', () => {

const query = repository.upsert(event).toString()

expect(query).to.equal('insert into "events" ("event_content", "event_created_at", "event_deduplication", "event_delegator", "event_id", "event_kind", "event_pubkey", "event_signature", "event_tags", "expires_at", "remote_address") values (\'{"name":"ottman@minds.io","about":"","picture":"https://feat-2311-nostr.minds.io/icon/1002952989368913934/medium/1564498626/1564498626/1653379539"}\', 1564498626, \'["55b702c167c85eb1c2d5ab35d68bedd1a35b94c01147364d2395c2f66f35a503",0]\', NULL, X\'e527fe8b0f64a38c6877f943a9e8841074056ba72aceb31a4c85e6d10b27095a\', 0, X\'55b702c167c85eb1c2d5ab35d68bedd1a35b94c01147364d2395c2f66f35a503\', X\'d1de98733de2b412549aa64454722d9b66ab3c68e9e0d0f9c5d42e7bd54c30a06174364b683d2c8dbb386ff47f31e6cb7e2f3c3498d8819ee80421216c8309a9\', \'[]\', NULL, \'::1\') on conflict (event_pubkey, event_kind, event_deduplication) WHERE (event_kind = 0 OR event_kind = 3 OR event_kind = 41 OR (event_kind >= 10000 AND event_kind < 20000)) OR (event_kind >= 30000 AND event_kind < 40000) do update set "event_id" = X\'e527fe8b0f64a38c6877f943a9e8841074056ba72aceb31a4c85e6d10b27095a\',"event_created_at" = 1564498626,"event_tags" = \'[]\',"event_content" = \'{"name":"ottman@minds.io","about":"","picture":"https://feat-2311-nostr.minds.io/icon/1002952989368913934/medium/1564498626/1564498626/1653379539"}\',"event_signature" = X\'d1de98733de2b412549aa64454722d9b66ab3c68e9e0d0f9c5d42e7bd54c30a06174364b683d2c8dbb386ff47f31e6cb7e2f3c3498d8819ee80421216c8309a9\',"event_delegator" = NULL,"remote_address" = \'::1\',"expires_at" = NULL where "events"."event_created_at" < 1564498626')
expect(query).to.equal('insert into "events" ("deleted_at", "event_content", "event_created_at", "event_deduplication", "event_delegator", "event_id", "event_kind", "event_pubkey", "event_signature", "event_tags", "expires_at", "remote_address") values (NULL, \'{"name":"ottman@minds.io","about":"","picture":"https://feat-2311-nostr.minds.io/icon/1002952989368913934/medium/1564498626/1564498626/1653379539"}\', 1564498626, \'["55b702c167c85eb1c2d5ab35d68bedd1a35b94c01147364d2395c2f66f35a503",0]\', NULL, X\'e527fe8b0f64a38c6877f943a9e8841074056ba72aceb31a4c85e6d10b27095a\', 0, X\'55b702c167c85eb1c2d5ab35d68bedd1a35b94c01147364d2395c2f66f35a503\', X\'d1de98733de2b412549aa64454722d9b66ab3c68e9e0d0f9c5d42e7bd54c30a06174364b683d2c8dbb386ff47f31e6cb7e2f3c3498d8819ee80421216c8309a9\', \'[]\', NULL, \'::1\') on conflict (event_pubkey, event_kind, event_deduplication) WHERE (event_kind = 0 OR event_kind = 3 OR event_kind = 41 OR (event_kind >= 10000 AND event_kind < 20000)) OR (event_kind >= 30000 AND event_kind < 40000) do update set "event_id" = X\'e527fe8b0f64a38c6877f943a9e8841074056ba72aceb31a4c85e6d10b27095a\',"event_created_at" = 1564498626,"event_tags" = \'[]\',"event_content" = \'{"name":"ottman@minds.io","about":"","picture":"https://feat-2311-nostr.minds.io/icon/1002952989368913934/medium/1564498626/1564498626/1653379539"}\',"event_signature" = X\'d1de98733de2b412549aa64454722d9b66ab3c68e9e0d0f9c5d42e7bd54c30a06174364b683d2c8dbb386ff47f31e6cb7e2f3c3498d8819ee80421216c8309a9\',"event_delegator" = NULL,"remote_address" = \'::1\',"expires_at" = NULL,"deleted_at" = NULL where "events"."event_created_at" < 1564498626')
})

it('replaces event based on event_pubkey, event_kind and event_deduplication', () => {
Expand All @@ -498,7 +480,7 @@ describe('EventRepository', () => {

const query = repository.upsert(event).toString()

expect(query).to.equal('insert into "events" ("event_content", "event_created_at", "event_deduplication", "event_delegator", "event_id", "event_kind", "event_pubkey", "event_signature", "event_tags", "expires_at", "remote_address") values (\'{"name":"ottman@minds.io","about":"","picture":"https://feat-2311-nostr.minds.io/icon/1002952989368913934/medium/1564498626/1564498626/1653379539"}\', 1564498626, \'["deduplication"]\', NULL, X\'e527fe8b0f64a38c6877f943a9e8841074056ba72aceb31a4c85e6d10b27095a\', 0, X\'55b702c167c85eb1c2d5ab35d68bedd1a35b94c01147364d2395c2f66f35a503\', X\'d1de98733de2b412549aa64454722d9b66ab3c68e9e0d0f9c5d42e7bd54c30a06174364b683d2c8dbb386ff47f31e6cb7e2f3c3498d8819ee80421216c8309a9\', \'[]\', NULL, \'::1\') on conflict (event_pubkey, event_kind, event_deduplication) WHERE (event_kind = 0 OR event_kind = 3 OR event_kind = 41 OR (event_kind >= 10000 AND event_kind < 20000)) OR (event_kind >= 30000 AND event_kind < 40000) do update set "event_id" = X\'e527fe8b0f64a38c6877f943a9e8841074056ba72aceb31a4c85e6d10b27095a\',"event_created_at" = 1564498626,"event_tags" = \'[]\',"event_content" = \'{"name":"ottman@minds.io","about":"","picture":"https://feat-2311-nostr.minds.io/icon/1002952989368913934/medium/1564498626/1564498626/1653379539"}\',"event_signature" = X\'d1de98733de2b412549aa64454722d9b66ab3c68e9e0d0f9c5d42e7bd54c30a06174364b683d2c8dbb386ff47f31e6cb7e2f3c3498d8819ee80421216c8309a9\',"event_delegator" = NULL,"remote_address" = \'::1\',"expires_at" = NULL where "events"."event_created_at" < 1564498626')
expect(query).to.equal('insert into "events" ("deleted_at", "event_content", "event_created_at", "event_deduplication", "event_delegator", "event_id", "event_kind", "event_pubkey", "event_signature", "event_tags", "expires_at", "remote_address") values (NULL, \'{"name":"ottman@minds.io","about":"","picture":"https://feat-2311-nostr.minds.io/icon/1002952989368913934/medium/1564498626/1564498626/1653379539"}\', 1564498626, \'["deduplication"]\', NULL, X\'e527fe8b0f64a38c6877f943a9e8841074056ba72aceb31a4c85e6d10b27095a\', 0, X\'55b702c167c85eb1c2d5ab35d68bedd1a35b94c01147364d2395c2f66f35a503\', X\'d1de98733de2b412549aa64454722d9b66ab3c68e9e0d0f9c5d42e7bd54c30a06174364b683d2c8dbb386ff47f31e6cb7e2f3c3498d8819ee80421216c8309a9\', \'[]\', NULL, \'::1\') on conflict (event_pubkey, event_kind, event_deduplication) WHERE (event_kind = 0 OR event_kind = 3 OR event_kind = 41 OR (event_kind >= 10000 AND event_kind < 20000)) OR (event_kind >= 30000 AND event_kind < 40000) do update set "event_id" = X\'e527fe8b0f64a38c6877f943a9e8841074056ba72aceb31a4c85e6d10b27095a\',"event_created_at" = 1564498626,"event_tags" = \'[]\',"event_content" = \'{"name":"ottman@minds.io","about":"","picture":"https://feat-2311-nostr.minds.io/icon/1002952989368913934/medium/1564498626/1564498626/1653379539"}\',"event_signature" = X\'d1de98733de2b412549aa64454722d9b66ab3c68e9e0d0f9c5d42e7bd54c30a06174364b683d2c8dbb386ff47f31e6cb7e2f3c3498d8819ee80421216c8309a9\',"event_delegator" = NULL,"remote_address" = \'::1\',"expires_at" = NULL,"deleted_at" = NULL where "events"."event_created_at" < 1564498626')
})
})
})

0 comments on commit 030bbb3

Please sign in to comment.